一、最小闭环
用户提问
→ 模型决定:直接回答 / 调用工具
→ 执行工具,把结果回填给模型
→ 模型再决定:继续调用 / 给最终答案
这就是 function calling(工具调用)的基本机制。模型本身不执行代码,它只输出「我要调用哪个工具、参数是什么」,由你的程序去执行。
二、工具定义长什么样
{
"name": "search_orders",
"description": "按用户 ID 查询订单列表,最多返回 20 条",
"parameters": {
"type": "object",
"properties": {
"user_id": {"type": "string", "description": "用户 ID,纯数字"},
"status": {"type": "string", "enum": ["paid", "shipped", "all"]}
},
"required": ["user_id"]
}
}
三个要点:
- description 决定模型会不会用对。写清「什么时候该用、什么时候不该用」。
- 参数用 enum 限制取值,比让模型自由发挥可靠得多。
- 工具粒度不要太细也不要太粗。太细会导致调用次数爆炸,太粗则参数复杂、容易传错。
三、循环要有限制
没有上限的循环 = 没有上限的成本与风险。
for step in range(MAX_STEPS): # 例如 6 步
resp = model(messages, tools=tools)
if not resp.tool_calls:
return resp.content
for call in resp.tool_calls:
result = run_tool(call.name, call.args)
messages.append(tool_result(call.id, result))
return fallback_answer() # 超步数兜底
同时要限制:
- 总 token 预算
- 总耗时
- 单个工具调用次数
- 单次工具返回大小(大结果要截断或摘要)
四、失败处理
工具会失败,模型也会「编造」参数。必须做的:
- 参数校验:类型、范围、必填,不合规直接返回错误信息给模型,让它重试(而不是抛异常中断)。
- 错误信息要可读:
{"error": "user_id 必须是纯数字"}比500 Internal Error有用得多。 - 幂等:写操作要能防重复(用请求 ID 去重)。
- 超时:每个工具设独立超时,别让一个慢接口拖死整个循环。
五、什么该人工确认
高风险操作必须加确认环节:
- 付款、转账、下单
- 删除数据、发邮件、发消息
- 修改权限、发布内容
做法:模型只输出「建议执行的动作」,由用户点确认后才真正执行。这不是技术限制,而是产品设计——让 Agent 有能力,但把最终决定权留给人。
六、什么时候不该用 Agent
- 流程固定的任务:直接写代码更快、更便宜、更可靠。用 Agent 反而增加不确定性。
- 要求 100% 准确的场景:如财务对账、合规审核,模型只能做辅助。
- 延迟敏感:多轮调用意味着秒级甚至十秒级延迟。
- 工具本身不稳定:Agent 会把工具的不稳定放大成「行为不可预测」。
判断标准:任务是否需要「根据中间结果动态决定下一步」。是,才考虑 Agent;否,用工作流。
七、可观测性
上线后必须能回答:
- 这次任务调用了哪些工具、参数是什么、返回了什么?
- 在哪一步失败的?
- 花了多少 token、多少时间?
所以要记录完整的消息轨迹(trace),并按任务 ID 串联。没有轨迹的 Agent 出问题基本无法排查。
八、评估
- 任务成功率:给一批真实任务,看端到端是否完成。
- 步数分布:平均几步、有没有异常长的。
- 工具错误率:哪些工具最常失败(往往是参数设计问题)。
- 成本:单任务平均 token。
小结
Agent = 模型 + 工具 + 循环;工具描述决定用对与否,参数用 enum 约束;循环必须设步数、token、时间上限;高风险操作要人工确认;流程固定的任务用工作流而不是 Agent。