Publish current ComfyUI O1Key code baseline
Replace the prior release tree with the current plugin, frontend, tests, and documentation. Document retired node IDs and the public Gitea update source.
This commit is contained in:
+209
-94
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
全能LLM对话助手节点
|
||||
提示词专家节点
|
||||
ComfyUI 自定义节点,通过 OpenAI 兼容协议调用市面上主流的 AI 对话大模型
|
||||
支持多模态(图片输入),单轮对话,非流式输出
|
||||
|
||||
@@ -15,25 +15,44 @@ from typing import Optional, Tuple, List
|
||||
|
||||
import torch
|
||||
from PIL import Image
|
||||
from comfy_api.latest import io
|
||||
|
||||
from ..utils.image_utils import tensor_to_pil
|
||||
from ..utils.config import get_api_key_or_raise, get_api_base_url, NETWORK_ROUTE_OPTIONS, get_base_url_by_route
|
||||
from ..utils.config import get_api_key_or_raise, get_base_url_by_route
|
||||
from ..utils.file_types import FileList
|
||||
|
||||
# ============================================================================
|
||||
# 模型配置
|
||||
# ============================================================================
|
||||
|
||||
DEFAULT_MODEL = "gpt-6-sol"
|
||||
|
||||
SUPPORTED_MODELS = [
|
||||
DEFAULT_MODEL,
|
||||
"gpt-6-astra",
|
||||
"gpt-5.6-sol",
|
||||
"gpt-5.5",
|
||||
"gemini-3.1-pro-preview",
|
||||
"deepseek-v4-pro",
|
||||
"claude-opus-4-7",
|
||||
"claude-opus-4-6",
|
||||
"gemini-3.5-flash",
|
||||
"claude-opus-5",
|
||||
"doubao-seed-2.0-pro",
|
||||
]
|
||||
|
||||
MAX_IMAGE_INPUTS = 9
|
||||
|
||||
REASONING_DEPTH_OPTIONS = ["低", "中", "高"]
|
||||
REASONING_DEPTH_VALUE_MAP = {
|
||||
"低": "low",
|
||||
"中": "medium",
|
||||
"高": "high",
|
||||
"low": "low",
|
||||
"medium": "medium",
|
||||
"high": "high",
|
||||
}
|
||||
|
||||
# 节点内实时 token 预览开关。设为 True 即可恢复原打字机效果。
|
||||
ENABLE_NODE_TYPEWRITER_PREVIEW = False
|
||||
|
||||
# 图片缩放最大尺寸
|
||||
MAX_IMAGE_DIMENSION = 1568
|
||||
|
||||
@@ -41,9 +60,18 @@ MAX_IMAGE_DIMENSION = 1568
|
||||
MAX_IMAGE_SIZE = 20 * 1024 * 1024
|
||||
|
||||
|
||||
class UniversalLLMChat:
|
||||
def _collect_autogrow_inputs(value) -> list:
|
||||
"""收集已连接的 Autogrow 输入,并兼容单个旧值。"""
|
||||
if value is None:
|
||||
return []
|
||||
if isinstance(value, dict):
|
||||
return [item for item in value.values() if item is not None]
|
||||
return [value]
|
||||
|
||||
|
||||
class UniversalLLMChat(io.ComfyNode):
|
||||
"""
|
||||
全能LLM对话助手
|
||||
提示词专家
|
||||
|
||||
功能:
|
||||
- 通过 OpenAI 兼容协议调用主流大模型
|
||||
@@ -52,51 +80,64 @@ class UniversalLLMChat:
|
||||
- API 密钥和地址继承插件统一配置
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._api_key = None
|
||||
self._base_url = None
|
||||
|
||||
def _ensure_config(self):
|
||||
"""延迟加载配置,首次调用时初始化"""
|
||||
if self._api_key is None:
|
||||
self._api_key = get_api_key_or_raise("O1KEY_API_KEY")
|
||||
self._base_url = get_api_base_url()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"网络线路": (NETWORK_ROUTE_OPTIONS, {
|
||||
"default": "全球加速"
|
||||
}),
|
||||
"模型": (SUPPORTED_MODELS, {
|
||||
"default": SUPPORTED_MODELS[0]
|
||||
}),
|
||||
"提示词": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"图片": ("IMAGE",),
|
||||
"视频": ("VIDEO",),
|
||||
"文件": ("FILE_LIST",),
|
||||
"令牌": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": False,
|
||||
"placeholder": "留空则使用默认 API Key",
|
||||
}),
|
||||
},
|
||||
"hidden": {
|
||||
"node_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("回复",)
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "text/generation"
|
||||
OUTPUT_NODE = True
|
||||
def define_schema(cls):
|
||||
image_inputs = io.Autogrow.Input(
|
||||
"图片组",
|
||||
template=io.Autogrow.TemplateNames(
|
||||
input=io.Image.Input("图片"),
|
||||
names=[f"图片{i}" for i in range(1, MAX_IMAGE_INPUTS + 1)],
|
||||
min=0,
|
||||
),
|
||||
tooltip=f"连接后自动增加输入端口,合计最多 {MAX_IMAGE_INPUTS} 张图片。",
|
||||
)
|
||||
return io.Schema(
|
||||
node_id="UniversalLLMChat",
|
||||
display_name="提示词专家",
|
||||
category="text/generation",
|
||||
inputs=[
|
||||
io.Combo.Input(
|
||||
"模型",
|
||||
options=SUPPORTED_MODELS,
|
||||
default=DEFAULT_MODEL,
|
||||
),
|
||||
io.Combo.Input(
|
||||
"思考深度",
|
||||
options=REASONING_DEPTH_OPTIONS,
|
||||
default="高",
|
||||
),
|
||||
io.Int.Input(
|
||||
"seed",
|
||||
default=0,
|
||||
min=0,
|
||||
max=2**31 - 1,
|
||||
step=1,
|
||||
display_mode=io.NumberDisplay.number,
|
||||
control_after_generate=io.ControlAfterGenerate.randomize,
|
||||
),
|
||||
io.String.Input(
|
||||
"api(可选)",
|
||||
default="",
|
||||
multiline=False,
|
||||
placeholder="留空则使用默认 API Key",
|
||||
),
|
||||
io.String.Input(
|
||||
"提示词",
|
||||
default="",
|
||||
multiline=True,
|
||||
),
|
||||
io.Video.Input("视频", optional=True),
|
||||
io.Custom("FILE_LIST").Input("文件", optional=True),
|
||||
image_inputs,
|
||||
],
|
||||
outputs=[
|
||||
io.String.Output(display_name="回复"),
|
||||
],
|
||||
hidden=[io.Hidden.unique_id],
|
||||
is_output_node=True,
|
||||
# 接收旧版固定图片端口及旧令牌字段,避免旧工作流直接失效。
|
||||
accept_all_inputs=True,
|
||||
)
|
||||
|
||||
def _resize_image(self, img: Image.Image) -> Image.Image:
|
||||
"""如果图片过长边超过限制,等比缩放"""
|
||||
@@ -105,7 +146,7 @@ class UniversalLLMChat:
|
||||
if max_dim > MAX_IMAGE_DIMENSION:
|
||||
scale = MAX_IMAGE_DIMENSION / max_dim
|
||||
new_w, new_h = int(w * scale), int(h * scale)
|
||||
print(f"全能LLM: 图片缩放 {w}x{h} -> {new_w}x{new_h}")
|
||||
print(f"提示词专家: 图片缩放 {w}x{h} -> {new_w}x{new_h}")
|
||||
return img.resize((new_w, new_h), Image.Resampling.LANCZOS)
|
||||
return img
|
||||
|
||||
@@ -197,14 +238,14 @@ class UniversalLLMChat:
|
||||
},
|
||||
})
|
||||
|
||||
print(f"全能LLM: 加载文件 {filename} ({file_size / 1024:.1f}KB, {mime})")
|
||||
print(f"提示词专家: 加载文件 {filename} ({file_size / 1024:.1f}KB, {mime})")
|
||||
|
||||
return parts
|
||||
|
||||
def _build_input(
|
||||
self,
|
||||
prompt: str,
|
||||
images: Optional[torch.Tensor] = None,
|
||||
image_tensors: Optional[List[torch.Tensor]] = None,
|
||||
file_paths: str = "",
|
||||
file_list: Optional[FileList] = None,
|
||||
video=None,
|
||||
@@ -212,15 +253,17 @@ class UniversalLLMChat:
|
||||
"""构建 chat/completions 格式的 messages 数组"""
|
||||
image_data_urls = []
|
||||
pil_images_cache = [] # 保留 PIL Image 用于总体积重新编码
|
||||
|
||||
if images is not None:
|
||||
pil_images = tensor_to_pil(images)
|
||||
for img in pil_images:
|
||||
img_resized = self._resize_image(img)
|
||||
if img_resized.mode in ('RGBA', 'P'):
|
||||
img_resized = img_resized.convert('RGB')
|
||||
pil_images_cache.append(img_resized)
|
||||
image_data_urls.append(self._image_to_data_url(img_resized))
|
||||
|
||||
if image_tensors:
|
||||
for tensor in image_tensors:
|
||||
if tensor is None:
|
||||
continue
|
||||
for img in tensor_to_pil(tensor):
|
||||
img_resized = self._resize_image(img)
|
||||
if img_resized.mode in ('RGBA', 'P'):
|
||||
img_resized = img_resized.convert('RGB')
|
||||
pil_images_cache.append(img_resized)
|
||||
image_data_urls.append(self._image_to_data_url(img_resized))
|
||||
|
||||
# 多图总体积控制
|
||||
if pil_images_cache and len(pil_images_cache) > 1:
|
||||
@@ -228,7 +271,7 @@ class UniversalLLMChat:
|
||||
len(base64.b64decode(url.split(',', 1)[1])) for url in image_data_urls
|
||||
)
|
||||
if total_bytes > MAX_IMAGE_SIZE:
|
||||
print(f"全能LLM: 图片总体积 {total_bytes / 1024 / 1024:.2f}MB 超过 {MAX_IMAGE_SIZE // 1024 // 1024}MB 限制,正在压缩...")
|
||||
print(f"提示词专家: 图片总体积 {total_bytes / 1024 / 1024:.2f}MB 超过 {MAX_IMAGE_SIZE // 1024 // 1024}MB 限制,正在压缩...")
|
||||
|
||||
# 降质量
|
||||
compressed = False
|
||||
@@ -242,7 +285,7 @@ class UniversalLLMChat:
|
||||
total_bytes = sum(len(base64.b64decode(u.split(',', 1)[1])) for u in new_urls)
|
||||
if total_bytes <= MAX_IMAGE_SIZE:
|
||||
image_data_urls = new_urls
|
||||
print(f"全能LLM: 图片压缩完成,总体积 {total_bytes / 1024 / 1024:.2f}MB ({len(pil_images_cache)}张图片,质量{quality})")
|
||||
print(f"提示词专家: 图片压缩完成,总体积 {total_bytes / 1024 / 1024:.2f}MB ({len(pil_images_cache)}张图片,质量{quality})")
|
||||
compressed = True
|
||||
break
|
||||
|
||||
@@ -260,12 +303,12 @@ class UniversalLLMChat:
|
||||
total_bytes = sum(len(base64.b64decode(u.split(',', 1)[1])) for u in new_urls)
|
||||
if total_bytes <= MAX_IMAGE_SIZE:
|
||||
image_data_urls = new_urls
|
||||
print(f"全能LLM: 图片压缩完成,总体积 {total_bytes / 1024 / 1024:.2f}MB ({len(pil_images_cache)}张图片,缩放{int(scale*100)}%)")
|
||||
print(f"提示词专家: 图片压缩完成,总体积 {total_bytes / 1024 / 1024:.2f}MB ({len(pil_images_cache)}张图片,缩放{int(scale*100)}%)")
|
||||
compressed = True
|
||||
break
|
||||
|
||||
if not compressed:
|
||||
print(f"全能LLM: 无法将 {len(pil_images_cache)} 张图片压缩到 {MAX_IMAGE_SIZE // 1024 // 1024}MB 以内,请减少图片数量或降低分辨率")
|
||||
print(f"提示词专家: 无法将 {len(pil_images_cache)} 张图片压缩到 {MAX_IMAGE_SIZE // 1024 // 1024}MB 以内,请减少图片数量或降低分辨率")
|
||||
raise ValueError(f"图片总体积 {total_bytes / 1024 / 1024:.2f}MB 超过限制,无法压缩到 {MAX_IMAGE_SIZE // 1024 // 1024}MB 以内")
|
||||
|
||||
# 处理视频输入(ComfyUI VIDEO 类型)
|
||||
@@ -305,7 +348,7 @@ class UniversalLLMChat:
|
||||
ext = os.path.splitext(vp)[1].lower()
|
||||
mime = mime_map.get(ext, "video/mp4")
|
||||
file_size = os.path.getsize(vp)
|
||||
print(f"全能LLM: 加载视频 {os.path.basename(vp)} ({file_size / 1024 / 1024:.1f}MB, {mime})")
|
||||
print(f"提示词专家: 加载视频 {os.path.basename(vp)} ({file_size / 1024 / 1024:.1f}MB, {mime})")
|
||||
with open(vp, "rb") as f:
|
||||
b64 = base64.b64encode(f.read()).decode("utf-8")
|
||||
video_url_str = f"data:{mime};base64,{b64}"
|
||||
@@ -314,7 +357,7 @@ class UniversalLLMChat:
|
||||
file_parts = []
|
||||
if file_list:
|
||||
for fd in file_list:
|
||||
print(f"全能LLM: 使用文件 {fd.filename}{fd.extension} ({fd.size / 1024:.1f}KB)")
|
||||
print(f"提示词专家: 使用文件 {fd.filename}{fd.extension} ({fd.size / 1024:.1f}KB)")
|
||||
file_parts.append({
|
||||
"type": "file",
|
||||
"file": {
|
||||
@@ -369,44 +412,89 @@ class UniversalLLMChat:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
模型: str,
|
||||
思考深度: str = "高",
|
||||
seed: int = 0,
|
||||
提示词: str = "",
|
||||
视频=None,
|
||||
文件: Optional[FileList] = None,
|
||||
**kwargs,
|
||||
) -> io.NodeOutput:
|
||||
worker = cls()
|
||||
result = worker.generate(
|
||||
模型=模型,
|
||||
思考深度=思考深度,
|
||||
seed=seed,
|
||||
提示词=提示词,
|
||||
视频=视频,
|
||||
文件=文件,
|
||||
node_id=str(cls.hidden.unique_id or ""),
|
||||
**kwargs,
|
||||
)
|
||||
return io.NodeOutput(*result)
|
||||
|
||||
def generate(
|
||||
self,
|
||||
模型: str,
|
||||
提示词: str,
|
||||
网络线路: str = "全球加速",
|
||||
图片: Optional[torch.Tensor] = None,
|
||||
思考深度: str = "高",
|
||||
seed: int = 0,
|
||||
提示词: str = "",
|
||||
视频=None,
|
||||
文件: Optional[FileList] = None,
|
||||
令牌: str = "",
|
||||
node_id: str = "",
|
||||
**kwargs,
|
||||
) -> Tuple[str]:
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
self._ensure_config()
|
||||
self._base_url = get_base_url_by_route(网络线路)
|
||||
# 用户填写 api 时覆盖默认 API Key;保留旧字段名兼容旧工作流。
|
||||
api_value = kwargs.get(
|
||||
"api(可选)",
|
||||
kwargs.get("分组令牌(可留空)", kwargs.get("令牌", "")),
|
||||
)
|
||||
effective_api_key = str(api_value).strip() if api_value else ""
|
||||
if not effective_api_key:
|
||||
effective_api_key = get_api_key_or_raise("O1KEY_API_KEY")
|
||||
base_url = get_base_url_by_route()
|
||||
reasoning_effort = REASONING_DEPTH_VALUE_MAP.get(思考深度, "medium")
|
||||
|
||||
# 如果用户传入了自定义令牌,则覆盖默认 API Key
|
||||
effective_api_key = 令牌.strip() if 令牌 and 令牌.strip() else self._api_key
|
||||
image_tensors = _collect_autogrow_inputs(kwargs.get("图片组"))
|
||||
if not image_tensors:
|
||||
# 兼容 Autogrow 改造前的「图片」及「图片1~图片9」端口。
|
||||
旧图片 = kwargs.get("图片")
|
||||
if 旧图片 is not None:
|
||||
image_tensors.append(旧图片)
|
||||
image_tensors.extend(
|
||||
kwargs[f"图片{i}"]
|
||||
for i in range(1, MAX_IMAGE_INPUTS + 1)
|
||||
if kwargs.get(f"图片{i}") is not None
|
||||
)
|
||||
|
||||
# 构建 input
|
||||
input_data = self._build_input(提示词, 图片, "", 文件, 视频)
|
||||
input_data = self._build_input(提示词, image_tensors, "", 文件, 视频)
|
||||
|
||||
img_count = len(tensor_to_pil(图片)) if 图片 is not None else 0
|
||||
img_count = sum(len(tensor_to_pil(t)) for t in image_tensors)
|
||||
file_count = len(文件) if 文件 else 0
|
||||
input_desc = "文本"
|
||||
if img_count: input_desc += f" + {img_count}张图片"
|
||||
if 视频 is not None: input_desc += " + 视频"
|
||||
if file_count: input_desc += f" + {file_count}个文件"
|
||||
|
||||
print(f"全能LLM: 模型 = {模型}")
|
||||
print(f"全能LLM: 输入 = {input_desc}")
|
||||
print(f"提示词专家: 模型 = {模型}")
|
||||
print(f"提示词专家: 思考深度 = {思考深度} ({reasoning_effort})")
|
||||
print(f"提示词专家: seed = {seed}")
|
||||
print(f"提示词专家: 输入 = {input_desc}")
|
||||
|
||||
# 构建请求体(chat/completions 格式)
|
||||
request_body = {
|
||||
"model": 模型,
|
||||
"messages": input_data,
|
||||
"stream": True,
|
||||
"reasoning_effort": reasoning_effort,
|
||||
"seed": seed,
|
||||
}
|
||||
|
||||
# 打印请求体,base64 截断显示
|
||||
@@ -415,10 +503,10 @@ class UniversalLLMChat:
|
||||
return {k: _truncate_for_log(v) for k, v in obj.items()}
|
||||
if isinstance(obj, list):
|
||||
return [_truncate_for_log(i) for i in obj]
|
||||
if isinstance(obj, str) and (obj.startswith("data:image") or obj.startswith("data:application") or obj.startswith("data:text")):
|
||||
if isinstance(obj, str) and obj.startswith("data:"):
|
||||
return obj[:60] + f"...[{len(obj)}chars]"
|
||||
return obj
|
||||
print(f"全能LLM: 请求原始内容 = {json.dumps(_truncate_for_log(request_body), ensure_ascii=False)}")
|
||||
print(f"提示词专家: 请求原始内容 = {json.dumps(_truncate_for_log(request_body), ensure_ascii=False)}")
|
||||
|
||||
# 发送请求(在独立线程中运行异步请求,避免与 ComfyUI 事件循环冲突)
|
||||
import aiohttp
|
||||
@@ -430,8 +518,10 @@ class UniversalLLMChat:
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {effective_api_key}",
|
||||
}
|
||||
url = f"{self._base_url}/v1/chat/completions"
|
||||
timeout = aiohttp.ClientTimeout(total=120)
|
||||
url = f"{base_url}/v1/chat/completions"
|
||||
# 流式接口不设整体 total 上限(否则会掐断高思考深度的长生成),
|
||||
# 改用连接超时 + 单次读取超时:只要在 sock_read 间隔内有数据返回就不超时。
|
||||
timeout = aiohttp.ClientTimeout(total=None, sock_connect=30, sock_read=300)
|
||||
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.post(url, headers=headers, json=request_body) as resp:
|
||||
@@ -439,6 +529,7 @@ class UniversalLLMChat:
|
||||
|
||||
if status != 200:
|
||||
body = await resp.text()
|
||||
print(f"提示词专家: 响应体 = {body}")
|
||||
try:
|
||||
err_data = json.loads(body)
|
||||
err_msg = err_data.get("error", {}).get("message", body[:200])
|
||||
@@ -458,17 +549,30 @@ class UniversalLLMChat:
|
||||
|
||||
# 流式读取,拼接 delta content
|
||||
reply_parts = []
|
||||
response_body_parts = []
|
||||
async for raw_line in resp.content:
|
||||
line = raw_line.decode("utf-8").strip()
|
||||
if not line or not line.startswith("data:"):
|
||||
continue
|
||||
data_str = line[len("data:"):].strip()
|
||||
response_body_parts.append(f"data: {data_str}")
|
||||
if data_str == "[DONE]":
|
||||
break
|
||||
try:
|
||||
chunk = json.loads(data_str)
|
||||
except Exception:
|
||||
continue
|
||||
stream_error = chunk.get("error")
|
||||
if stream_error:
|
||||
response_body = "\n".join(response_body_parts)
|
||||
print(f"提示词专家: 响应体 = {response_body}")
|
||||
if isinstance(stream_error, dict):
|
||||
error_message = stream_error.get("message", "上游服务暂时不可用")
|
||||
error_type = stream_error.get("type", "upstream_error")
|
||||
else:
|
||||
error_message = str(stream_error)
|
||||
error_type = "upstream_error"
|
||||
raise RuntimeError(f"上游服务错误 ({error_type}): {error_message}")
|
||||
choices = chunk.get("choices")
|
||||
if not choices:
|
||||
continue
|
||||
@@ -476,10 +580,17 @@ class UniversalLLMChat:
|
||||
content = delta.get("content")
|
||||
if content:
|
||||
reply_parts.append(content)
|
||||
UniversalLLMChat._send_stream_token(node_id, content)
|
||||
if ENABLE_NODE_TYPEWRITER_PREVIEW:
|
||||
UniversalLLMChat._send_stream_token(node_id, content)
|
||||
|
||||
UniversalLLMChat._send_stream_token(node_id, "", done=True)
|
||||
return "".join(reply_parts)
|
||||
response_body = "\n".join(response_body_parts)
|
||||
print(f"提示词专家: 响应体 = {response_body}")
|
||||
if ENABLE_NODE_TYPEWRITER_PREVIEW:
|
||||
UniversalLLMChat._send_stream_token(node_id, "", done=True)
|
||||
reply = "".join(reply_parts)
|
||||
if not reply:
|
||||
raise RuntimeError("模型未返回有效文本内容")
|
||||
return reply
|
||||
|
||||
def _run_in_thread():
|
||||
loop = asyncio.new_event_loop()
|
||||
@@ -492,22 +603,26 @@ class UniversalLLMChat:
|
||||
reply = pool.submit(_run_in_thread).result()
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
print(f"全能LLM: 生成完成 (耗时: {elapsed:.2f}s)")
|
||||
print(f"提示词专家: 生成完成 (耗时: {elapsed:.2f}s)")
|
||||
if reply:
|
||||
preview = reply[:100] + "..." if len(reply) > 100 else reply
|
||||
print(f"全能LLM: 回复预览: {preview}")
|
||||
print(f"提示词专家: 回复预览: {preview}")
|
||||
|
||||
return (reply,)
|
||||
|
||||
except ValueError as e:
|
||||
if str(e) == "未授权!":
|
||||
print("全能LLM: 请联系作者授权后方可使用!")
|
||||
print("提示词专家: 请联系作者授权后方可使用!")
|
||||
raise ValueError("未授权!") from None
|
||||
error_msg = str(e).split('\n')[0]
|
||||
print(f"全能LLM: ❌ {error_msg}")
|
||||
print(f"提示词专家: ❌ {error_msg}")
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e).split('\n')[0]
|
||||
print(f"全能LLM: ❌ {error_msg}")
|
||||
import asyncio as _asyncio
|
||||
if isinstance(e, _asyncio.TimeoutError):
|
||||
error_msg = "请求超时:服务端长时间未返回数据(可能是模型思考过久或网络不稳定),请重试或降低思考深度/图片数量"
|
||||
else:
|
||||
error_msg = str(e).split('\n')[0] or f"未知错误({type(e).__name__})"
|
||||
print(f"提示词专家: ❌ {error_msg}")
|
||||
raise RuntimeError(error_msg) from None
|
||||
|
||||
Reference in New Issue
Block a user