ForcePilot/backend/package/yuxi/agents/toolkits/service.py
2026-05-29 22:19:58 +08:00

134 lines
4.4 KiB
Python

from typing import Any
from yuxi.utils import logger
# 工具元数据缓存
_metadata_cache: list[dict] = []
def _extract_tool_info(tool_obj) -> dict:
"""从 tool_obj 提取基础信息"""
metadata = getattr(tool_obj, "metadata", {}) or {}
info = {
"slug": tool_obj.name,
"name": metadata.get("name", tool_obj.name), # 显示名称优先从 metadata 获取
"description": tool_obj.description,
"metadata": metadata,
"args": [],
}
if hasattr(tool_obj, "args_schema") and tool_obj.args_schema:
schema = tool_obj.args_schema
if hasattr(schema, "schema"):
schema = schema.schema()
for arg_name, arg_info in schema.get("properties", {}).items():
info["args"].append(
{
"name": arg_name,
"type": arg_info.get("type", ""),
"description": arg_info.get("description", ""),
}
)
return info
def _ensure_metadata_loaded():
"""延迟加载工具元数据(首次调用时自动触发)"""
global _metadata_cache
if _metadata_cache: # 已加载
return
from yuxi.agents.toolkits.registry import (
get_all_extra_metadata,
get_all_tool_instances,
)
# 获取所有工具实例
all_tools = get_all_tool_instances()
extra_meta = get_all_extra_metadata()
for tool in all_tools:
tool_name = tool.name
runtime_info = _extract_tool_info(tool)
# 合并附加元数据
if tool_name in extra_meta:
extra = extra_meta[tool_name]
runtime_info["category"] = extra.category
runtime_info["tags"] = extra.tags
runtime_info["config_guide"] = extra.config_guide
# display_name 优先级高于 tool.name
if extra.display_name:
runtime_info["name"] = extra.display_name
else:
# 未注册,设为默认分类
runtime_info["category"] = "buildin"
runtime_info["tags"] = []
runtime_info["config_guide"] = ""
_metadata_cache.append(runtime_info)
logger.info(f"Tool service loaded {len(_metadata_cache)} tools (lazy load)")
def get_tool_metadata(category: str = None) -> list[dict]:
"""获取工具元数据列表(延迟加载)"""
_ensure_metadata_loaded()
if category:
return [t for t in _metadata_cache if t.get("category") == category]
return _metadata_cache
def get_tool_instances_by_category(category: str) -> list[Any]:
from yuxi.agents.toolkits.registry import get_all_extra_metadata, get_all_tool_instances
extra_meta = get_all_extra_metadata()
tools = []
for tool in get_all_tool_instances():
tool_meta = extra_meta.get(tool.name)
tool_category = tool_meta.category if tool_meta else "buildin"
if tool_category == category:
tools.append(tool)
return tools
async def resolve_configured_runtime_tools(context) -> list[Any]:
from yuxi.agents.mcp.service import get_enabled_mcp_tools
selected_tools = []
selected_tool_names: set[str] = set()
buildin_tools = {tool.name: tool for tool in get_tool_instances_by_category("buildin")}
for tool_name in getattr(context, "tools", None) or []:
if not isinstance(tool_name, str) or tool_name in selected_tool_names:
continue
tool = buildin_tools.get(tool_name)
if tool is None:
logger.warning(f"Configured buildin tool not found, skip: {tool_name}")
continue
selected_tools.append(tool)
selected_tool_names.add(tool_name)
selected_mcp_servers: set[str] = set()
for server_name in getattr(context, "mcps", None) or []:
if not isinstance(server_name, str) or server_name in selected_mcp_servers:
continue
selected_mcp_servers.add(server_name)
try:
mcp_tools = await get_enabled_mcp_tools(server_name)
except Exception as e:
logger.warning(f"Failed to load configured MCP tools '{server_name}': {e}")
continue
if not mcp_tools:
logger.warning(f"Configured MCP unavailable, skip: {server_name}")
continue
for tool in mcp_tools:
if tool.name in selected_tool_names:
continue
selected_tools.append(tool)
selected_tool_names.add(tool.name)
return selected_tools