refactor(aibox): 工具基类 Tool/ToolRegistry 抽离至 tools 包
延续 common 包的拆分模式,形成 common <- tools <- aibox 单向依赖, 为后续工具子类逐步迁移铺路;aibox.py 瘦身约 230 行,工具子类与注册逻辑零改动。
This commit is contained in:
1 parent
91b30040c7
commit
14db64f7a2
5 files changed
+285
-233
No files matched your search
+4
-1
@@ -67,6 +67,9 @@ aibox/
|
|||||||
├── entry.html # 前端入口页
|
├── entry.html # 前端入口页
|
||||||
├── entry1.html # 前端入口页(变体)
|
├── entry1.html # 前端入口页(变体)
|
||||||
├── frontend.html # 前端主页面
|
├── frontend.html # 前端主页面
|
||||||
|
├── llm_client.py # 大模型客户端抽象层(多厂商切换)
|
||||||
|
├── common/ # 基础设施包(i18n / 配置 / 常量 / 日志)
|
||||||
|
├── tools/ # 工具系统基础包(Tool 基类 + ToolRegistry 注册器)
|
||||||
├── skills/ # 内置扩展技能(*.py 可执行脚本 / *.md Markdown 技能)
|
├── skills/ # 内置扩展技能(*.py 可执行脚本 / *.md Markdown 技能)
|
||||||
│ ├── hello-world.py
|
│ ├── hello-world.py
|
||||||
│ ├── aes-crypto.py
|
│ ├── aes-crypto.py
|
||||||
@@ -621,7 +624,7 @@ Server -> Client: {type: "dialogue_reset", content: "✅ 对话状态已完全
|
|||||||
|
|
||||||
### 添加新工具
|
### 添加新工具
|
||||||
|
|
||||||
1. 继承 `Tool` 基类(位于 `aibox.py`)
|
1. 继承 `Tool` 基类(位于 `tools/base.py`)
|
||||||
2. 实现 `get_name()`、`get_description()`、`get_parameters()`、`execute()`
|
2. 实现 `get_name()`、`get_description()`、`get_parameters()`、`execute()`
|
||||||
3. 可选实现 `execute_streaming()` 支持流式输出(通过 `output_queue` 发送 `("line", content)` 和 `("complete", final_result)`)
|
3. 可选实现 `execute_streaming()` 支持流式输出(通过 `output_queue` 发送 `("line", content)` 和 `("complete", final_result)`)
|
||||||
4. 在 `DeepSeekChat._register_tools()` 中注册
|
4. 在 `DeepSeekChat._register_tools()` 中注册
|
||||||
|
|||||||
+3
-232
@@ -41,6 +41,9 @@ from llm_client import create_llm_client
|
|||||||
# 基础设施(多语言 / 配置 / 常量 / 日志)已抽离至 common 模块
|
# 基础设施(多语言 / 配置 / 常量 / 日志)已抽离至 common 模块
|
||||||
from common import i18n, I18n, ConfigManager, Constants, constants, Logger
|
from common import i18n, I18n, ConfigManager, Constants, constants, Logger
|
||||||
|
|
||||||
|
# 工具基类与注册器已抽离至 tools 模块
|
||||||
|
from tools import Tool, ToolRegistry
|
||||||
|
|
||||||
# ==================== 提示词管理器 ====================
|
# ==================== 提示词管理器 ====================
|
||||||
|
|
||||||
class PromptManager:
|
class PromptManager:
|
||||||
@@ -2959,238 +2962,6 @@ class SubprocessCommandExecutor(CommandExecutor):
|
|||||||
with self._process_lock:
|
with self._process_lock:
|
||||||
self._current_process = None
|
self._current_process = None
|
||||||
|
|
||||||
# ==================== 工具基类 ====================
|
|
||||||
|
|
||||||
class Tool(ABC):
|
|
||||||
"""工具基类"""
|
|
||||||
|
|
||||||
def __init__(self, logger: Optional[Logger] = None):
|
|
||||||
self.logger = logger or Logger(self.__class__.__name__)
|
|
||||||
self.config = ConfigManager()
|
|
||||||
self.streaming_output = False # 标记是否正在流式输出
|
|
||||||
self.output_queue = None # 添加输出队列属性
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_name(self) -> str:
|
|
||||||
"""返回工具名称"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_description(self) -> str:
|
|
||||||
"""返回工具描述"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_parameters(self) -> Dict:
|
|
||||||
"""返回工具参数定义(JSON Schema格式)"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def execute(self, **kwargs) -> str:
|
|
||||||
"""执行工具并返回结果"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def execute_streaming(self, output_queue: queue.Queue, **kwargs):
|
|
||||||
"""流式执行工具,通过队列输出结果"""
|
|
||||||
self.output_queue = output_queue # 保存队列引用
|
|
||||||
self.streaming_output = True
|
|
||||||
try:
|
|
||||||
# 执行具体的工具逻辑,子类可以重写这个方法
|
|
||||||
result = self.execute(**kwargs)
|
|
||||||
# 如果结果不为空,发送到队列
|
|
||||||
if result:
|
|
||||||
# 对于非流式输出,直接发送完整结果
|
|
||||||
output_queue.put(("complete", result))
|
|
||||||
finally:
|
|
||||||
self.streaming_output = False
|
|
||||||
self.output_queue = None
|
|
||||||
|
|
||||||
def validate_params(self, required: List[str], **kwargs) -> Optional[str]:
|
|
||||||
"""验证必需参数"""
|
|
||||||
missing = [p for p in required if p not in kwargs or kwargs[p] is None]
|
|
||||||
if missing:
|
|
||||||
return f"❌ 错误:缺少必需参数: {', '.join(missing)}"
|
|
||||||
return None
|
|
||||||
|
|
||||||
def to_function_call_format(self) -> Dict:
|
|
||||||
"""转换为OpenAI函数调用格式"""
|
|
||||||
return {
|
|
||||||
"type": "function",
|
|
||||||
"function": {
|
|
||||||
"name": self.get_name(),
|
|
||||||
"description": self.get_description(),
|
|
||||||
"parameters": self.get_parameters()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def format_result(self, success: bool, message: str, data: Dict = None) -> str:
|
|
||||||
"""统一结果格式化"""
|
|
||||||
result = []
|
|
||||||
emoji = "✅" if success else "❌"
|
|
||||||
result.append(f"{emoji} {message}")
|
|
||||||
|
|
||||||
if data:
|
|
||||||
for key, value in data.items():
|
|
||||||
if isinstance(value, str) and "\n" in value:
|
|
||||||
result.append(f" {key}:")
|
|
||||||
for line in value.split("\n"):
|
|
||||||
result.append(f" {line}")
|
|
||||||
else:
|
|
||||||
result.append(f" {key}: {value}")
|
|
||||||
|
|
||||||
return "\n".join(result)
|
|
||||||
|
|
||||||
def check_path_safety(self, filepath: str) -> Tuple[bool, str, str]:
|
|
||||||
"""检查路径安全性(简化版)"""
|
|
||||||
try:
|
|
||||||
abs_path = os.path.abspath(os.path.expanduser(filepath))
|
|
||||||
|
|
||||||
# 检查文件大小
|
|
||||||
if os.path.exists(abs_path) and os.path.isfile(abs_path):
|
|
||||||
file_size = os.path.getsize(abs_path)
|
|
||||||
max_size = constants.MAX_FILE_SIZE
|
|
||||||
if file_size > max_size:
|
|
||||||
return False, abs_path, f"❌ 文件太大 ({file_size/1024/1024:.1f}MB)"
|
|
||||||
|
|
||||||
return True, abs_path, ""
|
|
||||||
except Exception as e:
|
|
||||||
return False, filepath, f"❌ 路径检查错误: {str(e)}"
|
|
||||||
|
|
||||||
def send_line(self, line: str):
|
|
||||||
"""发送一行输出到队列(如果存在)"""
|
|
||||||
if self.output_queue:
|
|
||||||
self.output_queue.put(("line", line))
|
|
||||||
|
|
||||||
def send_complete(self, result: str):
|
|
||||||
"""发送完成结果到队列(如果存在)"""
|
|
||||||
if self.output_queue:
|
|
||||||
self.output_queue.put(("complete", result))
|
|
||||||
|
|
||||||
|
|
||||||
class ToolRegistry:
|
|
||||||
"""工具注册器 - 支持技能热重载"""
|
|
||||||
|
|
||||||
def __init__(self, logger: Optional[Logger] = None):
|
|
||||||
self._tools: Dict[str, Tool] = {}
|
|
||||||
self._builtin_tools: Dict[str, Tool] = {} # 内置工具单独存储
|
|
||||||
self.logger = logger or Logger("ToolRegistry")
|
|
||||||
self.current_output_queue = None # 用于流式输出的队列
|
|
||||||
self.skill_manager: Optional['SkillManager'] = None # 技能管理器引用
|
|
||||||
|
|
||||||
def register(self, tool: Tool, is_builtin: bool = False):
|
|
||||||
"""注册工具,可以标记是否为内置工具"""
|
|
||||||
self._tools[tool.get_name()] = tool
|
|
||||||
if is_builtin:
|
|
||||||
self._builtin_tools[tool.get_name()] = tool
|
|
||||||
self.logger.info(f"工具已注册: {tool.get_name()} {'(内置)' if is_builtin else ''}")
|
|
||||||
|
|
||||||
def register_many(self, tools: List[Tool], is_builtin: bool = False):
|
|
||||||
"""批量注册工具"""
|
|
||||||
for tool in tools:
|
|
||||||
self.register(tool, is_builtin)
|
|
||||||
|
|
||||||
def get_tool(self, name: str) -> Optional[Tool]:
|
|
||||||
"""获取工具"""
|
|
||||||
return self._tools.get(name)
|
|
||||||
|
|
||||||
def get_all_tools(self) -> List[Tool]:
|
|
||||||
"""获取所有工具"""
|
|
||||||
return list(self._tools.values())
|
|
||||||
|
|
||||||
def get_tools_schemas(self) -> List[Dict]:
|
|
||||||
"""获取所有工具的OpenAI函数调用格式"""
|
|
||||||
return [tool.to_function_call_format() for tool in self._tools.values()]
|
|
||||||
|
|
||||||
def set_output_queue(self, queue: queue.Queue):
|
|
||||||
"""设置输出队列"""
|
|
||||||
self.current_output_queue = queue
|
|
||||||
# 同时设置所有工具的队列
|
|
||||||
for tool in self._tools.values():
|
|
||||||
tool.output_queue = queue
|
|
||||||
|
|
||||||
def execute_tool(self, name: str, **kwargs) -> str:
|
|
||||||
"""执行工具"""
|
|
||||||
tool = self.get_tool(name)
|
|
||||||
if not tool:
|
|
||||||
return f"❌ 错误:找不到工具 '{name}'"
|
|
||||||
|
|
||||||
self.logger.debug(f"工具调用: {name}({kwargs})")
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 检查是否有队列可用,并且工具支持流式输出
|
|
||||||
if self.current_output_queue and hasattr(tool, 'execute_streaming'):
|
|
||||||
# 设置工具的流式输出属性
|
|
||||||
tool.streaming_output = True
|
|
||||||
tool.output_queue = self.current_output_queue
|
|
||||||
|
|
||||||
# 创建队列来收集流式输出
|
|
||||||
output_queue = queue.Queue()
|
|
||||||
|
|
||||||
# 执行流式输出
|
|
||||||
thread = threading.Thread(
|
|
||||||
target=tool.execute_streaming,
|
|
||||||
args=(output_queue,),
|
|
||||||
kwargs=kwargs
|
|
||||||
)
|
|
||||||
thread.daemon = True
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
# 从队列收集输出并发送到当前队列
|
|
||||||
final_result = None
|
|
||||||
while thread.is_alive() or not output_queue.empty():
|
|
||||||
try:
|
|
||||||
msg_type, content = output_queue.get(timeout=0.1)
|
|
||||||
if msg_type == "line":
|
|
||||||
# 发送到当前队列(会通过WebSocket发送)
|
|
||||||
if self.current_output_queue:
|
|
||||||
self.current_output_queue.put(("line", content))
|
|
||||||
elif msg_type == "complete":
|
|
||||||
final_result = content
|
|
||||||
# 也发送完整结果
|
|
||||||
if self.current_output_queue:
|
|
||||||
self.current_output_queue.put(("complete", content))
|
|
||||||
except queue.Empty:
|
|
||||||
continue
|
|
||||||
|
|
||||||
thread.join(timeout=5)
|
|
||||||
if thread.is_alive():
|
|
||||||
return "❌ 工具执行超时"
|
|
||||||
|
|
||||||
result = final_result or ""
|
|
||||||
tool.streaming_output = False
|
|
||||||
tool.output_queue = None
|
|
||||||
else:
|
|
||||||
# 普通执行
|
|
||||||
result = tool.execute(**kwargs)
|
|
||||||
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
self.logger.debug(f"工具结果 ({elapsed:.2f}s): {result[:100]}...")
|
|
||||||
return result
|
|
||||||
except TypeError as e:
|
|
||||||
return f"❌ 工具执行错误: 参数错误 - {str(e)}"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ 工具执行错误: {str(e)}"
|
|
||||||
|
|
||||||
def reload_skills(self, skills_dir: str = None) -> int:
|
|
||||||
"""
|
|
||||||
重新加载所有扩展技能(热重载)。
|
|
||||||
委托给 SkillManager 重新扫描目录并执行 --help 收集简介。
|
|
||||||
"""
|
|
||||||
self.logger.info("开始重新加载扩展技能...")
|
|
||||||
if self.skill_manager is not None:
|
|
||||||
count = self.skill_manager.reload()
|
|
||||||
self.logger.info(i18n.get('skills_reloaded', count))
|
|
||||||
return count
|
|
||||||
self.logger.warning("skill_manager 未初始化,无法重载技能")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_skills_count(self) -> Tuple[int, int]:
|
|
||||||
"""获取内置工具和扩展技能的数量"""
|
|
||||||
builtin_count = len(self._builtin_tools)
|
|
||||||
skill_count = len(self._tools) - builtin_count
|
|
||||||
return builtin_count, skill_count
|
|
||||||
|
|
||||||
|
|
||||||
# ==================== 扩展技能管理器(支持 Markdown 格式) ====================
|
# ==================== 扩展技能管理器(支持 Markdown 格式) ====================
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
tools - aibox 工具系统基础模块(包)
|
||||||
|
|
||||||
|
抽离自 aibox.py 的工具基类与注册器,供 aibox.py 及后续迁移的
|
||||||
|
工具子类导入。当前包含:
|
||||||
|
- base.py : Tool 抽象基类
|
||||||
|
- registry.py : ToolRegistry 工具注册器
|
||||||
|
形成单向依赖:common <- tools <- aibox。
|
||||||
|
|
||||||
|
采用"重导出"策略:外部统一用 `from tools import Tool, ToolRegistry`,
|
||||||
|
调用点无需关心具体子模块所在文件。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .base import Tool
|
||||||
|
from .registry import ToolRegistry
|
||||||
|
|
||||||
|
__all__ = ["Tool", "ToolRegistry"]
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
tools.base - 工具抽象基类
|
||||||
|
|
||||||
|
抽离自 aibox.py 的 Tool 基类。仅依赖 common 与标准库,
|
||||||
|
不反向依赖 aibox 任何符号。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import queue
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from common import ConfigManager, Logger, constants
|
||||||
|
|
||||||
|
|
||||||
|
class Tool(ABC):
|
||||||
|
"""工具基类"""
|
||||||
|
|
||||||
|
def __init__(self, logger: Optional[Logger] = None):
|
||||||
|
self.logger = logger or Logger(self.__class__.__name__)
|
||||||
|
self.config = ConfigManager()
|
||||||
|
self.streaming_output = False # 标记是否正在流式输出
|
||||||
|
self.output_queue = None # 添加输出队列属性
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_name(self) -> str:
|
||||||
|
"""返回工具名称"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_description(self) -> str:
|
||||||
|
"""返回工具描述"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_parameters(self) -> Dict:
|
||||||
|
"""返回工具参数定义(JSON Schema格式)"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self, **kwargs) -> str:
|
||||||
|
"""执行工具并返回结果"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def execute_streaming(self, output_queue: queue.Queue, **kwargs):
|
||||||
|
"""流式执行工具,通过队列输出结果"""
|
||||||
|
self.output_queue = output_queue # 保存队列引用
|
||||||
|
self.streaming_output = True
|
||||||
|
try:
|
||||||
|
# 执行具体的工具逻辑,子类可以重写这个方法
|
||||||
|
result = self.execute(**kwargs)
|
||||||
|
# 如果结果不为空,发送到队列
|
||||||
|
if result:
|
||||||
|
# 对于非流式输出,直接发送完整结果
|
||||||
|
output_queue.put(("complete", result))
|
||||||
|
finally:
|
||||||
|
self.streaming_output = False
|
||||||
|
self.output_queue = None
|
||||||
|
|
||||||
|
def validate_params(self, required: List[str], **kwargs) -> Optional[str]:
|
||||||
|
"""验证必需参数"""
|
||||||
|
missing = [p for p in required if p not in kwargs or kwargs[p] is None]
|
||||||
|
if missing:
|
||||||
|
return f"❌ 错误:缺少必需参数: {', '.join(missing)}"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def to_function_call_format(self) -> Dict:
|
||||||
|
"""转换为OpenAI函数调用格式"""
|
||||||
|
return {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": self.get_name(),
|
||||||
|
"description": self.get_description(),
|
||||||
|
"parameters": self.get_parameters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def format_result(self, success: bool, message: str, data: Dict = None) -> str:
|
||||||
|
"""统一结果格式化"""
|
||||||
|
result = []
|
||||||
|
emoji = "✅" if success else "❌"
|
||||||
|
result.append(f"{emoji} {message}")
|
||||||
|
|
||||||
|
if data:
|
||||||
|
for key, value in data.items():
|
||||||
|
if isinstance(value, str) and "\n" in value:
|
||||||
|
result.append(f" {key}:")
|
||||||
|
for line in value.split("\n"):
|
||||||
|
result.append(f" {line}")
|
||||||
|
else:
|
||||||
|
result.append(f" {key}: {value}")
|
||||||
|
|
||||||
|
return "\n".join(result)
|
||||||
|
|
||||||
|
def check_path_safety(self, filepath: str) -> Tuple[bool, str, str]:
|
||||||
|
"""检查路径安全性(简化版)"""
|
||||||
|
try:
|
||||||
|
abs_path = os.path.abspath(os.path.expanduser(filepath))
|
||||||
|
|
||||||
|
# 检查文件大小
|
||||||
|
if os.path.exists(abs_path) and os.path.isfile(abs_path):
|
||||||
|
file_size = os.path.getsize(abs_path)
|
||||||
|
max_size = constants.MAX_FILE_SIZE
|
||||||
|
if file_size > max_size:
|
||||||
|
return False, abs_path, f"❌ 文件太大 ({file_size/1024/1024:.1f}MB)"
|
||||||
|
|
||||||
|
return True, abs_path, ""
|
||||||
|
except Exception as e:
|
||||||
|
return False, filepath, f"❌ 路径检查错误: {str(e)}"
|
||||||
|
|
||||||
|
def send_line(self, line: str):
|
||||||
|
"""发送一行输出到队列(如果存在)"""
|
||||||
|
if self.output_queue:
|
||||||
|
self.output_queue.put(("line", line))
|
||||||
|
|
||||||
|
def send_complete(self, result: str):
|
||||||
|
"""发送完成结果到队列(如果存在)"""
|
||||||
|
if self.output_queue:
|
||||||
|
self.output_queue.put(("complete", result))
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
tools.registry - 工具注册器
|
||||||
|
|
||||||
|
抽离自 aibox.py 的 ToolRegistry 类。仅依赖 common 与标准库,
|
||||||
|
不反向依赖 aibox 任何符号(skill_manager 为前向引用,运行期注入)。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import queue
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from common import Logger, i18n
|
||||||
|
|
||||||
|
from .base import Tool
|
||||||
|
|
||||||
|
|
||||||
|
class ToolRegistry:
|
||||||
|
"""工具注册器 - 支持技能热重载"""
|
||||||
|
|
||||||
|
def __init__(self, logger: Optional[Logger] = None):
|
||||||
|
self._tools: Dict[str, Tool] = {}
|
||||||
|
self._builtin_tools: Dict[str, Tool] = {} # 内置工具单独存储
|
||||||
|
self.logger = logger or Logger("ToolRegistry")
|
||||||
|
self.current_output_queue = None # 用于流式输出的队列
|
||||||
|
self.skill_manager: Optional['SkillManager'] = None # 技能管理器引用
|
||||||
|
|
||||||
|
def register(self, tool: Tool, is_builtin: bool = False):
|
||||||
|
"""注册工具,可以标记是否为内置工具"""
|
||||||
|
self._tools[tool.get_name()] = tool
|
||||||
|
if is_builtin:
|
||||||
|
self._builtin_tools[tool.get_name()] = tool
|
||||||
|
self.logger.info(f"工具已注册: {tool.get_name()} {'(内置)' if is_builtin else ''}")
|
||||||
|
|
||||||
|
def register_many(self, tools: List[Tool], is_builtin: bool = False):
|
||||||
|
"""批量注册工具"""
|
||||||
|
for tool in tools:
|
||||||
|
self.register(tool, is_builtin)
|
||||||
|
|
||||||
|
def get_tool(self, name: str) -> Optional[Tool]:
|
||||||
|
"""获取工具"""
|
||||||
|
return self._tools.get(name)
|
||||||
|
|
||||||
|
def get_all_tools(self) -> List[Tool]:
|
||||||
|
"""获取所有工具"""
|
||||||
|
return list(self._tools.values())
|
||||||
|
|
||||||
|
def get_tools_schemas(self) -> List[Dict]:
|
||||||
|
"""获取所有工具的OpenAI函数调用格式"""
|
||||||
|
return [tool.to_function_call_format() for tool in self._tools.values()]
|
||||||
|
|
||||||
|
def set_output_queue(self, queue: queue.Queue):
|
||||||
|
"""设置输出队列"""
|
||||||
|
self.current_output_queue = queue
|
||||||
|
# 同时设置所有工具的队列
|
||||||
|
for tool in self._tools.values():
|
||||||
|
tool.output_queue = queue
|
||||||
|
|
||||||
|
def execute_tool(self, name: str, **kwargs) -> str:
|
||||||
|
"""执行工具"""
|
||||||
|
tool = self.get_tool(name)
|
||||||
|
if not tool:
|
||||||
|
return f"❌ 错误:找不到工具 '{name}'"
|
||||||
|
|
||||||
|
self.logger.debug(f"工具调用: {name}({kwargs})")
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 检查是否有队列可用,并且工具支持流式输出
|
||||||
|
if self.current_output_queue and hasattr(tool, 'execute_streaming'):
|
||||||
|
# 设置工具的流式输出属性
|
||||||
|
tool.streaming_output = True
|
||||||
|
tool.output_queue = self.current_output_queue
|
||||||
|
|
||||||
|
# 创建队列来收集流式输出
|
||||||
|
output_queue = queue.Queue()
|
||||||
|
|
||||||
|
# 执行流式输出
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=tool.execute_streaming,
|
||||||
|
args=(output_queue,),
|
||||||
|
kwargs=kwargs
|
||||||
|
)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
# 从队列收集输出并发送到当前队列
|
||||||
|
final_result = None
|
||||||
|
while thread.is_alive() or not output_queue.empty():
|
||||||
|
try:
|
||||||
|
msg_type, content = output_queue.get(timeout=0.1)
|
||||||
|
if msg_type == "line":
|
||||||
|
# 发送到当前队列(会通过WebSocket发送)
|
||||||
|
if self.current_output_queue:
|
||||||
|
self.current_output_queue.put(("line", content))
|
||||||
|
elif msg_type == "complete":
|
||||||
|
final_result = content
|
||||||
|
# 也发送完整结果
|
||||||
|
if self.current_output_queue:
|
||||||
|
self.current_output_queue.put(("complete", content))
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
thread.join(timeout=5)
|
||||||
|
if thread.is_alive():
|
||||||
|
return "❌ 工具执行超时"
|
||||||
|
|
||||||
|
result = final_result or ""
|
||||||
|
tool.streaming_output = False
|
||||||
|
tool.output_queue = None
|
||||||
|
else:
|
||||||
|
# 普通执行
|
||||||
|
result = tool.execute(**kwargs)
|
||||||
|
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
self.logger.debug(f"工具结果 ({elapsed:.2f}s): {result[:100]}...")
|
||||||
|
return result
|
||||||
|
except TypeError as e:
|
||||||
|
return f"❌ 工具执行错误: 参数错误 - {str(e)}"
|
||||||
|
except Exception as e:
|
||||||
|
return f"❌ 工具执行错误: {str(e)}"
|
||||||
|
|
||||||
|
def reload_skills(self, skills_dir: str = None) -> int:
|
||||||
|
"""
|
||||||
|
重新加载所有扩展技能(热重载)。
|
||||||
|
委托给 SkillManager 重新扫描目录并执行 --help 收集简介。
|
||||||
|
"""
|
||||||
|
self.logger.info("开始重新加载扩展技能...")
|
||||||
|
if self.skill_manager is not None:
|
||||||
|
count = self.skill_manager.reload()
|
||||||
|
self.logger.info(i18n.get('skills_reloaded', count))
|
||||||
|
return count
|
||||||
|
self.logger.warning("skill_manager 未初始化,无法重载技能")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_skills_count(self) -> Tuple[int, int]:
|
||||||
|
"""获取内置工具和扩展技能的数量"""
|
||||||
|
builtin_count = len(self._builtin_tools)
|
||||||
|
skill_count = len(self._tools) - builtin_count
|
||||||
|
return builtin_count, skill_count
|
||||||
Reference in new issue
Block a user