Tool Calling 工程化
Tool Calling 的工程侧实现:从 Function Schema 定义到 Tool Registry、Tool Runtime、权限校验、参数验证、执行沙箱、结果归一化、错误处理的完整链路。
#type / concept
#status / evergreen
#tech / ai
#tech / architecture
[!info] related notes
- 所属 MOC: Tool Calling Engineering MOC, AI Agent Application MOC
- 模型侧: Function Calling — 模型如何输出 tool_call
- 运行时: Agent Runtime — 执行循环中的工具分发
- 协议: MCP (Model Context Protocol) — 工具协议标准
- 安全: Agent Guardrails — 工具调用的风险控制
Tool Calling 工程化
一句话定义
Tool Calling 工程化是把”模型输出一个函数名和参数”这件事变成一个可靠的工程系统的完整实践,包括 Schema 定义、工具注册、权限校验、参数验证、执行隔离、结果处理、错误恢复和审计日志。
它解决什么问题
Function Calling(见 Function Calling)解决了”模型如何表达想调用什么工具”的问题。但工程实践中还有大量问题:
- 工具在哪里注册?怎么发现?
- 谁有权限调用哪些工具?
- 参数不合法怎么办?
- 工具执行超时怎么办?
- 工具执行出错怎么恢复?
- 高风险操作(如删除、支付)要不要人类审批?
- 工具执行过程怎么记录和审计?
这些问题不在模型侧,而在工程侧。
核心原理
Tool Calling 的完整链路
模型输出 tool_call
│
▼
┌──────────────────────────┐
│ 1. Schema Validation │ 参数是否符合 JSON Schema
├──────────────────────────┤
│ 2. Permission Check │ 当前用户/Agent 是否有权调用
├──────────────────────────┤
│ 3. Risk Assessment │ 是否需要人类审批
├──────────────────────────┤
│ 4. Argument Enrichment │ 补充模型不知道的参数(如 user_id)
├──────────────────────────┤
│ 5. Execution │ 在沙箱或直接执行
├──────────────────────────┤
│ 6. Result Normalization │ 统一结果格式
├──────────────────────────┤
│ 7. Error Handling │ 超时、异常、降级
├──────────────────────────┤
│ 8. Audit Log │ 记录调用详情
├──────────────────────────┤
│ 9. Result to LLM │ 把结果回传给模型
└──────────────────────────┘
Tool Registry
Tool Registry 是工具的注册中心,负责:
class ToolRegistry:
def __init__(self):
self._tools: dict[str, Tool] = {}
def register(self, tool: Tool):
"""注册工具,包含 name、description、schema、handler"""
self._tools[tool.name] = tool
def get_schemas(self) -> list[dict]:
"""返回所有工具的 JSON Schema,用于传给 LLM"""
return [t.to_schema() for t in self._tools.values()]
def get(self, name: str) -> Tool:
"""按名称获取工具"""
return self._tools.get(name)
Tool 的完整定义
@dataclass
class Tool:
name: str # 工具名称
description: str # 给模型看的描述
parameters: dict # JSON Schema
handler: Callable # 执行函数
risk_level: str = "low" # low / medium / high
requires_approval: bool = False # 是否需要人类审批
timeout: int = 30 # 超时秒数
retry_policy: str = "none" # none / simple / exponential
idempotent: bool = False # 是否幂等
read_only: bool = True # 是否只读
在 React + Go + Python AI Service 架构中的位置
Tool Calling 跨越所有三层:
- React 前端: 展示工具调用状态(“正在搜索…”、“正在执行查询…”)、Human Approval UI
- Go 后端: 工具调用的权限校验、审计日志记录、结果持久化
- Python AI Service: Tool Registry、Tool Runtime、Tool Executor
React: ToolCallUI 展示调用状态
│
▼
Go: Permission Check → Audit Log
│
▼
Python: ToolRegistry → ToolExecutor → Result Normalization
典型工程实现
Python AI Service 侧
# 定义工具
tools = [
Tool(
name="query_sales_data",
description="查询指定时间范围内的销售数据",
parameters={
"type": "object",
"properties": {
"start_date": {"type": "string", "description": "开始日期"},
"end_date": {"type": "string", "description": "结束日期"},
},
"required": ["start_date", "end_date"]
},
handler=sales_query_handler,
risk_level="low",
read_only=True,
),
Tool(
name="delete_record",
description="删除指定记录",
parameters={...},
handler=delete_handler,
risk_level="high",
requires_approval=True,
read_only=False,
),
]
# 执行工具
async def execute_tool(tool_call, context):
tool = registry.get(tool_call.name)
# 参数验证
validated = validate_arguments(tool.parameters, tool_call.arguments)
# 权限检查
if not check_permission(context.user, tool):
return ToolResult(error="权限不足")
# 审批检查
if tool.requires_approval:
approval = await request_human_approval(tool_call, context)
if not approval.approved:
return ToolResult(error="用户拒绝执行")
# 执行(带超时)
try:
result = await asyncio.wait_for(
tool.handler(validated, context),
timeout=tool.timeout
)
return ToolResult(data=result)
except asyncio.TimeoutError:
return ToolResult(error="执行超时")
except Exception as e:
return ToolResult(error=str(e))
常见设计模式
1. Read-only vs Write 工具分离
只读工具(查询、搜索)不需要审批;写入工具(删除、支付)需要审批。
2. 风险等级分级
low: 只读查询,直接执行medium: 有副作用但可撤销,确认后执行high: 不可逆操作,必须人类审批
3. 工具描述即文档
工具的 description 是给模型看的”文档”,写得好坏直接影响模型选择工具的准确率。Anthropic 建议”像给初级开发者写文档一样写工具描述”。
4. 参数自动补全
模型不知道的参数(如当前 user_id、tenant_id)应该由运行时自动注入,而不是让模型猜。
常见坑
- 工具描述写得太模糊: “查询数据”比”查询指定时间范围内的销售数据,返回每日销售额和订单数”差很多
- 不做参数验证: 模型可能传入不合法的参数(如日期格式错误)
- 没有超时控制: 工具执行可能挂起,阻塞整个 Agent Loop
- 不做幂等处理: 重试可能导致重复执行(如重复扣款)
- 把所有工具都暴露给模型: 工具太多会降低选择准确率,应该按场景动态加载
和其他概念的关系
- vs Function Calling: Function Calling 是模型侧机制(模型如何输出 tool_call),Tool Calling 是工程侧实现(如何可靠执行)
- vs Tool Runtime: Runtime 是 Tool Calling 的执行引擎
- vs MCP: MCP 是工具的协议标准,Tool Calling 是工程实现
- vs Guardrails: Guardrails 挂在 Tool Calling 的权限检查和审批环节
总结
Tool Calling 工程化的核心是:模型只负责”说想调什么”,工程侧负责”能不能调、怎么调、调完怎么处理”。一个可靠的 Tool Calling 系统,比一个”聪明的模型”更重要。