增加版本标识,同时更新LLM PRO模型列表

This commit is contained in:
Kurosu
2026-02-12 17:40:23 +08:00
parent 9cbdddaaf5
commit 25a715d690
5 changed files with 169 additions and 75 deletions
+4 -2
View File
@@ -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
View File
@@ -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 解析错误,尝试获取原始响应内容帮助调试
+9
View File
@@ -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 = ""
+97
View File
@@ -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")