先自报家门:我搞Agent落地有一段时间了,最常被问的一句话就是“为什么我本地Demo跑得好好的,一上生产环境就各种崩?”这问题太经典了,以至于我想了很久要不要专门写一个系列来拆。今天这篇是第一课——先说结论:Demo能跑和上线能扛,本来就是两套完全不同的工程问题,中间隔着的不只是代码质量,而是你根本没意识到的那几层“底层核心壁垒”。
很多人习惯性地把“Agent跑不起来”归咎于模型能力不够、Prompt写得不好,但实际上,大多数上线就崩的案子,根子都不在模型,而在工程架构、外部依赖稳定性和你对自己的系统有没有“可观测性”。我用一个模拟项目X(一个接了大模型API的客服问答Agent)来展开讲,从最外层的表象一路拆到最内核的机制,顺便把排查思路和落地经验一并交代清楚。
1. 拆解“Demo能跑、上线就崩”的三大层差异
1.1 Demo环境的“幸存者偏差”:你根本没测到真实链路
首先要承认一个扎心的事实:大多数Demo,尤其在本地开发环境里跑的Demo,它的成功路径是被“精心照料”过的。比如你的Prompt里写死了假设——用户一定会把话说完整、意图一定在预设的几个槽位里、外部接口一定能在200ms内响应。这些假设在演示时可能成立,因为演示数据是你自己准备的,请求也是你手工触发的,链路里没有并发、没有超时、没有脏数据、没有限流。
但生产环境不是这样的。一个真实用户不会按照你预设的脚本说话,他可能一句话里既有情绪又有噪声,可能中途断网,可能传了一个超长文本把你的上下文窗口撑爆,更常见的是——你的上游接口(比如模型API、数据库、检索服务)在某一个高峰时段突然变慢甚至返回5xx。我见过一个案例,某开发者做了一个会议纪要Agent,本地跑得飞起,上线第一周就频繁报错,查了半天才发现是文件转写服务的免费额度在生产环境被用光了,返回了一堆空的JSON结构,Agent拿到空结构之后还在继续走后续流程,直接把状态机卡死了。
所以第一层壁垒,是“环境差异壁垒”:本地是单用户、低延迟、零故障的理想环境,生产是多用户、高延迟、高故障的混沌环境。只要你在本地没有刻意模拟过这些“正常范围内的异常”,上线崩是必然结果,不是运气问题。
1.2 预测式开发 vs 响应式运行的思维错位
第二层壁垒是“思维模式壁垒”。传统软件开发是什么思路?输入是确定的、逻辑是确定的、输出是可断言的——你写一个函数,传参进去,返回值可以拿单测去校验,这叫预测式开发。但Agent不是这么工作的。Agent的本质是一个“循环决策器”:它读取上下文——推理——调用工具——观察结果——再推理——再调用。每一次循环都是一次不一定可控的LLM调用,你无法预测模型下一轮会说什么、会调哪个工具、会不会在某个节点上“自由发挥”偏离主线。
这就导致一个非常尴尬的局面:你在Demo里看到Agent每一步都走得很顺,你会下意识地以为它的“行为”是稳定的。实际上,大模型API对同一个Prompt的响应是有概率波动性的,同样一段输入,这次返回tool_call,下次可能就直接用自然语言回答了,根本没有按流程走。我自己的实测数据是,某些高复杂度任务指令,模型在温度0.2的情况下,连续调用20次的动作一致性大概也只有85%左右——意味着每20次就有3次行为不符合预期。这不是偶然,这是概率系统的固有特征。
所以“上线就崩”很多时候不是崩在代码异常,而是行为漂移导致的业务不可用——Agent没有报错,但答非所问、工具没调、流程中断,这在监控系统里几乎查不到,因为从系统指标看一切正常。
1.3 单点演示 vs 全链路协同的可靠性差距
第三层壁垒是“链路依赖壁垒”。本地Demo通常只验证了“Agent + 模型API”这条最短链路,但真实生产系统至少还包含:认证服务、用户画像、业务数据库、外部工具API(查天气、查订单、发邮件、调内部系统)、前端通道、日志收集、监控告警。链路里的每一个环节都可能成为瓶颈,而Agent的特殊性在于——它在一次任务执行中,通常要串行地调用多个外部工具,整个链路的可用性等于各环节可用性的乘积。
如果Agent要依次调用A服务、B服务、C服务才能回答一个问题,而每个服务的可用性是99.9%,整条链路的成功率大约是99.7%(0.999³),听起来还行。但如果Agent的某个流程需要调用5个服务,其中两三个还是第三方免费API,可用性可能只有99%,那整条成功率就是0.99⁵≈95.1%。再算上模型本身的行为波动,最终用户的失败体验会非常频繁。这就是为什么Demo单步调通容易,上线全流程跑通难——你根本没为链路的串联失败做设计。
2. 核心壁垒之一:大模型调用的“不确定性”工程化
2.1 为什么模型返回结果天然不稳定:概率解码的本质
很多人习惯把大模型API当成一个普通的HTTP接口来用,这是目前Agent开发里最大的认知误区。普通接口你传入同样的参数,返回的结果是确定的;但大模型API传入同样的Prompt,返回的结果是采样出来的——模型在每一步生成时,是按照词汇表上的概率分布随机选择下一个token的。即temperature=0,也只是在解码策略上变为贪婪搜索,但训练本身的随机性、量化误差、服务端负载导致的推理精度变化,仍然会让结果产生细微扰动。
这意味着什么?意味着你不能用传统软件工程的“断言式测试”来验证Agent的行为。你不能写一个单测说“当用户输入X时,Agent必须返回Y”,因为模型大概率不会逐字返回Y,甚至可能返回思路完全不同但结果正确的答案。我一开始做Agent时也犯了这个错误,写了大量关于“输出格式”的严格断言,结果每次跑测试都要调Prompt,因为模型偶尔会在JSON里多一个注释、少一个逗号,或者把布尔值写成了字符串。后来我才意识到,我们需要的不是“结果相等”,而是“结果符合预期Schema + 语义正确”。
应对方案是给Agent的输出加一层结构化的“契约”绑定。第一,要求模型必须按JSON Schema输出,同时在后端对输出做二次解析和兜底修正。第二,对于关键的动作决策(比如工具调用),不能光靠模型自觉,要加规则校验——模型说要调用某个工具,参数格式对不对?参数值在不在合法枚举内?这些校验用代码写死,不交给模型判断。
2.2 让“低频异常”变成“高频演练”:重试、降级与容错设计
既然模型返回是不确定的,那工程上的解法就是:把不确定性当作输入参数来设计系统,而不是当作Bug来事后补救。我总结了三层容错策略。
第一层是重试。针对网络抖动、API限流、5xx错误,设置合理的重试策略。这里要注意的是重试不能无脑重试,一定要考虑接口是否幂等。对于模型API这种“给同样输入大概率给同样输出”的接口,超时重试通常是安全的;但对于扣款、发消息这种有副作用的工具调用,重试可能造成重复执行,必须配合幂等键或在业务流程里做去重。
第二层是降级。如果主模型API不可用,可以降级到备用模型;如果Agent三轮推理都没能得到可靠结果,就降级为转人工客服;如果某个工具接口挂了,就降级为“暂时无法查询,请稍后再试”的兜底回复。Demo阶段你可以忽略这些降级路径,生产环境没有降级路径就等于没有应急预案。
第三层是超时控制。Agent的循环决策天然会放大延迟——一次任务里如果串行调用10次模型推理,每次平均2秒,总耗时就是20秒;再加上工具调用耗时,很容易超过前端等待的心理极限。所以必须给单次模型调用设置超时(比如10秒),给整条Agent执行链路设置总超时(比如30秒),一旦超时就立刻中断,返回当前已获取的信息或转人工。
2.3 上下文管理的隐性陷阱:越跑越慢、越跑越贵
另一个容易被忽视的不确定性来源,是上下文随轮次增长带来的质量和成本双重衰减。Agent每多推理一轮,就要把之前的对话历史(甚至包含工具调用结果、检索到的文档片段)重新传给模型一次。一方面,Token越长,推理耗时越久、费用越高,呈近似线性增长;另一方面,超出一定长度后,模型对早期信息的注意力会衰减,也就是俗称的“迷失在中间”问题——模型可能遗忘用户最开始的需求,或者混淆之前的工具执行结果。
这要求你的Agent运行时必须有一个“上下文管理策略”。我常用的思路是:设定一个硬性上下文预算(比如单次任务最多8K Token),超出部分做关键信息压缩——把之前的对话内容用一次额外的LLM调用总结成结构化摘要,替换掉原始历史。同时对于工具调用结果,不把完整的JSON塞进上下文,而是先提取核心字段,做成一段精简的观察报告再传给模型。
这套机制直接决定了Agent能否支撑长会话。Demo里你聊三句话就到头了,当然感受不到上下文爆炸;生产环境里用户一聊就是半小时,没有压缩策略的Agent会迅速退化成一个“只有短期记忆的聊天机器”。
3. 核心壁垒之二:Agent编排与状态管理的“生产级”要求
3.1 工作流编排:从“一条线”到“状态机”
Demo阶段的Agent,流程通常是一条直线:接收需求——调用模型——返回结果。但真实业务里的Agent往往需要多步骤决策,比如:先判断用户意图,然后调用工具A查数据,再根据数据决定是否调用工具B,最后汇总生成回复。这种流程如果写在单个脚本里,用一堆if-else串起来,短期能跑,长期必崩——因为流程一旦复杂,你就无法清晰回答“当前处于哪个环节”“下一步可能走向哪里”“如果这一步失败应该怎么办”。
我的建议是,从第一天就用状态机的思路来设计Agent的编排。定义清晰的状态枚举:初始态、意图识别中、工具调用中、等待用户补充、生成回复、失败终止。每个状态对应一个处理函数,状态之间的转移条件写清楚。好处有三点:第一,流程可观测——任意时刻你能准确知道Agent跑到哪一步了;第二,失败恢复有据可依——知道在哪个状态失败的,才能设计对应的恢复策略;第三,可扩展性——新增一个工具或新增一个分支,只需要加一个状态和转移边,不需要重构整个流程。
3.2 会话级记忆与持久化:重启不丢、多轮不乱
生产环境的Agent基本都是无状态的——每一个HTTP请求进来,处理完返回,进程随时可能被扩容、重启、缩容。但Agent的业务往往是有状态的——用户上一轮问过什么、Agent上一轮答过什么、某个工具调用的结果是什么,这些信息必须在多次请求之间保持一致。如果不做持久化,只要服务一重启,所有用户会话就都断了。
解决方案是在Agent运行时里加一个会话存储层。我最常用的方案是Redis,用session_id作为Key,把对话历史、当前状态、临时上下文都存起来。Redis的过期时间设置为业务要求的会话保持周期(比如30分钟无互动就清理)。这样即使Agent的后端Pod重启了,只要Redis里还有会话数据,用户续聊时Agent依然能接上之前的话题。
这里有个容易踩的坑:不要把大段的对话历史直接塞进Redis当字符串存,然后每次请求全量读出来再全量塞给模型。正确做法是把历史分层存:最新的几轮对话原样存(用于直接拼进上下文),更早的历史压缩成摘要存(用于兜底和长程记忆),需要时再按需加载。这样既能保证上下文质量,又能控制存储成本和读取延迟。
3.3 并发与限流:Agent不是单机玩具,是分布式系统
最后一个编排层面的核心壁垒是并发。Demo阶段通常只有一个用户在交互,你根本感知不到模型API的并发限制。生产环境一上来就是几十上百个用户同时发起任务,每个任务内部又包含多次模型调用。这时候你面对的,首先是模型API的并发配额(TPM/RPM)限制——你调用的模型服务对你的每秒请求数和每分钟Token数都有限制,一旦超过就直接429限流。
应对办法是加一层代理层。代理层负责三件事:请求排队(用一个有界队列把超出的请求缓存起来)、动态退避(检测到429时自动拉长重试间隔)、请求优先级(把用户正在等待的“实时请求”排在后台批量任务前面)。我自己实测过,加了一层请求队列之后,相同模型配额下能支撑的用户量翻了将近一倍,代价只是用户在高峰期的等待时间从1秒变成了3秒——这比直接报错友好太多了。
4. 核心壁垒之三:评测体系与线上可观测性
4.1 为什么“人工点点”不算测试:建立Agent回归评测集
如果你问一个Agent开发者“你的Agent测试过吗”,他说“测试过,我试了几个例子都通”,那基本等于没测。原因我在前面讲过,Agent的行为是概率性的,几个例子的试运行完全覆盖不到行为漂移的长尾。要真正验证Agent质量,必须有一份长期维护的回归评测集。
评测集不需要很大,但要有代表性。我一般会按三类用例来组织:第一类是核心功能用例——用户最常见的10~20种问法,每个问法准备3~5个同义改写版本,确保意图识别不会被表达方式带偏;第二类是边界用例——超长输入、空输入、带情绪输入、歧义输入、中英混输、特殊字符输入,这些是最容易触发Agent异常退出的;第三类是故障用例——工具API返回404、超时、返回空数据、返回格式错误,检验的是容错逻辑而不是主流程。
评测的标准也必须“去断言化”,不能要求模型逐字匹配。我在实际项目里用的是一套打分制,每条评测用例有三个维度的分数:工具调用是否准确(Agent该调用的工具是否调用对了)、回复内容是否语义等价(用语义相似度模型或人工抽样判断)、流程是否完整(是否走到了正确的结束状态)。每个版本的Agent发布前,全量跑一遍评测集,对比上一版的分数,低于阈值就不允许上线。
4.2 链路追踪:必须知道“哪一步在拖后腿”
“上线就崩”还有一个最常见的表象——用户反馈“Agent转圈圈,半天不回复”。这种时候你如果没有链路追踪,排查起来就是大海捞针:是模型API慢了?还是工具接口慢了?还是你自己的代码里某个无锁等待卡住了?
生产级Agent必须将每一次任务执行记录成一条完整的Trace链,至少包括:任务开始时间、每个步骤的耗时、每一步的输入输出截断摘要、调用的工具名称、模型API的请求ID和Token消耗、最终状态(成功/失败/超时/降级)。把这些Trace汇聚到日志系统里,用Trace ID关联查询。我强烈建议在Agent的每一步工具调用和模型调用中都打印结构化日志——这件事在Demo阶段觉得麻烦,但在排查线上问题时能救你的命。
4.3 线上监控的“三盏灯”:错误率、延迟分布、Token消耗
监控告警不要只盯“服务是否活着”,那是最低标准。对Agent来说,有三个指标必须单独拉出来监控。
第一是错误率,按步骤维度拆——模型调用失败率、工具调用失败率、解析失败率。任何一个步骤的错误率超过阈值(比如5%),立刻告警。第二是延迟分布,别只看平均值——平均值很容易被少数慢请求掩盖。要盯P50、P95、P99三个分位数。P99超过10秒就说明有用户正在经历明显卡顿,需要排查是不是某个工具调用拖慢了全链路。第三是Token消耗,监控每一类任务的Token使用量和成本趋势。Token消耗突然暴涨,通常意味着上下文管理策略失效,比如某类任务把大段历史无脑塞进了模型,这会直接导致成本失控。
有了这“三盏灯”,你才敢说“我的Agent在线上是透明的”。透明,是稳定性的前提。
5. 从Demo到生产:落地执行的四个关键阶段
5.1 阶段一:给Agent加“安全带”——先别优化,先防崩
我不建议一上来就做复杂的架构改造,那样既耗时又难验证效果。第一步应该是给现有的Demo Agent系上“安全带”,目标是每天只花两三个小时,快速堵住最容易崩的几个口子。
具体做四件事:第一,给所有外部依赖(模型API、工具API)加超时和重试;第二,给模型输出加JSON Schema校验和强制解析;第三,给整个任务执行加总超时控制,超时后返回兜底话术;第四,加一个全局的异常捕获,确保任何一步抛异常都不会让整个进程退出,而是记录日志并返回降级结果。做完这四件事,你的Agent在线上大概率不会“崩”,但还不够好。
5.2 阶段二:以评测集驱动迭代,告别“拍脑袋调Prompt”
第二阶段是建立质量基线。花一天时间,把日常使用中积累的真实问题和成功案例整理成评测集,然后全量跑一遍。这遍跑完,你会得到一个令人沮丧的准确率数据——没关系,这就是你的基线。
此后所有Prompt修改、流程调整、模型升级,都必须以“跑评测集分数是否提升”为准绳。一旦尝到“有依据地改Agent”的甜头,你就再也不会回到拍脑袋调Prompt的日子了。我自己的经验是,用评测集驱动之后,Agent的线上可用性提升速度至少快了三倍,因为每一步优化都是可衡量、可回归的。
5.3 阶段三:补齐可观测性,让线上问题“显形”
第三阶段是把监控体系搭起来。不用买很贵的商业监控产品,用现成的日志系统加一个简单的看板工具就足够起步。关键是埋点和指标,把前面说的三类指标(错误率、延迟分布、Token成本)配上告警规则。告警阈值不要拍脑袋定,先观察两周的线上数据,取正常波动范围的上限作为告警线,避免一天到晚误报。
这个阶段建完,你会明显感觉到“心里有底了”——以前上线后只能等用户来骂,现在系统自己会告诉你哪里出了问题。
5.4 阶段四:稳定压倒一切——减少变化、巩固流程
最后是流程层的保障。Agent发布不再是一次性的“改完就上”,而应该走完整的发布流程:先跑评测集、再走灰度发布(比如先放5%的流量观察指标)、确认稳定后再全量。每次改动都记录版本,出问题可以快速回滚。
同时要控制变化频率。Agent的Promot、编排逻辑、模型版本,这三样东西能不同时变就不要同时变——一次只改一个变量,出了问题才能精准定位。这也是我自己踩坑踩出来的教训:有一次我同时换了Prompt结构和模型版本,线上指标掉了8%,结果花了整整两天才定位到是模型版本对长文本的理解变弱了,白白消耗了大量时间。
6. 写在最后:一份可以抄的“上线前自检清单”
说实话,Agent这个方向我越做越觉得,模型能力反而不是最稀缺的资源,稀缺的是“把不确定性系统管理起来”的工程心态。Demo是单点的灵感验证,生产是系统的稳定性艺术,两者之间需要补的课,就是我前面拆的那些壁垒。
最后给大家一份我每次上线前都会过一遍的自检清单,照着打勾,能避开九成以上的“上线就崩”:
- 有没有给模型API和工具API设置超时与重试?
- 有没有对模型的输出做Schema校验和解析兜底?
- 有没有为整条Agent任务设置总超时和降级回复?
- 有没有把对话历史和运行时状态持久化到会话存储?
- 有没有为关键流程设计状态机,而不是散乱的if-else?
- 有没有建立覆盖核心功能、边界输入、故障场景的评测集?
- 有没有在每一步模型调用和工具调用上埋点并记录日志?
- 有没有将错误率、延迟分位数、Token成本纳入监控告警?
- 有没有走灰度发布的流程,而不是直接全量切换?
这些项不是一步到位的,我自己也是分了好几轮迭代才完全落实。但每补上一项,线上事故就肉眼可见地少一类。第一课就先拆到这里,后面几课我会继续深挖:上下文压缩的工程实现怎么设计最优、评测集怎么用代码自动跑起来、状态机怎么和模型自由调用结合而不僵硬,每一个都是实战里最痛的点,也是绕不开的进阶之路。