feat(agents): Phase 6 tool system refactoring
Phase 6.1: ToolRegistry infrastructure - Add ToolManifest with ToolCategory, PermissionClass, SideEffectScope - Add ToolRegistry singleton with register/get/unregister/list/search - Add BaseTool abstract class with ReadTool/WriteTool/DBWriteTool/ExternalTool/NetworkTool subclasses - Add migration layer for backward compatibility Phase 6.2: Hook interception system - Add HookType (PRE_TOOL_USE, POST_TOOL_USE, TOOL_ERROR, TOOL_SKIP) - Add HookManager with singleton for hook registration - Add HookExecutor for pre/post/error hook execution Phase 6.3: Streaming execution - Add StreamingToolExecutor with batch execution support Phase 6.4: New builtin tools - Add file_tools: GlobTool, GrepTool, ReadFileTool, WriteFileTool - Add system_tools: BashTool, PowerShellTool - Add dev_tools: LSPTools, GitTool - Add collaboration_tools: TeamAgentTool, TaskBroadcastTool Tests: 29 passed
This commit is contained in:
251
backend/app/agents/tools/migration.py
Normal file
251
backend/app/agents/tools/migration.py
Normal file
@@ -0,0 +1,251 @@
|
||||
"""工具迁移和向后兼容层 - Phase 6.1
|
||||
|
||||
将现有 @tool 装饰的工具迁移到 ToolRegistry,同时保持向后兼容。
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
from typing import Any, Callable
|
||||
|
||||
from app.agents.tools.manifest import (
|
||||
PermissionClass,
|
||||
SideEffectScope,
|
||||
ToolCategory,
|
||||
ToolManifest,
|
||||
)
|
||||
from app.agents.tools.registry import get_tool_registry
|
||||
|
||||
|
||||
# 现有工具的类别映射
|
||||
_TOOL_CATEGORY_MAP: dict[str, tuple[ToolCategory, PermissionClass, SideEffectScope]] = {
|
||||
# 知识检索 - 只读
|
||||
"search_knowledge": (ToolCategory.READ, PermissionClass.READ, SideEffectScope.NONE),
|
||||
"get_knowledge_graph_context": (ToolCategory.READ, PermissionClass.READ, SideEffectScope.NONE),
|
||||
"hybrid_search": (ToolCategory.READ, PermissionClass.READ, SideEffectScope.NONE),
|
||||
"web_search": (ToolCategory.NETWORK, PermissionClass.EXTERNAL, SideEffectScope.NETWORK),
|
||||
# 知识构建 - 写入
|
||||
"build_knowledge_graph": (
|
||||
ToolCategory.WRITE,
|
||||
PermissionClass.WRITE,
|
||||
SideEffectScope.LOCAL_STATE,
|
||||
),
|
||||
# 任务工具
|
||||
"get_tasks": (ToolCategory.READ, PermissionClass.READ, SideEffectScope.NONE),
|
||||
"create_task": (ToolCategory.WRITE, PermissionClass.WRITE, SideEffectScope.LOCAL_STATE),
|
||||
"update_task_status": (ToolCategory.WRITE, PermissionClass.WRITE, SideEffectScope.LOCAL_STATE),
|
||||
# 日程工具
|
||||
"get_schedule_day": (ToolCategory.READ, PermissionClass.READ, SideEffectScope.NONE),
|
||||
"create_todo": (ToolCategory.WRITE, PermissionClass.WRITE, SideEffectScope.LOCAL_STATE),
|
||||
"create_schedule_task": (
|
||||
ToolCategory.WRITE,
|
||||
PermissionClass.WRITE,
|
||||
SideEffectScope.LOCAL_STATE,
|
||||
),
|
||||
"create_reminder": (ToolCategory.WRITE, PermissionClass.WRITE, SideEffectScope.LOCAL_STATE),
|
||||
"create_goal": (ToolCategory.WRITE, PermissionClass.WRITE, SideEffectScope.LOCAL_STATE),
|
||||
"resolve_time_expression": (ToolCategory.READ, PermissionClass.READ, SideEffectScope.NONE),
|
||||
# 论坛工具
|
||||
"get_forum_posts": (ToolCategory.READ, PermissionClass.READ, SideEffectScope.NONE),
|
||||
"create_forum_post": (ToolCategory.WRITE, PermissionClass.WRITE, SideEffectScope.LOCAL_STATE),
|
||||
"scan_forum_for_instructions": (ToolCategory.READ, PermissionClass.READ, SideEffectScope.NONE),
|
||||
}
|
||||
|
||||
|
||||
def get_tool_category(name: str) -> tuple[ToolCategory, PermissionClass, SideEffectScope]:
|
||||
"""获取工具的类别信息"""
|
||||
return _TOOL_CATEGORY_MAP.get(
|
||||
name,
|
||||
(ToolCategory.EXTERNAL, PermissionClass.EXTERNAL, SideEffectScope.NETWORK),
|
||||
)
|
||||
|
||||
|
||||
def infer_tags_from_docstring(docstring: str | None) -> list[str]:
|
||||
"""从 docstring 推断工具标签"""
|
||||
if not docstring:
|
||||
return []
|
||||
tags = []
|
||||
doc_lower = docstring.lower()
|
||||
if "搜索" in docstring or "查询" in docstring or "search" in doc_lower:
|
||||
tags.append("search")
|
||||
if "创建" in docstring or "新建" in docstring or "create" in doc_lower:
|
||||
tags.append("create")
|
||||
if "获取" in docstring or "读取" in docstring or "get" in doc_lower:
|
||||
tags.append("read")
|
||||
if "更新" in docstring or "修改" in docstring or "update" in doc_lower:
|
||||
tags.append("update")
|
||||
return tags
|
||||
|
||||
|
||||
def migrate_tool(tool_func: Callable) -> Callable:
|
||||
"""将现有 @tool 装饰的函数迁移到 ToolRegistry
|
||||
|
||||
Args:
|
||||
tool_func: LangChain @tool 装饰的函数
|
||||
|
||||
Returns:
|
||||
原函数(已注册到 registry)
|
||||
"""
|
||||
registry = get_tool_registry()
|
||||
|
||||
# 如果已经注册,跳过
|
||||
if registry.get(tool_func.name):
|
||||
return tool_func
|
||||
|
||||
# 获取类别信息
|
||||
category, permission, side_effect = get_tool_category(tool_func.name)
|
||||
|
||||
# 从 docstring 提取 description
|
||||
description = tool_func.description if hasattr(tool_func, "description") else ""
|
||||
|
||||
# 推断 tags
|
||||
tags = infer_tags_from_docstring(description)
|
||||
tags.append("migrated")
|
||||
|
||||
# 创建 manifest
|
||||
manifest = ToolManifest(
|
||||
name=tool_func.name,
|
||||
description=description,
|
||||
category=category,
|
||||
parameters={}, # LangChain @tool 动态处理参数
|
||||
return_schema={},
|
||||
permission_class=permission,
|
||||
side_effect_scope=side_effect,
|
||||
requires_confirmation=side_effect != SideEffectScope.NONE,
|
||||
is_streaming=False,
|
||||
tags=tags,
|
||||
)
|
||||
|
||||
# 注册到 registry
|
||||
registry.register(manifest, tool_func)
|
||||
|
||||
return tool_func
|
||||
|
||||
|
||||
def migrate_all_tools() -> int:
|
||||
"""迁移所有现有工具到 ToolRegistry
|
||||
|
||||
Returns:
|
||||
迁移的工具数量
|
||||
"""
|
||||
from app.agents.tools import (
|
||||
ALL_TOOLS,
|
||||
KNOWLEDGE_GRAPH_TOOLS,
|
||||
KNOWLEDGE_RETRIEVAL_TOOLS,
|
||||
SCHEDULE_READ_TOOLS,
|
||||
SCHEDULE_WRITE_TOOLS,
|
||||
TASK_TOOLS,
|
||||
FORUM_TOOLS,
|
||||
)
|
||||
|
||||
all_tools = (
|
||||
KNOWLEDGE_RETRIEVAL_TOOLS
|
||||
+ KNOWLEDGE_GRAPH_TOOLS
|
||||
+ TASK_TOOLS
|
||||
+ SCHEDULE_READ_TOOLS
|
||||
+ SCHEDULE_WRITE_TOOLS
|
||||
+ FORUM_TOOLS
|
||||
)
|
||||
|
||||
count = 0
|
||||
for tool in all_tools:
|
||||
try:
|
||||
migrate_tool(tool)
|
||||
count += 1
|
||||
except Exception as e:
|
||||
print(f"Failed to migrate tool {getattr(tool, 'name', 'unknown')}: {e}")
|
||||
|
||||
return count
|
||||
|
||||
|
||||
class BackwardCompatTool:
|
||||
"""向后兼容工具包装器
|
||||
|
||||
确保现有代码通过 registry.get_executor() 仍能正常调用工具。
|
||||
"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self._registry = get_tool_registry()
|
||||
|
||||
def __call__(self, *args, **kwargs) -> Any:
|
||||
executor = self._registry.get_executor(self.name)
|
||||
if executor is None:
|
||||
raise ValueError(f"Tool not found in registry: {self.name}")
|
||||
return executor(*args, **kwargs)
|
||||
|
||||
def invoke(self, tool_input: dict[str, Any]) -> Any:
|
||||
"""LangChain 风格的 invoke 调用"""
|
||||
executor = self._registry.get_executor(self.name)
|
||||
if executor is None:
|
||||
raise ValueError(f"Tool not found in registry: {self.name}")
|
||||
|
||||
# 处理位置参数
|
||||
if isinstance(tool_input, dict):
|
||||
return executor(**tool_input)
|
||||
return executor(tool_input)
|
||||
|
||||
|
||||
def create_compat_layer() -> dict[str, BackwardCompatTool]:
|
||||
"""创建向后兼容层
|
||||
|
||||
返回一个字典,允许通过名称访问兼容的工具包装器。
|
||||
"""
|
||||
registry = get_tool_registry()
|
||||
tools = registry.list_all()
|
||||
|
||||
return {tool.name: BackwardCompatTool(tool.name) for tool in tools}
|
||||
|
||||
|
||||
# 自动迁移装饰器
|
||||
def auto_migrate(func: Callable) -> Callable:
|
||||
"""自动迁移装饰器
|
||||
|
||||
用于装饰新的 @tool 函数,自动注册到 registry。
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# 迁移到 registry
|
||||
migrate_tool(wrapper)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# 便捷函数:获取兼容的工具执行器
|
||||
def get_tool_executor(name: str) -> Callable | None:
|
||||
"""获取工具执行器(兼容层)
|
||||
|
||||
优先从 registry 获取,fallback 到直接导入。
|
||||
"""
|
||||
registry = get_tool_registry()
|
||||
executor = registry.get_executor(name)
|
||||
|
||||
if executor is not None:
|
||||
return executor
|
||||
|
||||
# Fallback: 直接从模块导入(仅用于迁移期间)
|
||||
try:
|
||||
from app.agents.tools import (
|
||||
TASK_TOOLS,
|
||||
SCHEDULE_READ_TOOLS,
|
||||
SCHEDULE_WRITE_TOOLS,
|
||||
FORUM_TOOLS,
|
||||
KNOWLEDGE_RETRIEVAL_TOOLS,
|
||||
)
|
||||
|
||||
all_tools = (
|
||||
KNOWLEDGE_RETRIEVAL_TOOLS
|
||||
+ TASK_TOOLS
|
||||
+ SCHEDULE_READ_TOOLS
|
||||
+ SCHEDULE_WRITE_TOOLS
|
||||
+ FORUM_TOOLS
|
||||
)
|
||||
|
||||
for tool in all_tools:
|
||||
if hasattr(tool, "name") and tool.name == name:
|
||||
return tool
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user