让 AI 真正做事:工具调用的设计方法
工具调用是 Agent 的执行底座。读完你会知道:工具该怎么描述、多个工具如何编排、哪些操作必须拦一道、以及为什么"description 写得好不好"直接决定成功率。
Function Calling 协议长什么样
工具调用(业界常叫 Function Calling 或 Tool Use)的本质,是把"能力"用结构化 schema 描述出来,让模型只负责生成合法的调用请求,真正的执行交给你的程序。模型输出的是一段 JSON,而不是去真的干这件事。
一个工具定义通常包含:name(名字)、description(干什么用)、parameters(用 JSON Schema 描述参数名、类型、是否必填、取值范围)。模型看到这些,就能在合适的时候吐出 {"name": "query_order", "arguments": {"order_id": "8842"}}。注意:模型只给请求,你拿到后校验、执行、再把结果喂回去——这是安全边界的根本来源。
{
"name": "query_order",
"description": "按订单号查询订单的当前状态与金额,只读,不产生副作用",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "订单号,如 8842"}
},
"required": ["order_id"]
}
}并行调用与依赖编排
现代模型支持一次返回多个 tool_call。当几个子任务彼此独立(查天气、查汇率、查库存),应当并行执行以压低延迟;而当 B 依赖 A 的返回(先查订单号再查物流),就必须串行,等 A 的结果回灌上下文后再发起 B。
沙箱与最小权限原则
工具执行的"程序侧"必须被管住。核心原则是最小权限(Least Privilege):每个工具只拥有完成它那件事所需的最小能力,并且运行在沙箱里——限网络、限文件系统、限可调用资源。模型请求的 order_id 即便被注入恶意内容,沙箱也拦得住它越界访问。
tool_call→Schema 校验
+参数清洗→权限层
(只读自动/写需确认)→沙箱执行→审计日志
+结果回灌
getattr(tools, call.name) dispatch,模型就可能拼出一个你没打算开放的函数名。务必用白名单映射:模型给的名字只在你显式登记过的表里查,查不到直接报错。错误处理、重试与人类确认
工具会失败:超时、参数非法、下游 500、限流。设计上要做到三点:
- 可理解的报错:把失败原因作为 observation 回灌,让模型有机会换参数重试,而不是让它凭空编一个结果。
- 重试与退避:对瞬时错误(限流、网络抖动)做有限次指数退避重试;对确定性错误(参数非法)不要重试,直接返回。
- HITL(Human-in-the-Loop):凡是带副作用且不可逆的操作(发邮件、改库、扣款、调用外部付费 API),默认暂停等人确认。确认通过才真正执行,且要保证幂等——同一请求重复提交不会重复扣款。
def dispatch(call):
if call.name not in REGISTRY: # 白名单,杜绝未登记函数
return error("unknown tool")
tool = REGISTRY[call.name]
args = tool.schema.validate(call.arguments) # 参数校验,失败即报错
if tool.side_effect and not call.human_approved:
return need_confirmation(call) # 危险操作:先等人
with sandbox(tool.permissions): # 最小权限沙箱
return tool.run(args, idempotency_key=call.id)工具 description 直接决定成功率
这是最容易被低估的一点:模型靠 description 判断"什么时候该用这个工具、传什么参数"。写得含糊,模型就乱用或不用。好的 description 要交代:做什么、何时用、何时不用、参数怎么填、返回值含义、有无副作用。
| 写法 | 示例 | 结果 |
|---|---|---|
| 差 | "获取用户信息" | 模型分不清要不要带 token、返回什么,常漏参数 |
| 好 | "按 user_id 查询用户资料(昵称、等级),只读;需 user_id,无则先问用户" | 模型知道何时调用、必填什么、不会瞎编 |
从只读工具起步的安全实践
落地建议非常明确:先只做只读工具。天气、文档检索、数据库查询、知识库搜索——这些无副作用,可以放心自动执行,用来把"调用链路 + 校验 + 日志"跑顺。等这套闭环稳了,再逐步开放带确认的写操作。下面把上一节的闭环扩展成"只读自动、写需确认"的落地姿态:
(自动执行)→链路稳定
校验/日志/重试→写操作
(HITL 确认)→幂等+审计
才放行
工具调用把"AI 能想"和"程序能做"清晰地切开:模型只产出结构化的调用意图,真正的执行、权限与后果都由你的代码掌握。把 description 写清楚、把危险操作拦一道、把失败变成可纠正的 observation,Agent 才不至于失控——而这一切,正是上层 MCP 与 Skill 能复用的执行底座。