feat(temple): add Temple modal with Tools browser and Skills management
This commit is contained in:
348
backend/app/routers/tools.py
Normal file
348
backend/app/routers/tools.py
Normal file
@@ -0,0 +1,348 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user