"""工具迁移和向后兼容层 - 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