Files
2025-06-06 23:05:21 +08:00

143 lines
5.0 KiB
Python

import os
import json
import logging
from typing import Optional, List
from openai import OpenAI, Stream
from openai.types.chat import ChatCompletionChunk
# ----------------------------
# 日志系统配置
# ----------------------------
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
log_path = os.path.join(desktop_path, "deepseek_comfyui.log")
logging.basicConfig(
filename=log_path,
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filemode='w'
)
logger = logging.getLogger("ComfyUI-DeepSeek")
logger.addHandler(logging.StreamHandler())
class ComfyUI_LLM_Online:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"api_key": ("STRING", {"default": "", "password": True}),
"input_str": ("STRING", {"multiline": True, "dynamicPrompts": False}),
"model": (["deepseek-chat", "deepseek-coder","deepseek-reasoner"], {"default": "deepseek-chat"}),
"temperature": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.1}),
"max_tokens": ("INT", {"default": 512, "min": 1, "max": 4096}),
"stop_sequences": ("STRING", {"default": ""}),
"stream_mode": (["enable", "disable"], {"default": "disable"}),
},
"optional": {
"system_prompt": ("STRING", {"default": "你是有帮助的AI助手", "multiline": True}),
"context": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("response",)
FUNCTION = "query_llm"
CATEGORY = "LLM"
OUTPUT_NODE = True
def _validate_inputs(self, **kwargs):
"""参数验证"""
if not kwargs.get("api_key"):
raise ValueError("API密钥不能为空")
if len(kwargs.get("input_str", "").strip()) < 2:
raise ValueError("输入内容过短(至少需要2个字符)")
if not 0 <= kwargs.get("temperature", 0.7) <= 1:
raise ValueError("温度参数需在0-1之间")
def _build_messages(self, **kwargs):
"""构造消息历史"""
messages = []
# 系统提示
if system_prompt := kwargs.get("system_prompt"):
messages.append({"role": "system", "content": system_prompt})
# 上下文历史
if context := kwargs.get("context"):
try:
history = json.loads(context)
if isinstance(history, list):
messages.extend(history)
except json.JSONDecodeError:
logger.warning("上下文解析失败,使用原始字符串")
messages.append({"role": "user", "content": context})
# 当前输入
messages.append({"role": "user", "content": kwargs["input_str"]})
return messages
def _handle_stream_response(self, stream: Stream[ChatCompletionChunk]) -> str:
"""处理流式响应"""
full_response = []
for chunk in stream:
if content := chunk.choices[0].delta.content:
full_response.append(content)
yield content # 实时输出
logger.debug(f"完整响应:{''.join(full_response)}")
return ''.join(full_response)
def query_llm(self, **kwargs):
try:
# 输入验证
self._validate_inputs(**kwargs)
# 初始化客户端
client = OpenAI(
api_key=kwargs["api_key"],
base_url="https://api.deepseek.com/v1",
)
# 构造请求参数
stop_sequences = [s.strip() for s in kwargs["stop_sequences"].split(",") if s.strip()]
# 创建API请求
response = client.chat.completions.create(
model=kwargs["model"],
messages=self._build_messages(**kwargs),
temperature=kwargs["temperature"],
max_tokens=kwargs["max_tokens"],
stop=stop_sequences if stop_sequences else None,
stream=kwargs["stream_mode"] == "enable",
)
# 处理响应
if isinstance(response, Stream):
# 流式处理
return (self._handle_stream_response(response),)
# 普通响应
content = response.choices[0].message.content
logger.debug(f"API响应:{content}")
return (content,)
except Exception as e:
logger.error(f"API请求失败:{str(e)}", exc_info=True)
return (f"错误:{str(e)}",)
@classmethod
def IS_CHANGED(cls, **kwargs):
"""通过哈希值检测输入变化"""
return hash(json.dumps(kwargs, sort_keys=True))
# 节点注册
NODE_CLASS_MAPPINGS = {
"DeepSeek_Online": ComfyUI_LLM_Online
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DeepSeek_Online": "🧠 DeepSeek 智能助手"
}