349 lines
13 KiB
Python
349 lines
13 KiB
Python
|
|
"""Tools API Router
|
|||
|
|
|
|||
|
|
聚合两套工具体系的元数据:
|
|||
|
|
1. 注册层 (app/tools/) - YAML manifest 定义
|
|||
|
|
2. Agent 层 (app/agents/tools/) - @tool 装饰器定义
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import re
|
|||
|
|
import importlib
|
|||
|
|
|
|||
|
|
from fastapi import APIRouter, Depends
|
|||
|
|
|
|||
|
|
from app.routers.auth import get_current_user
|
|||
|
|
from app.models.user import User
|
|||
|
|
from app.schemas.tools import (
|
|||
|
|
ToolsResponse,
|
|||
|
|
ToolCategory,
|
|||
|
|
ToolSubgroup,
|
|||
|
|
ToolInfo,
|
|||
|
|
ToolCommand,
|
|||
|
|
ToolStats,
|
|||
|
|
ToolSummary,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
router = APIRouter(prefix="/api/tools", tags=["Tools"])
|
|||
|
|
|
|||
|
|
# ============================================================
|
|||
|
|
# 辅助函数
|
|||
|
|
# ============================================================
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _parse_command_from_docstring(docstring: str) -> dict:
|
|||
|
|
"""从函数的 docstring 解析参数信息"""
|
|||
|
|
params = {"type": "object", "properties": {}, "required": []}
|
|||
|
|
if not docstring:
|
|||
|
|
return params
|
|||
|
|
|
|||
|
|
# 简单解析 Args: 段落
|
|||
|
|
args_match = re.search(
|
|||
|
|
r"Args:\s*(.*?)(?=\n\s*(?:Returns?|Raises?)|$", docstring, re.DOTALL | re.IGNORECASE
|
|||
|
|
)
|
|||
|
|
if args_match:
|
|||
|
|
args_section = args_match.group(1)
|
|||
|
|
# 匹配形如 "arg_name (type): description" 的行
|
|||
|
|
for line in args_section.strip().split("\n"):
|
|||
|
|
line = line.strip()
|
|||
|
|
if not line:
|
|||
|
|
continue
|
|||
|
|
# 匹配: "name (type): description" 或 "name: description"
|
|||
|
|
m = re.match(r"(\w+)\s*(?:\(\s*(\w+)\s*\))?\s*:", line)
|
|||
|
|
if m:
|
|||
|
|
param_name = m.group(1)
|
|||
|
|
params["properties"][param_name] = {"type": "string", "description": line}
|
|||
|
|
params["required"].append(param_name)
|
|||
|
|
|
|||
|
|
return params
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _build_agent_tools() -> list[ToolInfo]:
|
|||
|
|
"""扫描 app/agents/tools/ 目录,内省 @tool 装饰器"""
|
|||
|
|
tools: list[ToolInfo] = []
|
|||
|
|
|
|||
|
|
# 分类映射:文件名 -> (分类名, 子分类名)
|
|||
|
|
category_map = {
|
|||
|
|
"search": ("Agent层", "知识检索"),
|
|||
|
|
"schedule": ("Agent层", "日程管理"),
|
|||
|
|
"task": ("Agent层", "任务管理"),
|
|||
|
|
"forum": ("Agent层", "论坛功能"),
|
|||
|
|
"time_reasoning": ("Agent层", "时间推理"),
|
|||
|
|
"builtins/file_tools": ("Agent层", "文件工具"),
|
|||
|
|
"builtins/system_tools": ("Agent层", "系统命令"),
|
|||
|
|
"builtins/dev_tools": ("Agent层", "开发工具"),
|
|||
|
|
"builtins/collaboration_tools": ("Agent层", "协作工具"),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 工具名称 -> 中文显示名
|
|||
|
|
display_names = {
|
|||
|
|
"search_knowledge": "知识库搜索",
|
|||
|
|
"get_knowledge_graph_context": "知识图谱查询",
|
|||
|
|
"build_knowledge_graph": "构建知识图谱",
|
|||
|
|
"hybrid_search": "混合搜索",
|
|||
|
|
"web_search": "联网搜索",
|
|||
|
|
"get_schedule_day": "获取日程",
|
|||
|
|
"create_todo": "创建待办",
|
|||
|
|
"create_schedule_task": "创建日程任务",
|
|||
|
|
"create_reminder": "创建提醒",
|
|||
|
|
"create_goal": "创建目标",
|
|||
|
|
"get_tasks": "获取任务列表",
|
|||
|
|
"create_task": "创建任务",
|
|||
|
|
"update_task_status": "更新任务状态",
|
|||
|
|
"get_forum_posts": "获取论坛帖子",
|
|||
|
|
"create_forum_post": "发布论坛帖子",
|
|||
|
|
"scan_forum_for_instructions": "扫描论坛指令",
|
|||
|
|
"resolve_time_expression": "解析时间表达式",
|
|||
|
|
"glob": "文件路径匹配",
|
|||
|
|
"grep": "文件内容搜索",
|
|||
|
|
"read_file": "读取文件",
|
|||
|
|
"write_file": "写入文件",
|
|||
|
|
"bash": "Bash命令",
|
|||
|
|
"powershell": "PowerShell命令",
|
|||
|
|
"git": "Git操作",
|
|||
|
|
"lsp_tools": "LSP代码导航",
|
|||
|
|
"team_agent": "团队Agent通信",
|
|||
|
|
"task_broadcast": "任务广播",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 工具描述
|
|||
|
|
descriptions = {
|
|||
|
|
"search_knowledge": "搜索用户的私人知识库,返回最相关的文档片段",
|
|||
|
|
"get_knowledge_graph_context": "获取用户知识图谱的上下文信息",
|
|||
|
|
"build_knowledge_graph": "从文档构建/更新知识图谱",
|
|||
|
|
"hybrid_search": "混合搜索,结合向量语义检索和关键词匹配",
|
|||
|
|
"web_search": "通过 SearxNG 搜索外部网页信息",
|
|||
|
|
"get_schedule_day": "获取指定日期的 todo/task/reminder/goal 聚合信息",
|
|||
|
|
"create_todo": "创建指定日期的待办",
|
|||
|
|
"create_schedule_task": "创建任务,支持优先级和截止日期",
|
|||
|
|
"create_reminder": "创建提醒,支持自然语言时间",
|
|||
|
|
"create_goal": "创建指定日期的目标",
|
|||
|
|
"get_tasks": "获取用户当前的任务列表",
|
|||
|
|
"create_task": "创建新任务",
|
|||
|
|
"update_task_status": "更新任务状态",
|
|||
|
|
"get_forum_posts": "获取论坛帖子列表",
|
|||
|
|
"create_forum_post": "在论坛发布新帖子",
|
|||
|
|
"scan_forum_for_instructions": "扫描论坛中的指令类帖子",
|
|||
|
|
"resolve_time_expression": "解析中文自然语言时间表达",
|
|||
|
|
"glob": "使用 glob 模式查找文件路径",
|
|||
|
|
"grep": "在文件中搜索匹配的文本行",
|
|||
|
|
"read_file": "读取文件内容",
|
|||
|
|
"write_file": "写入文件内容",
|
|||
|
|
"bash": "执行 Bash 命令",
|
|||
|
|
"powershell": "执行 PowerShell 命令",
|
|||
|
|
"git": "执行 Git 命令",
|
|||
|
|
"lsp_tools": "LSP 代码导航和查找引用",
|
|||
|
|
"team_agent": "向团队 Agent 发送消息或请求协作",
|
|||
|
|
"task_broadcast": "向多个 Agent 广播任务",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 需要扫描的模块
|
|||
|
|
modules_to_scan = [
|
|||
|
|
("app.agents.tools.search", "search"),
|
|||
|
|
("app.agents.tools.schedule", "schedule"),
|
|||
|
|
("app.agents.tools.task", "task"),
|
|||
|
|
("app.agents.tools.forum", "forum"),
|
|||
|
|
("app.agents.tools.time_reasoning", "time_reasoning"),
|
|||
|
|
("app.agents.tools.builtins.file_tools", "builtins/file_tools"),
|
|||
|
|
("app.agents.tools.builtins.system_tools", "builtins/system_tools"),
|
|||
|
|
("app.agents.tools.builtins.dev_tools", "builtins/dev_tools"),
|
|||
|
|
("app.agents.tools.builtins.collaboration_tools", "builtins/collaboration_tools"),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for module_name, category_key in modules_to_scan:
|
|||
|
|
try:
|
|||
|
|
mod = importlib.import_module(module_name)
|
|||
|
|
except ImportError:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 扫描模块中所有 @tool 装饰的函数
|
|||
|
|
for attr_name in dir(mod):
|
|||
|
|
if attr_name.startswith("_"):
|
|||
|
|
continue
|
|||
|
|
attr = getattr(mod, attr_name)
|
|||
|
|
# 检查是否是 langchain @tool 装饰的对象
|
|||
|
|
if hasattr(attr, "name") and hasattr(attr, "description"):
|
|||
|
|
tool_name = attr.name
|
|||
|
|
tool_desc = attr.description or ""
|
|||
|
|
# 清理 docstring 中的参数说明用于显示
|
|||
|
|
display_desc = re.sub(r"\s*Args:\s*.*", "", tool_desc, flags=re.DOTALL).strip()
|
|||
|
|
display_desc = re.sub(
|
|||
|
|
r"\s*Returns?:\s*.*", "", display_desc, flags=re.DOTALL
|
|||
|
|
).strip()
|
|||
|
|
|
|||
|
|
# 获取 category 和 subcategory
|
|||
|
|
cat_info = category_map.get(category_key, ("Agent层", category_key))
|
|||
|
|
category, subcategory = cat_info[0], cat_info[1]
|
|||
|
|
|
|||
|
|
# 获取参数 schema
|
|||
|
|
params_schema = getattr(attr, "args_schema", None)
|
|||
|
|
parameters = {}
|
|||
|
|
if params_schema:
|
|||
|
|
try:
|
|||
|
|
if hasattr(params_schema, "model_json_schema"):
|
|||
|
|
parameters = params_schema.model_json_schema()
|
|||
|
|
elif hasattr(params_schema, "schema"):
|
|||
|
|
parameters = params_schema.schema()
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
tool_info = ToolInfo(
|
|||
|
|
name=tool_name,
|
|||
|
|
display_name=display_names.get(tool_name, tool_name),
|
|||
|
|
description=descriptions.get(tool_name, display_desc or tool_desc),
|
|||
|
|
category=category,
|
|||
|
|
subcategory=subcategory,
|
|||
|
|
source="agent",
|
|||
|
|
source_file=module_name,
|
|||
|
|
tags=[],
|
|||
|
|
enabled=True,
|
|||
|
|
commands=[
|
|||
|
|
ToolCommand(
|
|||
|
|
name=tool_name,
|
|||
|
|
description=tool_desc or display_desc,
|
|||
|
|
parameters=parameters,
|
|||
|
|
)
|
|||
|
|
],
|
|||
|
|
stats=ToolStats(),
|
|||
|
|
)
|
|||
|
|
tools.append(tool_info)
|
|||
|
|
|
|||
|
|
return tools
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _build_manifest_tools() -> list[ToolInfo]:
|
|||
|
|
"""从 YAML manifest 构建工具信息"""
|
|||
|
|
tools: list[ToolInfo] = []
|
|||
|
|
|
|||
|
|
# manifest 文件 -> 分类映射
|
|||
|
|
manifest_map = {
|
|||
|
|
"file_operator": (
|
|||
|
|
"注册层",
|
|||
|
|
"文件操作",
|
|||
|
|
[
|
|||
|
|
ToolCommand(name="read_file", description="读取指定路径的文件内容"),
|
|||
|
|
ToolCommand(name="write_file", description="将内容写入文件"),
|
|||
|
|
ToolCommand(name="list_directory", description="列出目录内容"),
|
|||
|
|
ToolCommand(name="search_files", description="递归搜索匹配模式的文件"),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
"task_manager": (
|
|||
|
|
"注册层",
|
|||
|
|
"任务管理",
|
|||
|
|
[
|
|||
|
|
ToolCommand(name="create_task", description="创建新任务"),
|
|||
|
|
ToolCommand(name="list_tasks", description="列出任务"),
|
|||
|
|
ToolCommand(name="get_task", description="获取任务详情"),
|
|||
|
|
ToolCommand(name="complete_task", description="标记任务完成"),
|
|||
|
|
ToolCommand(name="fail_task", description="标记任务失败"),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
"web_fetch": (
|
|||
|
|
"注册层",
|
|||
|
|
"网页抓取",
|
|||
|
|
[
|
|||
|
|
ToolCommand(name="fetch", description="抓取网页内容"),
|
|||
|
|
ToolCommand(name="screenshot", description="截取网页截图"),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
"web_search": (
|
|||
|
|
"注册层",
|
|||
|
|
"联网搜索",
|
|||
|
|
[
|
|||
|
|
ToolCommand(name="search", description="执行语义级搜索"),
|
|||
|
|
ToolCommand(name="deep_search", description="深度搜索,带摘要生成"),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
manifest_descriptions = {
|
|||
|
|
"file_operator": "强大的文件系统操作工具,支持读写、搜索、下载等功能",
|
|||
|
|
"task_manager": "任务创建、查询、更新和状态管理",
|
|||
|
|
"web_fetch": "网页内容抓取工具,支持 HTML 解析、截图等功能",
|
|||
|
|
"web_search": "语义级并发搜索引擎,支持多源搜索和结果聚合",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for tool_name, (category, subcategory, commands) in manifest_map.items():
|
|||
|
|
tool_info = ToolInfo(
|
|||
|
|
name=tool_name,
|
|||
|
|
display_name=subcategory,
|
|||
|
|
description=manifest_descriptions.get(tool_name, ""),
|
|||
|
|
category=category,
|
|||
|
|
subcategory=subcategory,
|
|||
|
|
source="manifest",
|
|||
|
|
source_file=f"app/tools/manifests/{tool_name}.yaml",
|
|||
|
|
tags=[],
|
|||
|
|
enabled=True,
|
|||
|
|
commands=commands,
|
|||
|
|
stats=ToolStats(),
|
|||
|
|
)
|
|||
|
|
tools.append(tool_info)
|
|||
|
|
|
|||
|
|
return tools
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ============================================================
|
|||
|
|
# 路由
|
|||
|
|
# ============================================================
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("", response_model=ToolsResponse)
|
|||
|
|
async def list_tools(
|
|||
|
|
current_user: User = Depends(get_current_user),
|
|||
|
|
):
|
|||
|
|
"""获取所有内置工具列表(只读)"""
|
|||
|
|
# 构建工具列表
|
|||
|
|
manifest_tools = _build_manifest_tools()
|
|||
|
|
agent_tools = _build_agent_tools()
|
|||
|
|
|
|||
|
|
all_tools = manifest_tools + agent_tools
|
|||
|
|
|
|||
|
|
# 按 category 和 subcategory 分组
|
|||
|
|
category_map: dict[str, dict[str, list[ToolInfo]]] = {
|
|||
|
|
"注册层": {},
|
|||
|
|
"Agent层": {},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for tool in all_tools:
|
|||
|
|
cat = tool.category
|
|||
|
|
subcat = tool.subcategory
|
|||
|
|
if cat not in category_map:
|
|||
|
|
category_map[cat] = {}
|
|||
|
|
if subcat not in category_map[cat]:
|
|||
|
|
category_map[cat][subcat] = []
|
|||
|
|
category_map[cat][subcat].append(tool)
|
|||
|
|
|
|||
|
|
# 构建响应
|
|||
|
|
categories = []
|
|||
|
|
for cat_name, subgroups_dict in category_map.items():
|
|||
|
|
if not subgroups_dict:
|
|||
|
|
continue
|
|||
|
|
subgroups = []
|
|||
|
|
for subcat_name, tools_list in subgroups_dict.items():
|
|||
|
|
subgroups.append(
|
|||
|
|
ToolSubgroup(
|
|||
|
|
name=subcat_name,
|
|||
|
|
display_name=subcat_name,
|
|||
|
|
tools=tools_list,
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
categories.append(
|
|||
|
|
ToolCategory(
|
|||
|
|
name=cat_name,
|
|||
|
|
display_name=cat_name,
|
|||
|
|
subgroups=subgroups,
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 计算摘要
|
|||
|
|
total_commands = sum(len(t.commands) for t in all_tools)
|
|||
|
|
active_commands = sum(len(t.commands) for t in all_tools if t.enabled)
|
|||
|
|
|
|||
|
|
summary = ToolSummary(
|
|||
|
|
total_commands=total_commands,
|
|||
|
|
active_commands=active_commands,
|
|||
|
|
total_tools=len(all_tools),
|
|||
|
|
manifest_tools=len(manifest_tools),
|
|||
|
|
agent_tools=len(agent_tools),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return ToolsResponse(categories=categories, summary=summary)
|