V2.0.0
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
节点基类模块
|
||||
提供所有节点的通用基础能力
|
||||
"""
|
||||
|
||||
from .base_node import BaseNode
|
||||
from .llm_node_base import LLMNodeBase
|
||||
from .vlm_node_base import VLMNodeBase
|
||||
|
||||
__all__ = [
|
||||
'BaseNode', 'LLMNodeBase', 'VLMNodeBase'
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
"""
|
||||
节点抽象基类
|
||||
提供所有节点的通用基础能力,包括线程管理、中断检测、Provider配置等
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Callable, Dict, Optional, Tuple
|
||||
|
||||
from comfy.model_management import InterruptProcessingException
|
||||
|
||||
from ...utils.common import PREFIX as LOG_PREFIX, REQUEST_PREFIX, PROCESS_PREFIX
|
||||
|
||||
|
||||
class BaseNode:
|
||||
"""
|
||||
所有节点的抽象基类
|
||||
|
||||
提供通用功能:
|
||||
- 线程执行与中断管理
|
||||
- Provider配置覆盖
|
||||
- 规则模板获取
|
||||
"""
|
||||
|
||||
# 子类应该覆盖这些常量
|
||||
LOG_PREFIX = LOG_PREFIX
|
||||
REQUEST_PREFIX = REQUEST_PREFIX
|
||||
PROCESS_PREFIX = PROCESS_PREFIX
|
||||
|
||||
|
||||
def _run_thread_with_interrupt(
|
||||
self,
|
||||
target_func: Callable,
|
||||
args: Tuple,
|
||||
task_name: str = "任务"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
在独立线程中运行任务,并支持中断检测
|
||||
|
||||
参数:
|
||||
target_func: 要在线程中执行的函数
|
||||
args: 传递给函数的参数元组
|
||||
task_name: 任务名称,用于日志输出(已废弃,保留参数以兼容旧代码)
|
||||
|
||||
返回:
|
||||
result_container中的'result'字段内容
|
||||
|
||||
异常:
|
||||
InterruptProcessingException: 当检测到用户中断时
|
||||
"""
|
||||
result_container = {}
|
||||
|
||||
# 启动线程
|
||||
thread = threading.Thread(target=target_func, args=args)
|
||||
thread.start()
|
||||
|
||||
# 等待完成,同时检查中断
|
||||
while thread.is_alive():
|
||||
try:
|
||||
import nodes
|
||||
nodes.before_node_execution()
|
||||
except:
|
||||
# 检测到中断,立即抛出异常
|
||||
raise InterruptProcessingException()
|
||||
time.sleep(0.1)
|
||||
|
||||
return result_container.get('result')
|
||||
|
||||
def _run_async_in_thread(
|
||||
self,
|
||||
async_func: Callable,
|
||||
result_container: Dict[str, Any],
|
||||
*args,
|
||||
**kwargs
|
||||
) -> None:
|
||||
"""
|
||||
在独立线程中运行异步任务的辅助方法
|
||||
|
||||
这个方法会:
|
||||
1. 创建新的事件循环
|
||||
2. 运行异步函数
|
||||
3. 将结果存入result_container
|
||||
4. 正确处理中断异常
|
||||
|
||||
参数:
|
||||
async_func: 异步函数
|
||||
result_container: 结果容器字典
|
||||
*args, **kwargs: 传递给async_func的参数
|
||||
|
||||
结果:
|
||||
result_container['result'] = 函数返回值或错误信息
|
||||
"""
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
result = loop.run_until_complete(async_func(*args, **kwargs))
|
||||
result_container['result'] = result
|
||||
except (asyncio.CancelledError, KeyboardInterrupt):
|
||||
# 捕获中断异常,不格式化错误
|
||||
print(f"{self.LOG_PREFIX} 检测到异步任务被取消")
|
||||
result_container['result'] = {"success": False, "error": "任务被中断"}
|
||||
except Exception as e:
|
||||
# 捕获其他异常,让子类处理格式化
|
||||
result_container['result'] = {"success": False, "error": str(e)}
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
def _execute_with_interrupt(
|
||||
self,
|
||||
thread_func: Callable,
|
||||
thread_args: Tuple,
|
||||
cancel_event: Optional[Any] = None
|
||||
) -> Any:
|
||||
"""
|
||||
执行带中断检测的线程任务(通用封装)
|
||||
|
||||
参数:
|
||||
thread_func: 线程中执行的函数(通常是包含异步逻辑的函数)
|
||||
thread_args: 传递给thread_func的参数
|
||||
cancel_event: 可选的取消事件,用于通知异步任务中断
|
||||
|
||||
返回:
|
||||
result_container['result']的内容
|
||||
|
||||
异常:
|
||||
InterruptProcessingException: 当检测到用户中断时
|
||||
"""
|
||||
result_container = {}
|
||||
|
||||
# 如果提供了cancel_event,将其传递给thread_func
|
||||
if cancel_event is not None:
|
||||
thread = threading.Thread(target=thread_func, args=(result_container, cancel_event) + thread_args)
|
||||
else:
|
||||
thread = threading.Thread(target=thread_func, args=(result_container,) + thread_args)
|
||||
thread.start()
|
||||
|
||||
# 等待完成,同时检查中断
|
||||
while thread.is_alive():
|
||||
is_interrupted = False
|
||||
try:
|
||||
import nodes
|
||||
nodes.before_node_execution()
|
||||
|
||||
# 双重检查:额外检查 PromptServer 的全局中断状态
|
||||
# 某些情况下 nodes.before_node_execution() 可能不会抛出异常
|
||||
from server import PromptServer
|
||||
if hasattr(PromptServer.instance, 'execution_interrupted') and PromptServer.instance.execution_interrupted:
|
||||
is_interrupted = True
|
||||
except:
|
||||
is_interrupted = True
|
||||
|
||||
if is_interrupted:
|
||||
# 检测到中断,设置取消事件(如果提供)
|
||||
if cancel_event is not None:
|
||||
try:
|
||||
cancel_event.set()
|
||||
except:
|
||||
pass
|
||||
raise InterruptProcessingException()
|
||||
time.sleep(0.1)
|
||||
|
||||
return result_container.get('result')
|
||||
|
||||
def _override_ollama_config(
|
||||
self,
|
||||
provider_config: Dict[str, Any],
|
||||
auto_unload: bool
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
覆盖Ollama配置中的auto_unload参数
|
||||
|
||||
参数:
|
||||
provider_config: 原始Provider配置
|
||||
auto_unload: 节点级别的自动释放设置
|
||||
|
||||
返回:
|
||||
新的配置字典(不修改原始配置)
|
||||
"""
|
||||
config_copy = provider_config.copy()
|
||||
config_copy['auto_unload'] = auto_unload
|
||||
return config_copy
|
||||
|
||||
def _get_prompt_template(
|
||||
self,
|
||||
template_name: str,
|
||||
prompt_type: str,
|
||||
use_temp_rule: bool,
|
||||
temp_rule_content: str,
|
||||
default_content: str
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
获取提示词模板内容
|
||||
|
||||
参数:
|
||||
template_name: 模板名称
|
||||
prompt_type: 模板类型('expand_prompts', 'vision_prompts'等)
|
||||
use_temp_rule: 是否使用临时规则
|
||||
temp_rule_content: 临时规则内容
|
||||
default_content: 默认提示词内容
|
||||
|
||||
返回:
|
||||
(prompt_content, rule_name) 元组
|
||||
"""
|
||||
# 使用临时规则
|
||||
if use_temp_rule and temp_rule_content:
|
||||
return temp_rule_content, "临时规则"
|
||||
|
||||
# 从配置获取模板
|
||||
from ...config_manager import config_manager
|
||||
system_prompts = config_manager.get_system_prompts()
|
||||
|
||||
if not system_prompts:
|
||||
return default_content, "默认规则"
|
||||
|
||||
prompts = system_prompts.get(prompt_type, {})
|
||||
if not prompts:
|
||||
return default_content, "默认规则"
|
||||
|
||||
# 按显示名称匹配
|
||||
for key, value in prompts.items():
|
||||
if value.get('name') == template_name:
|
||||
content = value.get('content', '')
|
||||
if content:
|
||||
return content, template_name
|
||||
|
||||
# 按键名匹配
|
||||
for key, value in prompts.items():
|
||||
if key == template_name:
|
||||
content = value.get('content', '')
|
||||
if content:
|
||||
return content, template_name
|
||||
|
||||
# 未找到,使用默认
|
||||
return default_content, "默认规则"
|
||||
@@ -0,0 +1,221 @@
|
||||
"""
|
||||
LLM节点基类
|
||||
为LLM类节点(扩写、翻译等)提供专用基础能力
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
import asyncio
|
||||
|
||||
from .base_node import BaseNode
|
||||
from ...utils.common import format_api_error
|
||||
|
||||
|
||||
class LLMNodeBase(BaseNode):
|
||||
"""
|
||||
LLM节点基类
|
||||
|
||||
提供LLM节点专用功能:
|
||||
- LLM Provider配置获取
|
||||
- LLM相关的通用逻辑
|
||||
- 动态服务/模型列表生成
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_llm_service_options():
|
||||
"""
|
||||
获取所有可用的LLM服务/模型选项列表
|
||||
|
||||
返回格式: ["百度翻译", "智谱/glm-4-flash", "Ollama/qwen3:14b", ...]
|
||||
百度翻译服务无模型概念,仅显示服务名
|
||||
其他服务显示为"服务名/模型名"格式
|
||||
|
||||
返回:
|
||||
List[str]: 服务/模型选项列表
|
||||
"""
|
||||
from ...config_manager import config_manager
|
||||
|
||||
options = []
|
||||
services = config_manager.get_all_services()
|
||||
|
||||
for service in services:
|
||||
service_name = service.get('name', '')
|
||||
service_id = service.get('id', '')
|
||||
service_type = service.get('type', '')
|
||||
|
||||
# 百度翻译特殊处理:仅显示服务名
|
||||
if service_type == 'baidu':
|
||||
options.append(service_name)
|
||||
continue
|
||||
|
||||
# 其他服务:遍历llm_models
|
||||
llm_models = service.get('llm_models', [])
|
||||
for model in llm_models:
|
||||
model_name = model.get('name', '')
|
||||
if model_name:
|
||||
# 格式: "服务名/模型名"
|
||||
options.append(f"{service_name}/{model_name}")
|
||||
|
||||
# 如果没有任何选项,返回默认值避免ComfyUI报错
|
||||
if not options:
|
||||
options = ["智谱"]
|
||||
|
||||
return options
|
||||
|
||||
@staticmethod
|
||||
def get_translate_service_options():
|
||||
"""
|
||||
获取翻译服务专用的服务/模型选项列表
|
||||
|
||||
与get_llm_service_options的区别:
|
||||
- 硬编码添加"百度翻译"选项(百度翻译使用独立配置,不在model_services中)
|
||||
- 专门用于翻译节点和翻译按钮
|
||||
|
||||
返回格式: ["百度翻译", "智谱/glm-4-flash", "Ollama/qwen3:14b", ...]
|
||||
|
||||
返回:
|
||||
List[str]: 服务/模型选项列表(包含百度翻译)
|
||||
"""
|
||||
from ...config_manager import config_manager
|
||||
|
||||
options = []
|
||||
|
||||
# ---硬编码添加百度翻译---
|
||||
# 百度翻译使用独立的baidu_translate配置,不在model_services列表中
|
||||
baidu_config = config_manager.load_config().get('baidu_translate', {})
|
||||
# 即使没有配置app_id,也要显示百度选项
|
||||
options.append("百度翻译")
|
||||
|
||||
# ---动态获取其他LLM服务---
|
||||
services = config_manager.get_all_services()
|
||||
|
||||
for service in services:
|
||||
service_name = service.get('name', '')
|
||||
service_type = service.get('type', '')
|
||||
|
||||
# 遍历llm_models
|
||||
llm_models = service.get('llm_models', [])
|
||||
for model in llm_models:
|
||||
model_name = model.get('name', '')
|
||||
if model_name:
|
||||
# 格式: "服务名/模型名"
|
||||
options.append(f"{service_name}/{model_name}")
|
||||
|
||||
return options
|
||||
|
||||
@staticmethod
|
||||
def parse_service_model(service_model_str: str):
|
||||
"""
|
||||
解析"服务名/模型名"格式的字符串
|
||||
|
||||
特殊处理:
|
||||
- "百度翻译": 返回 ('baidu', None) - 百度翻译使用独立配置
|
||||
|
||||
参数:
|
||||
service_model_str: 服务/模型字符串,例如 "智谱/glm-4-flash" 或 "百度翻译"
|
||||
|
||||
返回:
|
||||
Tuple[str, Optional[str]]: (service_id, model_name)
|
||||
- service_id: 服务ID (如 'zhipu', 'baidu')
|
||||
- model_name: 模型名称,如果没有则为None
|
||||
"""
|
||||
from ...config_manager import config_manager
|
||||
|
||||
# 分割字符串
|
||||
if '/' in service_model_str:
|
||||
service_name, model_name = service_model_str.split('/', 1)
|
||||
else:
|
||||
service_name = service_model_str
|
||||
model_name = None
|
||||
|
||||
# ---特殊处理:百度翻译---
|
||||
if service_name in ['百度翻译', '百度', 'baidu']:
|
||||
return 'baidu', None
|
||||
|
||||
# 查找对应的service_id
|
||||
services = config_manager.get_all_services()
|
||||
for service in services:
|
||||
if service.get('name') == service_name:
|
||||
return service.get('id'), model_name
|
||||
|
||||
# 未找到,返回None
|
||||
return None, None
|
||||
|
||||
def _get_provider_config(
|
||||
self,
|
||||
config_manager: Any,
|
||||
provider: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
获取指定LLM Provider的配置
|
||||
|
||||
参数:
|
||||
config_manager: 配置管理器实例
|
||||
provider: Provider标识符(如 'zhipu', 'ollama'等)
|
||||
|
||||
返回:
|
||||
Provider配置字典,如果未找到则返回 None
|
||||
"""
|
||||
llm_config = config_manager.get_llm_config()
|
||||
|
||||
if 'providers' in llm_config and provider in llm_config['providers']:
|
||||
return llm_config['providers'][provider]
|
||||
|
||||
return None
|
||||
|
||||
def _run_llm_task(
|
||||
self,
|
||||
llm_service_func: Callable,
|
||||
provider: str,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
执行LLM任务(扩写/翻译)的统一方法
|
||||
|
||||
封装了:
|
||||
1. 异步任务的线程执行
|
||||
2. 中断异常的捕获和处理
|
||||
3. API异常的格式化
|
||||
|
||||
参数:
|
||||
llm_service_func: LLMService的异步方法
|
||||
provider: Provider名称(用于错误格式化)
|
||||
*args, **kwargs: 传递给llm_service_func的参数
|
||||
|
||||
返回:
|
||||
{"success": bool, "data": dict, "error": str} 格式的结果
|
||||
"""
|
||||
def thread_task(result_container, cancel_event):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
# 将cancel_event传递给服务函数
|
||||
kwargs['cancel_event'] = cancel_event
|
||||
result = loop.run_until_complete(llm_service_func(*args, **kwargs))
|
||||
result_container['result'] = result
|
||||
except (asyncio.CancelledError, KeyboardInterrupt):
|
||||
print(f"{self.LOG_PREFIX} 异步任务被取消")
|
||||
result_container['result'] = {"success": False, "error": "任务被中断"}
|
||||
except Exception as e:
|
||||
# 捕获其他异常,格式化错误信息
|
||||
error_message = format_api_error(e, provider)
|
||||
result_container['result'] = {"success": False, "error": error_message}
|
||||
finally:
|
||||
# 清理所有未完成的任务,消除 "Task was destroyed but it is pending" 警告
|
||||
try:
|
||||
pending = asyncio.all_tasks(loop)
|
||||
if pending:
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
# 等待任务取消完成,忽略取消错误
|
||||
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
# 创建取消事件
|
||||
import threading
|
||||
cancel_event = threading.Event()
|
||||
|
||||
return self._execute_with_interrupt(thread_task, (), cancel_event)
|
||||
@@ -0,0 +1,279 @@
|
||||
"""
|
||||
VLM节点基类
|
||||
为VLM类节点(图像反推、视频反推等)提供专用基础能力
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import threading
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
import torch
|
||||
from comfy.model_management import InterruptProcessingException
|
||||
|
||||
from .base_node import BaseNode
|
||||
from ...utils.image import tensor_to_base64, compute_image_hash
|
||||
from ...utils.common import format_api_error
|
||||
|
||||
|
||||
class VLMNodeBase(BaseNode):
|
||||
"""
|
||||
VLM节点基类
|
||||
|
||||
提供VLM节点专用功能:
|
||||
- VLM Provider配置获取
|
||||
- 图像处理工具方法
|
||||
- 图像哈希计算
|
||||
- 统一的异步任务执行与中断处理
|
||||
- 动态服务/模型列表生成
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_vlm_service_options():
|
||||
"""
|
||||
获取所有可用的VLM服务/模型选项列表
|
||||
|
||||
返回格式: ["智谱/glm-4v-flash", "Ollama/llava:Q6_K", ...]
|
||||
所有服务显示为"服务名/模型名"格式
|
||||
|
||||
返回:
|
||||
List[str]: 服务/模型选项列表
|
||||
"""
|
||||
from ...config_manager import config_manager
|
||||
|
||||
options = []
|
||||
services = config_manager.get_all_services()
|
||||
|
||||
for service in services:
|
||||
service_name = service.get('name', '')
|
||||
service_id = service.get('id', '')
|
||||
service_type = service.get('type', '')
|
||||
|
||||
# 百度翻译没有vlm_models,跳过
|
||||
if service_type == 'baidu':
|
||||
continue
|
||||
|
||||
# 遍历vlm_models
|
||||
vlm_models = service.get('vlm_models', [])
|
||||
for model in vlm_models:
|
||||
model_name = model.get('name', '')
|
||||
if model_name:
|
||||
# 格式: "服务名/模型名"
|
||||
options.append(f"{service_name}/{model_name}")
|
||||
|
||||
# 如果没有任何选项,返回默认值避免ComfyUI报错
|
||||
if not options:
|
||||
options = ["智谱"]
|
||||
|
||||
return options
|
||||
|
||||
@staticmethod
|
||||
def parse_service_model(service_model_str: str):
|
||||
"""
|
||||
解析"服务名/模型名"格式的字符串
|
||||
|
||||
参数:
|
||||
service_model_str: 服务/模型字符串,例如 "智谱/glm-4v-flash"
|
||||
|
||||
返回:
|
||||
Tuple[str, Optional[str]]: (service_id, model_name)
|
||||
- service_id: 服务ID (如 'zhipu', 'ollama')
|
||||
- model_name: 模型名称,如果没有则为None
|
||||
"""
|
||||
from ...config_manager import config_manager
|
||||
|
||||
# 分割字符串
|
||||
if '/' in service_model_str:
|
||||
service_name, model_name = service_model_str.split('/', 1)
|
||||
else:
|
||||
service_name = service_model_str
|
||||
model_name = None
|
||||
|
||||
# 查找对应的service_id
|
||||
services = config_manager.get_all_services()
|
||||
for service in services:
|
||||
if service.get('name') == service_name:
|
||||
return service.get('id'), model_name
|
||||
|
||||
# 未找到,返回None
|
||||
return None, None
|
||||
|
||||
def _get_provider_config(
|
||||
self,
|
||||
config_manager: Any,
|
||||
provider: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
获取指定VLM Provider的配置
|
||||
|
||||
参数:
|
||||
config_manager: 配置管理器实例
|
||||
provider: Provider标识符(如 'zhipu', 'ollama'等)
|
||||
|
||||
返回:
|
||||
Provider配置字典,如果未找到则返回 None
|
||||
"""
|
||||
vision_config = config_manager.get_vision_config()
|
||||
|
||||
if 'providers' in vision_config and provider in vision_config['providers']:
|
||||
return vision_config['providers'][provider]
|
||||
|
||||
return None
|
||||
|
||||
def _image_to_base64(self, image_tensor: torch.Tensor, quality: int = 95) -> str:
|
||||
"""
|
||||
将图像tensor转换为base64编码
|
||||
|
||||
参数:
|
||||
image_tensor: 图像tensor
|
||||
quality: JPEG压缩质量(1-100)
|
||||
|
||||
返回:
|
||||
base64编码的data URL
|
||||
"""
|
||||
return tensor_to_base64(image_tensor, quality)
|
||||
|
||||
def _compute_image_hash(self, image_tensor: Optional[torch.Tensor]) -> str:
|
||||
"""
|
||||
计算图像tensor的哈希值(用于IS_CHANGED)
|
||||
|
||||
参数:
|
||||
image_tensor: 图像tensor或None
|
||||
|
||||
返回:
|
||||
MD5哈希值的十六进制字符串
|
||||
"""
|
||||
return compute_image_hash(image_tensor)
|
||||
|
||||
def _run_async_task(
|
||||
self,
|
||||
async_func: Callable,
|
||||
provider: str,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
在独立线程中运行异步任务,并处理中断
|
||||
|
||||
这个方法封装了:
|
||||
1. 异步事件循环的创建和清理
|
||||
2. 中断异常的捕获和处理
|
||||
3. 普通异常的格式化
|
||||
|
||||
参数:
|
||||
async_func: 异步函数(coroutine function)
|
||||
provider: Provider名称(用于错误格式化)
|
||||
*args, **kwargs: 传递给async_func的参数
|
||||
|
||||
返回:
|
||||
{"success": bool, "data": dict, "error": str} 格式的结果
|
||||
|
||||
示例:
|
||||
result = self._run_async_task(
|
||||
VisionService.analyze_image,
|
||||
"zhipu",
|
||||
image_data=data,
|
||||
request_id=req_id,
|
||||
...
|
||||
)
|
||||
"""
|
||||
result_container = {}
|
||||
|
||||
def thread_target():
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
# 执行异步任务
|
||||
result = loop.run_until_complete(async_func(*args, **kwargs))
|
||||
result_container['result'] = result
|
||||
except (asyncio.CancelledError, KeyboardInterrupt):
|
||||
# 捕获中断异常,不格式化错误
|
||||
print(f"{self.LOG_PREFIX} 检测到异步任务被取消")
|
||||
result_container['result'] = {"success": False, "error": "任务被中断"}
|
||||
except Exception as e:
|
||||
# 捕获其他异常,格式化错误信息
|
||||
error_message = format_api_error(e, provider)
|
||||
result_container['result'] = {"success": False, "error": error_message}
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
# 使用基类的线程中断检测方法
|
||||
self._run_thread_with_interrupt(
|
||||
thread_target,
|
||||
(),
|
||||
task_name="异步任务"
|
||||
)
|
||||
|
||||
def _run_vision_task(
|
||||
self,
|
||||
vision_service_func: Callable,
|
||||
provider: str,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
执行视觉任务(图像/视频分析)的统一方法
|
||||
|
||||
封装了:
|
||||
1. 异步任务的线程执行
|
||||
2. 中断异常的捕获和处理
|
||||
3. API异常的格式化
|
||||
|
||||
参数:
|
||||
vision_service_func: VisionService的异步方法(如analyze_image, analyze_images)
|
||||
provider: Provider名称(用于错误格式化,如"zhipu")
|
||||
*args, **kwargs: 传递给vision_service_func的参数
|
||||
|
||||
返回:
|
||||
{"success": bool, "data": dict, "error": str} 格式的结果
|
||||
|
||||
异常:
|
||||
InterruptProcessingException: 当检测到用户中断时
|
||||
|
||||
示例:
|
||||
result = self._run_vision_task(
|
||||
VisionService.analyze_image,
|
||||
"zhipu",
|
||||
image_data=data,
|
||||
request_id=req_id,
|
||||
prompt_content=prompt,
|
||||
custom_provider="zhipu",
|
||||
custom_provider_config=config
|
||||
)
|
||||
"""
|
||||
def thread_task(result_container, cancel_event):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
# 将cancel_event传递给服务函数
|
||||
kwargs['cancel_event'] = cancel_event
|
||||
result = loop.run_until_complete(vision_service_func(*args, **kwargs))
|
||||
result_container['result'] = result
|
||||
except (asyncio.CancelledError, KeyboardInterrupt):
|
||||
# 捕获中断异常,不格式化错误
|
||||
print(f"{self.LOG_PREFIX} 检测到异步任务被取消")
|
||||
result_container['result'] = {"success": False, "error": "任务被中断"}
|
||||
except Exception as e:
|
||||
# 捕获其他异常,格式化错误信息
|
||||
error_message = format_api_error(e, provider)
|
||||
result_container['result'] = {"success": False, "error": error_message}
|
||||
finally:
|
||||
# 清理所有未完成的任务,消除 "Task was destroyed but it is pending" 警告
|
||||
try:
|
||||
pending = asyncio.all_tasks(loop)
|
||||
if pending:
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
# 等待任务取消完成,忽略取消错误
|
||||
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
# 创建取消事件
|
||||
import threading
|
||||
cancel_event = threading.Event()
|
||||
|
||||
# 使用基类的中断检测执行
|
||||
return self._execute_with_interrupt(thread_task, (), cancel_event)
|
||||
|
||||
Reference in New Issue
Block a user