Tool Calling 工程化

Tool Calling 的工程侧实现:从 Function Schema 定义到 Tool Registry、Tool Runtime、权限校验、参数验证、执行沙箱、结果归一化、错误处理的完整链路。

#type / concept #status / evergreen #tech / ai #tech / architecture

[!info] related notes

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)应该由运行时自动注入,而不是让模型猜。

常见坑

  1. 工具描述写得太模糊: “查询数据”比”查询指定时间范围内的销售数据,返回每日销售额和订单数”差很多
  2. 不做参数验证: 模型可能传入不合法的参数(如日期格式错误)
  3. 没有超时控制: 工具执行可能挂起,阻塞整个 Agent Loop
  4. 不做幂等处理: 重试可能导致重复执行(如重复扣款)
  5. 把所有工具都暴露给模型: 工具太多会降低选择准确率,应该按场景动态加载

和其他概念的关系

  • 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 系统,比一个”聪明的模型”更重要。

参考资料

创建于 2026/6/30 更新于 2026/7/15