开发一个生产级 Agent 的最佳实践
做 Agent 开发其实很容易让模型“动起来”:接一个搜索接口,再接一个数据库,屏幕上开始滚动 Thought、Action、Observation,看上去就像系统真的会思考了。
可一旦把它接进真实业务,画风很快就变了。
物流接口偶发超时,模型连续重试十几次;上下文越积越长,答案开始前后矛盾;某个工具返回格式稍微变了一下,整条任务链直接卡死。更麻烦的是,排查时你只看到一句“任务执行失败”,根本不知道问题发生在模型、工具,还是某个子 Agent。
这时候才会发现:能跑通一次的 Agent,只是 Demo;能够长期稳定地替人干活,才算系统。
真正难的部分通常不在 Prompt,而在传统软件工程与大模型不确定性的交界处。业务拆解、工具契约、容错、上下文管理、评估、发布和可观测性,缺一块,线上迟早会补课。
一、先别写 Prompt,把业务里的“决策”拆出来
业务方很少会给你一份适合直接编码的需求。他们更可能说:“把客服成本降下来”“让供应链响应更快”“把合同审核自动化”。这些是方向,不是系统边界。
如果听完需求就开始选模型、搭工作流,大概率会做出一个演示效果不错、但没人敢真正使用的东西。正确的起点,是把业务目标翻译成决策链路。
以智能客服为例,表面任务是“回复用户”,实际至少包含意图识别、信息查询、规则判断、方案生成和动作执行。查订单和解释规则风险很低,改收货地址风险更高,退款或赔付则可能直接造成资金损失。它们不应该走同一条自动化路径。
我更习惯在设计阶段先回答三个问题:这个动作如果做错了,损失有多大;结果是否可逆;系统有多大把握知道自己做对了。
据此可以把动作分成三个区域。低风险、高置信度的任务自动执行;信息不足或置信度较低的任务先补充信息;高风险、不可逆的任务必须暂停,保留当前状态,转人工审批。人工确认后,流程再从断点恢复,而不是从头重跑。
这里的人并不是 Agent 失败后的“兜底客服”,而是系统里正式的一环。一个靠谱的人机协同流程,至少要保存任务上下文、工具调用结果、待审批动作、风险原因和恢复位置。否则人工拿到的只是一张“请处理”的工单,还得重新调查一遍,所谓提效也就不存在了。
二、工具调用不是“让模型猜参数”
Agent 最终还是要通过 API、数据库和内部服务完成动作。工具定义如果写得含糊,模型就只能靠猜。
一个常见的坏例子是:
工具:update_order 说明:更新订单信息 参数:order_id, data对程序员来说,这个定义都不够用,更别说模型。data里能写哪些字段,金额用元还是分,地址是否允许为空,重复调用会不会重复扣款,工具失败后返回什么,都没有讲清楚。
更稳妥的做法,是把工具当成一份严格的接口契约。输入用 Schema 约束,枚举值写全,必填项和默认值明确,示例覆盖正常与异常分支;输出则使用稳定的结构化格式,不要把“成功”“部分成功”“稍后重试”都塞进一段自然语言。
{"status":"retryable_error","error_code":"UPSTREAM_TIMEOUT","message":"物流服务在 2 秒内未响应","retry_after_ms":1000}复杂场景里,仅有说明书还不够。可以为模型提供少量经过挑选的调用样例,特别是容易混淆的边界情况。不过,示例的作用是帮助模型理解契约,不是代替契约。真正的安全边界仍然要由代码完成:参数校验、权限校验、幂等键、速率限制,以及执行前后的业务规则检查。
有一条经验很朴素:凡是不能接受模型做错的事情,就不要只靠 Prompt 约束。
三、失败不是异常,而是正常分支
生产环境里的第三方服务一定会超时,数据库连接一定会抖动,模型也一定会偶尔返回无法解析的内容。区别只在于它什么时候发生。
因此,工具调用链不该只有“成功”和“报错退出”两种状态。至少要区分可重试错误、不可重试错误、需要人工介入,以及可以降级的错误。
对网络抖动、限流等短暂故障,可以采用带抖动的指数退避:
delay = min(base * 2^attempt + random_jitter, max_delay)这里的随机抖动很重要。没有它,大量请求会在同一时间失败,又在同一时间重试,形成新的流量尖峰。重试还必须有上限,并配合总超时预算。一个已经耗时 20 秒的任务,没有必要再机械地重试五轮。
更关键的是,并非所有调用都能重试。查询库存可以,创建支付、发送优惠券、提交合同则必须先保证幂等。否则用户只点了一次,系统可能执行三次。
当重试耗尽后,系统需要进入可解释的降级路径。例如实时物流查不到时,返回最近一次同步状态并提示稍后刷新;主模型不可用时,切换到能力较弱但稳定的备用模型;自动决策无法继续时,生成带完整上下文的人工工单。降级不是随便回一句“系统繁忙”,而是在能力下降时,仍然把核心业务维持在可接受范围内。
熔断也不能省。若某个下游在短时间内持续失败,继续调用只会拖垮自己和对方。达到阈值后应暂时切断请求,进入半开状态做小流量探测,确认恢复后再逐步放量。
四、Agent 循环必须有刹车
ReAct 一类“思考—行动—观察”循环很适合处理开放任务,但它也带来了一个传统服务里不常见的问题:系统可能非常认真地做无用功。
模型会在两个工具之间反复横跳,会用不同措辞重复同一个查询,也可能因为拿不到理想结果而持续调用付费接口。如果没有限制,一次普通请求就能跑出几十轮推理和一张离谱的账单。
所以每个任务都应该有硬边界,包括最大执行步数、最大工具调用次数、最大 Token 消耗、最大费用和墙钟时间。还可以记录最近若干次动作的指纹,当相同或高度相似的调用持续出现时,直接判定为循环。
复杂任务则应该先拆成有明确完成条件的子任务。每个子任务都需要输入、预期输出、允许使用的工具和失败策略。不要让模型只拿着一句宏大目标,在十几轮调用后自己回忆“我最初要干什么”。
这类限制看起来不够“智能”,却是系统敢于上线的前提。好的 Agent 不是永远不犯错,而是犯错时知道停在哪里。
五、多 Agent 的重点不是数量,而是边界
单 Agent 能解决的问题,没必要拆成五个角色开会。多 Agent 架构真正有价值的地方,是隔离权限、上下文和专业能力。
例如,调度 Agent 负责理解目标和分配任务;库存 Agent 只能读取与锁定库存;财务 Agent 可以计算金额,但付款动作需要单独授权。这样做的好处不是界面上出现更多头像,而是每个角色的责任和权限都更容易审计。
多 Agent 系统最容易失控的是上下文。若所有角色都携带完整历史消息,Token 成本会迅速增长,模型也会被无关信息干扰。更合理的方式是分层保存信息:原始事件写入可追溯存储;当前任务只保留最近窗口;长期信息压缩成结构化摘要;子 Agent 仅获取完成当前任务所需的最小上下文。
摘要不能只是一段“聊了什么”,最好包含已确认事实、未决问题、关键约束、已执行动作和证据引用。重要数据仍应指向原始记录,避免摘要在多轮压缩后逐渐失真。
换句话说,上下文管理不只是省 Token,它还是一种权限控制和认知降噪。
六、没有评估集,就没有“效果变好了”
Agent 的输出有随机性,开发者手动试几条 Case,感觉回答顺了,并不能证明版本可以上线。
评估应该从业务任务出发。通用指标通常包括端到端任务完成率、工具调用成功率、人工接管率、平均成本和延迟分位数。P50 能反映日常体验,P95、P99 更能暴露高并发和长链路下的问题。具体阈值不能照抄别人的数字,要根据业务损失、下游能力和用户预期来定。
离线评估集也不能只放“标准问题”。真正有价值的 Case 往往来自线上事故、用户的奇怪表达、工具超时、空数据、权限不足,以及历史上模型最容易误判的边界条件。每次出现新故障,都应该沉淀成回归样本。
对于自然语言质量,可以使用规则、程序化校验和 LLM-as-a-Judge 组合评估。金额、日期、字段完整性适合代码判断;语气、相关性和解释质量可以交给评审模型。但评审模型同样会漂移,因此需要固定版本、固定评分标准,并定期用人工标注集校准。
最终我们关心的不是“模型说得像不像人”,而是它有没有把事情办成、有没有越权,以及代价是否可控。
七、发布新版本时,默认它可能有问题
模型、Prompt、工具描述和工作流中的任何一个变化,都可能改变系统行为。传统单元测试依然重要,但它覆盖不了所有语义变化。
一次完整的发布流程应该同时包含确定性测试和语义回归。前者检查 Schema、权限、幂等、状态迁移和错误分支;后者用固定评估集比较新旧版本在任务成功率、风险违规率、延迟和成本上的变化。
通过测试也不等于直接全量。更稳妥的方式是先让新版本承接很小一部分真实流量,或先跑影子流量,再根据指标逐步扩大。监控到任务失败率、工具异常率、成本或延迟显著恶化时,系统应自动停止放量并回滚。
这里最怕的是只能回滚代码,不能回滚配置。Prompt、模型版本、工具 Schema、路由规则和评估集都应该版本化,并且能准确回答:某次请求到底使用了哪一套组合。
八、最后补上可观测性,否则故障只能靠猜
一条 Agent 请求可能经过调度、多个模型、几个子 Agent 和一串外部工具。只记录入口和最终结果,几乎无法排障。
每个请求都需要唯一的 Trace ID,并贯穿模型调用、工具调用、队列任务和人工审批。每个 Span 至少记录耗时、状态、重试次数、Token、费用、模型与 Prompt 版本、工具参数摘要和错误码。涉及隐私的数据应脱敏,但不能因为担心日志泄漏,就干脆什么都不记。
真正有用的告警也不是“出现了一次错误”,而是能表达趋势:某工具五分钟错误率超过基线,某版本的任务完成率显著下降,单任务平均调用次数突然翻倍,或者人工接管率持续上升。
当这些数据能串起来,排障才从“翻日志碰运气”变成回答具体问题:慢在哪一步,失败集中在哪个版本,哪类输入最容易触发,以及降级是否真的生效。
结语:Agent 工程,最后还是工程
做出一个会调用工具的 Agent 并不难。难的是让它在接口抖动、模型漂移、流量突增和需求变化中,仍然完成该完成的任务;做不了时安全停下;出了问题能快速定位;发新版本时不把所有用户一起拉来冒险。
这背后没有某个神奇框架能一键解决。它依赖的是一组不算新、却很容易被忽略的工程能力:把模糊目标拆成可执行流程,为工具建立严格契约,把失败设计成正常分支,限制失控循环,管理上下文,用数据评估效果,再通过灰度和追踪把风险收进笼子里。
如果一个 Agent 只能在演示时聪明,它还不算真正可用。真正的生产级系统,往往显得没那么“炫”:该重试时重试,该降级时降级,该找人时找人,该停下时绝不硬撑。
但也正是这种克制,才让智能真正进入业务。
