LLM 工具调用工程化:让 Function Calling 稳定、幂等、可恢复
"Function Calling 的难点不是让模型返回函数名,而是处理选错工具、参数漂移、重复执行、超时与部分成功。本文给出工具契约、执行器、幂等、重试和确认机制的完整设计。"
本文要点
- 主题聚焦:大模型、AI Agent
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
给模型注册一个函数并成功调用,是 Agent demo 最有成就感的时刻。生产环境真正困难的部分却从这里才开始:模型会选错工具,会漏参数,会把字符串填进数字字段;网络会超时,工具可能已经执行成功但响应丢失;用户刷新页面后,同一个写操作可能再次提交。
可靠的工具调用不是模型能力,而是一套执行系统能力。
一、把工具定义当成公共 API
工具名称、描述和参数 schema 共同决定模型如何使用它。模糊描述会迫使模型猜测,过大的工具则让权限和错误处理失控。
一个好的工具应满足:
- 名称表达单一动作,例如
create_invoice_draft,而不是manage_invoice。 - 描述说明何时使用、何时不要使用,以及关键前置条件。
- 参数使用明确类型、枚举、范围和格式。
- 返回稳定的结构化结果与机器可读错误码。
- 读操作和写操作分离,高风险动作独立建模。
const CreateDraftSchema = z.object({
customerId: z.string().min(1),
currency: z.enum(["CNY", "USD", "EUR"]),
lines: z.array(z.object({
sku: z.string().min(1),
quantity: z.number().int().positive(),
})).min(1).max(100),
idempotencyKey: z.string().uuid(),
});
不要把鉴权字段交给模型填写。userId、tenantId 和权限范围应该从可信会话注入执行上下文。
二、执行器要独立于模型循环
模型只负责提出“想调用什么”,执行器负责判断“能不能调用、怎样调用”。一个可靠执行器通常经过以下阶段:
- 解析并验证工具名称与参数。
- 根据当前身份执行权限策略。
- 判断是否需要人工确认。
- 生成幂等键并检查历史执行。
- 设置超时、并发和资源限制后调用。
- 规范化结果或错误。
- 写入审计事件,再把安全摘要返回模型。
工具实现不应直接接收整段对话或 system prompt。它只接收验证后的业务参数与可信执行上下文,减少无关数据进入敏感服务。
三、幂等是写操作的生命线
考虑“创建工单”工具:下游已经创建成功,但响应返回前网络断开。Agent 看见超时后重试,如果没有幂等机制,就会生成两张工单。
幂等键应绑定稳定业务意图,而不是每次重试重新生成。可以由上层任务 ID、步骤 ID 与规范化参数哈希组成。服务端保存幂等键、请求摘要、执行状态和结果引用。
状态至少区分:
PENDING:正在执行,重复请求等待或返回处理中。SUCCEEDED:返回第一次成功结果,不重复执行。FAILED_RETRYABLE:允许按策略重试。FAILED_FINAL:参数或权限错误,不应重试。UNKNOWN:无法确认下游是否成功,需要查询或人工处理。
“超时”不一定等于失败。对于支付、发消息、创建资源等动作,先通过查询接口确认结果,再决定是否重试。
四、错误必须帮助模型恢复
把数据库堆栈直接返回给模型既泄露内部信息,也无法指导下一步。执行器应把错误规范化为稳定类型:
{
"ok": false,
"error": {
"code": "CUSTOMER_NOT_FOUND",
"message": "No customer matched the supplied customerId.",
"retryable": false,
"suggestedAction": "Search the customer before creating a draft."
}
}
参数校验失败可以把具体字段问题返回模型,让它修正一次;权限不足应停止并向用户说明;限流与临时网络错误可以指数退避;业务冲突则需要重新获取最新状态。
不要无限自动重试。限制每个步骤和整个任务的重试次数,并记录错误指纹。相同错误连续出现时,应结束循环或转人工。
五、并行调用要分析依赖
模型可能一次提出多个工具调用。只读且互不依赖的查询可以并行,例如同时读取订单和用户资料;“创建资源后更新资源”存在明确依赖,必须串行。
执行器不应仅按模型返回顺序盲目并发。可以让模型输出步骤依赖,也可以由工作流代码固定 DAG。写操作并行时还要考虑事务边界、锁和补偿逻辑。
如果一组操作部分成功,系统必须知道如何恢复。对可逆动作设计补偿操作;对不可逆动作记录完成点,并让后续步骤从该点继续,而不是整组重跑。
六、人工确认要绑定具体动作
高风险工具不应只弹一个模糊的“是否允许 Agent 操作”。确认界面应展示:工具名称、目标资源、关键参数、影响范围、费用或不可逆后果。
确认 token 要绑定规范化参数哈希和短有效期。用户确认“向 A 发送草稿”后,模型如果把收件人改成 B,旧确认必须失效。确认结果也应进入审计日志。
低风险、可逆、高频的动作可以根据策略自动批准,但仍需限额。权限设计不是“全部自动”与“每步都问”二选一,而是根据风险分层。
七、可观测性与评测
每次调用记录工具版本、参数摘要、授权结果、确认结果、幂等键、耗时、重试、下游请求 ID 和最终状态。敏感参数只记录脱敏值或哈希。
离线评测不仅要问模型是否选对工具,还要验证:参数是否满足 schema、是否调用了不必要工具、调用顺序是否正确、失败后是否采用合理恢复路径、是否突破预算与权限。
线上重点观察工具成功率、参数校验失败率、平均调用数、重复调用率、人工拒绝率和未知状态数量。调用成功率很高但平均每个任务多调用三次,同样意味着描述或规划存在问题。
八、一个务实的上线顺序
先从只读工具开始,建立 schema、审计和错误规范;再接入可逆写操作,加入幂等与预览;最后才开放不可逆操作,并配置人工确认、限额和应急停用开关。
每新增一个工具都要回答:它能访问哪些数据、最坏影响是什么、重复执行会怎样、超时后如何确认、谁可以批准、如何撤销。答不清这些问题,就不应该交给 Agent 自动调用。
小结
Function Calling 只是模型输出的一种格式,可靠工具调用则是一套完整的分布式执行协议。清晰契约减少模型猜测,服务端授权守住边界,幂等避免重复副作用,错误分类帮助恢复,确认机制控制高风险动作。把这些能力放进独立执行器后,Agent 才有资格从演示走向生产。
Related