增加版本标识,同时更新LLM PRO模型列表
This commit is contained in:
+4
-2
@@ -9,11 +9,13 @@ NODE_CLASS_MAPPINGS = {
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RN Translator": "RunNode Translater",
|
||||
"RN Prompt Translator": "RunNode Prompt Translater",
|
||||
"RN Translator": "RunNode Translator",
|
||||
"RN Prompt Translator": "RunNode Prompt Translator",
|
||||
"RN Midjourney Prompter": "Midjourney Style Prompter",
|
||||
"RN LLM API": "RunNode LLM API Node",
|
||||
"RN LLM API Pro": "RunNode LLM API Pro Node",
|
||||
}
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
|
||||
__version__ = "1.0"
|
||||
|
||||
+59
-73
@@ -3,14 +3,45 @@ import time
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import torch
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import base64
|
||||
from ComfyUI_RN_External_Interface.utils import generate_request_id, log_prepare, log_complete, log_error, ProgressBar
|
||||
from .utils import generate_request_id, log_prepare, log_complete, log_error, ProgressBar
|
||||
|
||||
ENV_KEYS_API_KEY = ["COMFYUI_RN_API_KEY", "COMFLY_API_KEY", "RUNNODE_API_KEY", "RN_API_KEY", "LLM_API_KEY", "OPENAI_API_KEY", "DEEPSEEK_API_KEY"]
|
||||
ENV_KEYS_BASE_URL = ["COMFYUI_RN_BASE_URL", "COMFLY_BASE_URL", "RUNNODE_BASE_URL", "RN_BASE_URL", "LLM_API_BASEURL", "OPENAI_BASE_URL", "OPENAI_API_BASE_URL", "DEEPSEEK_API_BASE_URL"]
|
||||
|
||||
MODEL_MAPPING = {
|
||||
"Kimi-K2.5": "moonshotai/Kimi-K2.5",
|
||||
"Kimi-K2-Thinking": "moonshotai/Kimi-K2-Thinking",
|
||||
"Qwen2.5-32B-Instruct": "Qwen/Qwen2.5-32B-Instruct",
|
||||
"Qwen2.5-72B-Instruct": "Qwen/Qwen2.5-72B-Instruct",
|
||||
"Qwen2.5-VL-32B-Instruct": "Qwen/Qwen2.5-VL-32B-Instruct",
|
||||
"Qwen2.5-VL-72B-Instruct": "Qwen/Qwen2.5-VL-72B-Instruct",
|
||||
"Qwen2.5-Omni-7B": "Qwen/Qwen2.5-Omni-7B",
|
||||
"Qwen3-32B": "Qwen/Qwen3-32B",
|
||||
"Qwen3-Embedding-8B": "Qwen/Qwen3-Embedding-8B",
|
||||
"Qwen3-Reranker-8B": "Qwen/Qwen3-Reranker-8B",
|
||||
"Qwen3-Coder-480B-A35B-Instruct": "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||
"Qwen3-VL-30B-A3B-Instruct": "Qwen/Qwen3-VL-30B-A3B-Instruct",
|
||||
"QwQ-32B": "Qwen/QwQ-32B",
|
||||
"DeepSeek-R1": "deepseek-ai/DeepSeek-R1",
|
||||
"DeepSeek-R1-Distill-Qwen-32B": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
|
||||
"DeepSeek-R1-0528": "deepseek-ai/DeepSeek-R1-0528",
|
||||
"DeepSeek-V3": "deepseek-ai/DeepSeek-V3",
|
||||
"DeepSeek-V3-0324": "deepseek-ai/DeepSeek-V3-0324",
|
||||
"DeepSeek-OCR": "deepseek-ai/DeepSeek-OCR",
|
||||
"GD-DeepSeek-R1": "GD/DeepSeek-R1",
|
||||
"glm-4-9b-chat": "glm-4-9b-chat",
|
||||
"GLM-4-32B-0414": "ZhipuAI/GLM-4-32B-0414",
|
||||
"GLM-4.1V-9B-Thinking": "ZhipuAI/GLM-4.1V-9B-Thinking",
|
||||
"GLM-4.6": "ZhipuAI/GLM-4.6",
|
||||
"bge-m3": "bge-m3",
|
||||
"bge-reranker-v2-m3": "bge-reranker-v2-m3",
|
||||
"PaddleOCR-VL-0.9B": "PaddleOCR-VL-0.9B",
|
||||
}
|
||||
|
||||
def get_first_env(names):
|
||||
for n in names:
|
||||
v = os.environ.get(n)
|
||||
@@ -30,8 +61,8 @@ def _normalize_baseurl(u):
|
||||
def get_config():
|
||||
try:
|
||||
config_paths = [
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'ComfyUI_rn_translator-config.json'),
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'comfyui_rn_translator-config.json')
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'ComfyUI_rn_prompter-config.json'),
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'comfyui_rn_prompter-config.json')
|
||||
]
|
||||
|
||||
config_path = None
|
||||
@@ -108,8 +139,8 @@ def save_config(config):
|
||||
def get_vllm_config():
|
||||
try:
|
||||
config_paths = [
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'ComfyUI_rn_translator-config.json'),
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'comfyui_rn_translator-config.json')
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'ComfyUI_rn_prompter-config.json'),
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'comfyui_rn_prompter-config.json')
|
||||
]
|
||||
|
||||
config_path = None
|
||||
@@ -183,8 +214,8 @@ def get_model_config(model_name):
|
||||
try:
|
||||
# 修改配置文件路径为新的配置文件
|
||||
config_paths = [
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'ComfyUI_rn_translator-config.json'),
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'comfyui_rn_translator-config.json')
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'ComfyUI_rn_prompter-config.json'),
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "config", 'comfyui_rn_prompter-config.json')
|
||||
]
|
||||
|
||||
config_path = None
|
||||
@@ -199,25 +230,7 @@ def get_model_config(model_name):
|
||||
config = json.load(f)
|
||||
|
||||
# 模型名称映射字典(前端显示名称 -> 配置中的模型名称)
|
||||
mapping = {
|
||||
"Qwen2.5-32B-Instruct": "Qwen/Qwen2.5-32B-Instruct",
|
||||
"Qwen2.5-72B-Instruct": "Qwen/Qwen2.5-72B-Instruct",
|
||||
"Qwen2.5-VL-32B-Instruct": "Qwen/Qwen2.5-VL-32B-Instruct",
|
||||
"Qwen2.5-Omni-7B": "Qwen/Qwen2.5-Omni-7B",
|
||||
"Qwen3-32B": "Qwen/Qwen3-32B",
|
||||
"Qwen3-Embedding-8B": "Qwen/Qwen3-Embedding-8B",
|
||||
"Qwen3-Reranker-8B": "Qwen/Qwen3-Reranker-8B",
|
||||
"Qwen3-Coder-480B-A35B-Instruct": "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||
"DeepSeek-R1-Distill-Qwen-32B": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
|
||||
"DeepSeek-R1-0528": "deepseek-ai/DeepSeek-R1-0528",
|
||||
"DeepSeek-V3-0324": "deepseek-ai/DeepSeek-V3-0324",
|
||||
"glm-4-9b-chat": "glm-4-9b-chat",
|
||||
"GLM-4-32B-0414": "ZhipuAI/GLM-4-32B-0414",
|
||||
"GLM-4.1V-9B-Thinking": "ZhipuAI/GLM-4.1V-9B-Thinking",
|
||||
"GLM-4.6": "ZhipuAI/GLM-4.6",
|
||||
"bge-m3": "bge-m3",
|
||||
"bge-reranker-v2-m3": "bge-reranker-v2-m3",
|
||||
}
|
||||
mapping = MODEL_MAPPING
|
||||
|
||||
# 获取映射后的模型名称
|
||||
mapped_name = mapping.get(model_name, model_name)
|
||||
@@ -847,8 +860,13 @@ class RN_LLMAPI_Node():
|
||||
vllm_cfg = get_vllm_config()
|
||||
has_visual = (ref_image is not None) or (video is not None)
|
||||
selected_cfg = vllm_cfg if has_visual else cfg
|
||||
used_api_baseurl = _normalize_baseurl(api_baseurl or os.environ.get("COMFYUI_RN_BASE_URL") or selected_cfg.get("base_url"))
|
||||
used_api_key = (api_key or os.environ.get("COMFYUI_RN_API_KEY") or selected_cfg.get("api_key") or "")
|
||||
|
||||
# 处理输入回退逻辑:只有当输入不为空(且不全是空格)时才使用输入值
|
||||
used_api_baseurl = api_baseurl if api_baseurl and api_baseurl.strip() else None
|
||||
used_api_key = api_key if api_key and api_key.strip() else None
|
||||
|
||||
used_api_baseurl = _normalize_baseurl(used_api_baseurl or os.environ.get("COMFYUI_RN_BASE_URL") or selected_cfg.get("base_url"))
|
||||
used_api_key = (used_api_key or os.environ.get("COMFYUI_RN_API_KEY") or selected_cfg.get("api_key") or "")
|
||||
used_model = (model or selected_cfg.get("model") or ("qwen25-vl-32b-instruct" if has_visual else "gpt-4o-mini"))
|
||||
|
||||
client = OpenAI(api_key=used_api_key, base_url=used_api_baseurl)
|
||||
@@ -919,24 +937,7 @@ class RN_LLMAPI_Pro_Node():
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"model": (["Qwen2.5-32B-Instruct",
|
||||
"Qwen2.5-72B-Instruct",
|
||||
"Qwen2.5-VL-32B-Instruct",
|
||||
"Qwen2.5-Omni-7B",
|
||||
"Qwen3-32B",
|
||||
"Qwen3-Embedding-8B",
|
||||
"Qwen3-Reranker-8B",
|
||||
"Qwen3-Coder-480B-A35B-Instruct",
|
||||
"DeepSeek-R1-Distill-Qwen-32B",
|
||||
"DeepSeek-R1-0528",
|
||||
"DeepSeek-V3-0324",
|
||||
"glm-4-9b-chat",
|
||||
"GLM-4-32B-0414",
|
||||
"GLM-4.1V-9B-Thinking",
|
||||
"GLM-4.6",
|
||||
"bge-m3",
|
||||
"bge-reranker-v2-m3",
|
||||
], {"default": "Qwen2.5-VL-32B-Instruct"}),
|
||||
"model": (list(MODEL_MAPPING.keys()), {"default": "Kimi-K2.5"}),
|
||||
"role": ("STRING", {"multiline": True, "default": "You are a helpful assistant"}),
|
||||
"prompt": ("STRING", {"multiline": True, "default": "Hello"}),
|
||||
"temperature": ("FLOAT", {"default": 0.6}),
|
||||
@@ -966,41 +967,21 @@ class RN_LLMAPI_Pro_Node():
|
||||
# 获取模型对应的配置
|
||||
model_config = get_model_config(model)
|
||||
|
||||
# 处理输入回退逻辑:只有当输入不为空(且不全是空格)时才使用输入值
|
||||
used_api_baseurl = api_baseurl if api_baseurl and api_baseurl.strip() else None
|
||||
used_api_key = api_key if api_key and api_key.strip() else None
|
||||
|
||||
# 优先级: 输入参数 > 环境变量 > 模型特定配置 > 全局配置
|
||||
used_api_baseurl = api_baseurl or env_base_url or model_config.get('base_url', '')
|
||||
used_api_key = api_key or env_api_key or model_config.get('api_key', '')
|
||||
used_api_baseurl = used_api_baseurl or env_base_url or model_config.get('base_url', '') or get_config().get('base_url', '')
|
||||
used_api_key = used_api_key or env_api_key or model_config.get('api_key', '') or get_config().get('api_key', '')
|
||||
used_model = model_config.get('model', '') # 使用配置中的模型名称
|
||||
|
||||
# 如果配置中没有模型名称,使用映射后的名称
|
||||
if not used_model:
|
||||
# 模型名称映射字典
|
||||
mapping = {
|
||||
"Qwen2.5-32B-Instruct": "Qwen/Qwen2.5-32B-Instruct",
|
||||
"Qwen2.5-72B-Instruct": "Qwen/Qwen2.5-72B-Instruct",
|
||||
"Qwen2.5-VL-32B-Instruct": "Qwen/Qwen2.5-VL-32B-Instruct",
|
||||
"Qwen2.5-Omni-7B": "Qwen/Qwen2.5-Omni-7B",
|
||||
"Qwen3-32B": "Qwen/Qwen3-32B",
|
||||
"Qwen3-Embedding-8B": "Qwen/Qwen3-Embedding-8B",
|
||||
"Qwen3-Reranker-8B": "Qwen/Qwen3-Reranker-8B",
|
||||
"Qwen3-Coder-480B-A35B-Instruct": "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||
"DeepSeek-R1-Distill-Qwen-32B": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
|
||||
"DeepSeek-R1-0528": "deepseek-ai/DeepSeek-R1-0528",
|
||||
"DeepSeek-V3-0324": "deepseek-ai/DeepSeek-V3-0324",
|
||||
"glm-4-9b-chat": "glm-4-9b-chat",
|
||||
"GLM-4-32B-0414": "ZhipuAI/GLM-4-32B-0414",
|
||||
"GLM-4.1V-9B-Thinking": "ZhipuAI/GLM-4.1V-9B-Thinking",
|
||||
"GLM-4.6": "ZhipuAI/GLM-4.6",
|
||||
"bge-m3": "bge-m3",
|
||||
"bge-reranker-v2-m3": "bge-reranker-v2-m3",
|
||||
}
|
||||
mapping = MODEL_MAPPING
|
||||
used_model = mapping.get(model, model)
|
||||
|
||||
# 如果base_url或api_key为空,使用默认的get_config作为后备
|
||||
if not used_api_baseurl or not used_api_key:
|
||||
cfg = get_config()
|
||||
used_api_baseurl = used_api_baseurl or cfg.get('base_url', '')
|
||||
used_api_key = used_api_key or cfg.get('api_key', '')
|
||||
|
||||
# 最终的检查,确保有base_url
|
||||
if not used_api_baseurl:
|
||||
return ("ERROR: No API base URL configured. Please check your configuration.",)
|
||||
@@ -1050,6 +1031,11 @@ class RN_LLMAPI_Pro_Node():
|
||||
prompt = 'Error: No response from API (Empty choices)'
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "not a multimodal model" in error_msg:
|
||||
prompt = f"{used_model}不是多模态模型,无法识别图片,请重新选择模型"
|
||||
rn_pbar.error(prompt)
|
||||
return (prompt,)
|
||||
|
||||
print(f"API call error: {error_msg}")
|
||||
|
||||
# 诊断:如果是 JSON 解析错误,尝试获取原始响应内容帮助调试
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "comfyui-rn-prompter"
|
||||
description = "RunNode prompter nodes for ComfyUI"
|
||||
version = "1.0"
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "SF-Runnode"
|
||||
DisplayName = "RunNode Prompter"
|
||||
Icon = ""
|
||||
@@ -0,0 +1,97 @@
|
||||
import sys
|
||||
import io
|
||||
import time
|
||||
import threading
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
|
||||
if sys.platform == 'win32' and sys.stdout.encoding != 'utf-8':
|
||||
try:
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
PREFIX = "✨"
|
||||
ERROR_PREFIX = "✨-❌"
|
||||
PROCESS_PREFIX = "✨"
|
||||
REQUEST_PREFIX = "✨"
|
||||
WARN_PREFIX = "✨-⚠️"
|
||||
|
||||
def _enable_windows_vt():
|
||||
if os.name == 'nt':
|
||||
try:
|
||||
import ctypes
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
handle = kernel32.GetStdHandle(-11)
|
||||
mode = ctypes.c_ulong()
|
||||
kernel32.GetConsoleMode(handle, ctypes.byref(mode))
|
||||
kernel32.SetConsoleMode(handle, mode.value | 0x0004)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_enable_windows_vt()
|
||||
|
||||
def sanitize_sensitive_network_info(text: str) -> str:
|
||||
try:
|
||||
t = str(text)
|
||||
t = re.sub(r"(host=')([^']+)(')", r"\1<hidden-host>\3", t)
|
||||
t = re.sub(r'(host=")([^"]+)(")', r'\1<hidden-host>\3', t)
|
||||
t = re.sub(r"(port=)(\d+)", r"\1<hidden-port>", t)
|
||||
t = re.sub(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", "<hidden-ip>", t)
|
||||
t = re.sub(r"\b(?:[0-9A-Fa-f]{1,4}:){2,7}[0-9A-Fa-f]{1,4}\b", "<hidden-ipv6>", t)
|
||||
t = re.sub(r"\b::(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}\b", "<hidden-ipv6>", t)
|
||||
t = re.sub(r"(Connection to )([^\s]+)", r"\1<hidden-host>", t)
|
||||
hide_url_hosts = os.environ.get("RUNNODE_HIDE_URL_HOSTS", "true").lower() != "false"
|
||||
if hide_url_hosts:
|
||||
t = re.sub(r"(https?://)([^/\s]+)", r"\1<hidden-host>", t)
|
||||
return t
|
||||
except Exception:
|
||||
return str(text)
|
||||
|
||||
def generate_request_id(task_type: str, provider: str) -> str:
|
||||
short_uuid = str(uuid.uuid4())[:8]
|
||||
return f"rn_{provider}_{task_type}_{short_uuid}"
|
||||
|
||||
def log_prepare(task_name, request_id, prefix, service_name, model_version=None, speed=None, **kwargs):
|
||||
info = f" {kwargs}" if kwargs else ""
|
||||
if model_version: info += f" model_version={model_version}"
|
||||
if speed: info += f" speed={speed}"
|
||||
print(f"{PREFIX} {prefix} [{task_name}] {request_id} Preparing...{info}")
|
||||
|
||||
def log_complete(task_name, request_id, prefix, service_name, image_url=None, **kwargs):
|
||||
info = f" {kwargs}" if kwargs else ""
|
||||
if image_url: info += f" image_url={image_url}"
|
||||
print(f"{PREFIX} {prefix} [{task_name}] {request_id} Completed.{info}")
|
||||
|
||||
def log_error(message, request_id=None, detail=None, source="RunNode", service_name=None):
|
||||
if service_name:
|
||||
print(f"{ERROR_PREFIX} [{source}] {service_name} Error: {message} - {str(detail)}")
|
||||
else:
|
||||
print(f"{ERROR_PREFIX} [{source}] Error: {message} - {str(detail)}")
|
||||
|
||||
class ProgressBar:
|
||||
def __init__(self, request_id, service_name, extra_info="", streaming=True, task_type="Task", source="RunNode"):
|
||||
self.request_id = request_id
|
||||
self.service_name = service_name
|
||||
self.streaming = streaming
|
||||
self.last_update = time.time()
|
||||
|
||||
def update_absolute(self, value):
|
||||
if time.time() - self.last_update > 0.5:
|
||||
print(f"{PROCESS_PREFIX} Progress: {value}%")
|
||||
self.last_update = time.time()
|
||||
|
||||
def update(self, value):
|
||||
self.update_absolute(value)
|
||||
|
||||
def set_generating(self):
|
||||
if self.streaming:
|
||||
print(f"{PROCESS_PREFIX} Generating...")
|
||||
|
||||
def error(self, message):
|
||||
print(f"{ERROR_PREFIX} Error: {sanitize_sensitive_network_info(str(message))}")
|
||||
|
||||
def done(self, char_count=0, elapsed_ms=0):
|
||||
print(f"{PREFIX} Done in {elapsed_ms}ms")
|
||||
Reference in New Issue
Block a user