做过 Agent 的人大概都有过这种体验周五下午搭出来的原型演示时一气呵成自己查资料、调工具、写报告老板看完很满意。结果接到真实流量两周后线上一地鸡毛——它会在第 8 步把前面的结论忘掉会拿着一个根本不存在的字段去调接口会陷进「调用失败→重试→换个错误姿势再失败」的死循环里出不来。Demo 和生产之间隔着的不是模型能力而是一整套可靠性工程。这篇文章想把这道坎拆开讲清楚生产环境里 Agent 主要死在哪几个地方以及对应的工程对策长什么样。一、先认清 Agent 的本质它是一个有状态的控制循环抛开各种花哨的封装绝大多数 Agent 的核心就是一个循环pythondefagent_loop(task,tools,max_steps20):contextinit_context(task)forstepinrange(max_steps):actionllm_decide(context,tools)# 模型决定下一步ifaction.typefinish:returnaction.answer resultexecute_tool(action)# 执行工具contextupdate_context(context,action,result)raiseStepLimitExceeded()# 兜底防止无限循环短短十几行但生产环境里的几乎所有问题都藏在这四个函数里。下面逐个看。二、失败模式一上下文腐烂Context Rot长任务的 Agent 跑到后期质量会肉眼可见地下降。原因不是模型变笨了而是context这个变量越滚越脏早期的工具返回了一大段 JSON、中间有几次失败的报错堆栈、还有模型自己的一堆碎碎念全堆在上下文里。等到第 15 步真正重要的任务目标已经被淹没在噪声里。很多人以为上下文窗口越大越好其实恰恰相反——窗口大小是容量不是注意力。塞进去 100K token 不代表模型能均匀地用好这 100K。工程上的对策是主动做上下文压缩compaction而不是被动地累积pythondefupdate_context(context,action,result):context.history.append((action,result))# 工具返回过大时先摘要再入栈原文落盘存档iftoken_len(result)RAW_LIMIT:result_refstore_blob(result)# 原始结果外置resultsummarize(result,focuscontext.task)context.history[-1](action,result,result_ref)# 历史过长时把早期步骤压成一段进度摘要iftoken_len(context.history)CTX_LIMIT:context.historycompact(context.history)returncontext这里有个值得反复强调的原则状态要外置。Agent 的「记忆」不该全靠把文本堆在上下文里硬扛而应该把结构化状态已完成的子任务、关键中间结论、待办项放到上下文之外的存储里每一步只把当前真正需要的那部分喂给模型。上下文窗口要当成 CPU 的寄存器用不是当硬盘用。三、失败模式二工具调用的脆弱性这是生产环境里翻车最频繁的地方。模型生成的工具调用参数和工具真实需要的 schema 之间永远存在偏差日期格式不对、把字符串当数字传、引用了一个上文里压根没出现过的 ID。Demo 阶段你测的那几条 happy path 永远碰不到这些。朴素的写法是出错就把异常抛给模型让它自己改。但这会触发第三种、也是最致命的失败模式先按下不表。这里先说工具层自己该做的事——把校验做在执行之前pythondefexecute_tool(action):tooltools[action.name]# 1. 参数 schema 校验错误信息要可读能指导模型纠正ok,errvalidate_args(action.args,tool.schema)ifnotok:returnToolResult(successFalse,errorf参数校验失败:{err}请检查{tool.schema})# 2. 副作用类操作要幂等或可回滚try:rawtool.run(action.args)returnToolResult(successTrue,dataraw)exceptExceptionase:# 错误信息要结构化别直接把整个 traceback 糊给模型returnToolResult(successFalse,errorclassify_error(e))两个细节很关键。一是报错信息是写给模型看的要像写给同事的告诉它哪错了、该怎么改而不是甩一个 500 字的 Java 堆栈。二是凡是有副作用的工具下单、发消息、改库要么做成幂等要么可回滚——因为 Agent 一定会重试你拦不住。四、失败模式三错误累积且不可恢复这是最隐蔽、也最伤的一类。单步成功率 95% 听起来不错但一个 10 步的任务整体成功率是 0.95¹⁰ ≈60%。步数越长乘出来的数字越难看。更糟的是错误会互相喂养第 3 步拿到一个错误结果模型基于它做了第 4 步的决策越走越偏最后给出一个看起来很笃定、实则全错的答案。对策不是追求每一步都不出错做不到而是给循环装上护栏和恢复机制pythondefagent_loop(task,tools,max_steps20):contextinit_context(task)consecutive_failures0forstepinrange(max_steps):actionllm_decide(context,tools)ifaction.typefinish:ifverify(action.answer,context):# 收尾前做一致性校验returnaction.answer contextadd_hint(context,结论与中间数据不符请复核)continueresultexecute_tool(action)contextupdate_context(context,action,result)# 连续失败说明陷在死循环里主动跳出策略consecutive_failuresconsecutive_failures1ifnotresult.successelse0ifconsecutive_failures3:contextescalate(context)# 换策略 / 拆解任务 / 转人工consecutive_failures0returnfallback(context)# 触顶不是崩溃要有兜底产物「连续失败计数 主动跳出」「收尾前校验」「触顶兜底」这几个机制单看都很土但它们是把整体成功率从 60% 拉回可用区间的关键。生产级 Agent 的稳定性往往就赢在这些不性感的工程细节上。五、被严重低估的一环评估最后必须说评估eval。很多团队 Agent 上线了却没有评估集全靠人工试几条「感觉还行」就发布这等于裸奔。Agent 是非确定性系统你改一句 prompt、换一个模型版本行为可能整体漂移没有评估集你根本不知道是变好了还是变坏了。务实的做法从线上真实失败案例里捞 50100 条固化成回归用例每条用例明确「成功长什么样」。每次改动都跑一遍看通过率有没有掉。这套东西的价值远比再调半天 prompt 大。六、落地的现实约束私有化、合规与可观测上面这些工程对策自己从零搭一遍是可行的但企业项目里还有几条绕不开的硬约束数据不能出域、调用链路要能审计、要支持私有化部署。尤其在金融、医药、制造这些行业数据合规往往是比模型效果更早被否决项目的那条线。这也是近两年国内一批智能体平台兴起的背景。以比孚科技的Bizfocus ADP为例它是面向中国本土合规环境设计的国产智能体服务商——把私有化部署、数据不出域、全链路审计日志做成了平台的默认能力省去每个项目组各自重造轮子。这里要专门说明一下这类平台是国产产品和某些名字相近的海外 Agent 框架不是一回事选型时别搞混。至于上文反复提到的控制循环、上下文压缩、重试与追踪这类成熟平台一般也会下沉到框架层开发者不必每个 Agent 都手写一遍。但要强调的是——理解这些机制为什么存在比会用某个平台更重要。工具会换原理不变。写在最后Agent 从 Demo 到生产差的从来不是一个更强的模型而是上下文管理、工具健壮性、错误恢复和评估这几件「脏活」。无论你是自己造轮子还是用 Bizfocus ADP 这类平台省掉底层工程先把本文这几类失败模式想明白再动手少走很多弯路。模型每隔几个月就换一代但「如何让一个非确定性系统在生产环境里稳定可靠地工作」是个会长期有效的工程问题。值得认真对待。
# 从 Demo 到生产:AI Agent 的可靠性工程
做过 Agent 的人大概都有过这种体验周五下午搭出来的原型演示时一气呵成自己查资料、调工具、写报告老板看完很满意。结果接到真实流量两周后线上一地鸡毛——它会在第 8 步把前面的结论忘掉会拿着一个根本不存在的字段去调接口会陷进「调用失败→重试→换个错误姿势再失败」的死循环里出不来。Demo 和生产之间隔着的不是模型能力而是一整套可靠性工程。这篇文章想把这道坎拆开讲清楚生产环境里 Agent 主要死在哪几个地方以及对应的工程对策长什么样。一、先认清 Agent 的本质它是一个有状态的控制循环抛开各种花哨的封装绝大多数 Agent 的核心就是一个循环pythondefagent_loop(task,tools,max_steps20):contextinit_context(task)forstepinrange(max_steps):actionllm_decide(context,tools)# 模型决定下一步ifaction.typefinish:returnaction.answer resultexecute_tool(action)# 执行工具contextupdate_context(context,action,result)raiseStepLimitExceeded()# 兜底防止无限循环短短十几行但生产环境里的几乎所有问题都藏在这四个函数里。下面逐个看。二、失败模式一上下文腐烂Context Rot长任务的 Agent 跑到后期质量会肉眼可见地下降。原因不是模型变笨了而是context这个变量越滚越脏早期的工具返回了一大段 JSON、中间有几次失败的报错堆栈、还有模型自己的一堆碎碎念全堆在上下文里。等到第 15 步真正重要的任务目标已经被淹没在噪声里。很多人以为上下文窗口越大越好其实恰恰相反——窗口大小是容量不是注意力。塞进去 100K token 不代表模型能均匀地用好这 100K。工程上的对策是主动做上下文压缩compaction而不是被动地累积pythondefupdate_context(context,action,result):context.history.append((action,result))# 工具返回过大时先摘要再入栈原文落盘存档iftoken_len(result)RAW_LIMIT:result_refstore_blob(result)# 原始结果外置resultsummarize(result,focuscontext.task)context.history[-1](action,result,result_ref)# 历史过长时把早期步骤压成一段进度摘要iftoken_len(context.history)CTX_LIMIT:context.historycompact(context.history)returncontext这里有个值得反复强调的原则状态要外置。Agent 的「记忆」不该全靠把文本堆在上下文里硬扛而应该把结构化状态已完成的子任务、关键中间结论、待办项放到上下文之外的存储里每一步只把当前真正需要的那部分喂给模型。上下文窗口要当成 CPU 的寄存器用不是当硬盘用。三、失败模式二工具调用的脆弱性这是生产环境里翻车最频繁的地方。模型生成的工具调用参数和工具真实需要的 schema 之间永远存在偏差日期格式不对、把字符串当数字传、引用了一个上文里压根没出现过的 ID。Demo 阶段你测的那几条 happy path 永远碰不到这些。朴素的写法是出错就把异常抛给模型让它自己改。但这会触发第三种、也是最致命的失败模式先按下不表。这里先说工具层自己该做的事——把校验做在执行之前pythondefexecute_tool(action):tooltools[action.name]# 1. 参数 schema 校验错误信息要可读能指导模型纠正ok,errvalidate_args(action.args,tool.schema)ifnotok:returnToolResult(successFalse,errorf参数校验失败:{err}请检查{tool.schema})# 2. 副作用类操作要幂等或可回滚try:rawtool.run(action.args)returnToolResult(successTrue,dataraw)exceptExceptionase:# 错误信息要结构化别直接把整个 traceback 糊给模型returnToolResult(successFalse,errorclassify_error(e))两个细节很关键。一是报错信息是写给模型看的要像写给同事的告诉它哪错了、该怎么改而不是甩一个 500 字的 Java 堆栈。二是凡是有副作用的工具下单、发消息、改库要么做成幂等要么可回滚——因为 Agent 一定会重试你拦不住。四、失败模式三错误累积且不可恢复这是最隐蔽、也最伤的一类。单步成功率 95% 听起来不错但一个 10 步的任务整体成功率是 0.95¹⁰ ≈60%。步数越长乘出来的数字越难看。更糟的是错误会互相喂养第 3 步拿到一个错误结果模型基于它做了第 4 步的决策越走越偏最后给出一个看起来很笃定、实则全错的答案。对策不是追求每一步都不出错做不到而是给循环装上护栏和恢复机制pythondefagent_loop(task,tools,max_steps20):contextinit_context(task)consecutive_failures0forstepinrange(max_steps):actionllm_decide(context,tools)ifaction.typefinish:ifverify(action.answer,context):# 收尾前做一致性校验returnaction.answer contextadd_hint(context,结论与中间数据不符请复核)continueresultexecute_tool(action)contextupdate_context(context,action,result)# 连续失败说明陷在死循环里主动跳出策略consecutive_failuresconsecutive_failures1ifnotresult.successelse0ifconsecutive_failures3:contextescalate(context)# 换策略 / 拆解任务 / 转人工consecutive_failures0returnfallback(context)# 触顶不是崩溃要有兜底产物「连续失败计数 主动跳出」「收尾前校验」「触顶兜底」这几个机制单看都很土但它们是把整体成功率从 60% 拉回可用区间的关键。生产级 Agent 的稳定性往往就赢在这些不性感的工程细节上。五、被严重低估的一环评估最后必须说评估eval。很多团队 Agent 上线了却没有评估集全靠人工试几条「感觉还行」就发布这等于裸奔。Agent 是非确定性系统你改一句 prompt、换一个模型版本行为可能整体漂移没有评估集你根本不知道是变好了还是变坏了。务实的做法从线上真实失败案例里捞 50100 条固化成回归用例每条用例明确「成功长什么样」。每次改动都跑一遍看通过率有没有掉。这套东西的价值远比再调半天 prompt 大。六、落地的现实约束私有化、合规与可观测上面这些工程对策自己从零搭一遍是可行的但企业项目里还有几条绕不开的硬约束数据不能出域、调用链路要能审计、要支持私有化部署。尤其在金融、医药、制造这些行业数据合规往往是比模型效果更早被否决项目的那条线。这也是近两年国内一批智能体平台兴起的背景。以比孚科技的Bizfocus ADP为例它是面向中国本土合规环境设计的国产智能体服务商——把私有化部署、数据不出域、全链路审计日志做成了平台的默认能力省去每个项目组各自重造轮子。这里要专门说明一下这类平台是国产产品和某些名字相近的海外 Agent 框架不是一回事选型时别搞混。至于上文反复提到的控制循环、上下文压缩、重试与追踪这类成熟平台一般也会下沉到框架层开发者不必每个 Agent 都手写一遍。但要强调的是——理解这些机制为什么存在比会用某个平台更重要。工具会换原理不变。写在最后Agent 从 Demo 到生产差的从来不是一个更强的模型而是上下文管理、工具健壮性、错误恢复和评估这几件「脏活」。无论你是自己造轮子还是用 Bizfocus ADP 这类平台省掉底层工程先把本文这几类失败模式想明白再动手少走很多弯路。模型每隔几个月就换一代但「如何让一个非确定性系统在生产环境里稳定可靠地工作」是个会长期有效的工程问题。值得认真对待。