
上个月我们给部门做了一个内部知识库答疑 Agent 的评审。演示环境里它准确抽取文档、连着答对八轮追问还能现场改口风调整答案会议室里一片叫好。可灰度一开不到三天客服团队就炸了同一道政策问题上午给的是 A 版本下午解释就走样用户明明已经在上一轮确认过客诉单号下一轮 Agent 自己嘴里蹦出来一个新单号更离谱的是它在调用工单系统查询时偶尔会把“查不到”包装成“查询成功但结果为空”。这种“Demo 惊艳、上线拉胯”的现象在 Agent 落地圈里太常见了。我做了几年大模型应用层自己也被这个魔咒反复教训过最后才想明白问题不在模型推理能力强不强而在于我们把“单个 Demo”当成了“完整产品”来验收。Demo 跑通的是理想路径生产环境考验的是异常路径、边界条件、权限管控和资源水位。这篇文章我想把踩过的坑和拆过的方案摊开聊重点讲清楚四道坎——确定性、上下文记忆、工具调用、并发与可观测性——每一道都给出可以直接抄的工程解法。哪怕你是刚入门的 agent 开发者也能照着这套思路把项目从“能演示”推到“敢上线”。1. 先承认现实Demo 的“惊艳”是精心安排的剧本1.1 演示本质上是单线程、低噪声、高容错的过程很多团队在评审 Agent 时其实是在看一场被反复排练过的演出。演示者心里清楚哪条路径最容易成功Prompt 经过几十轮微调语料提前清洗过连问题的顺序都是设计好的。这种单线程脚本下跑出来的流畅效果反映的是整个团队对最优路径的记忆而不是系统对真实世界的适应能力。真实用户完全不一样。他们不会按剧本提问会把口语、错别字、省略语混在一起会在一个会话里突然切换主题会在 Agent 明确给出结果后再追问一句“你确定吗”。真实系统还掺着网络抖动、下游接口限流、第三方权限变更这些噪声。你在 Demo 里看不到这些问题不是因为它们不存在而是因为它们被刻意绕开了。所以我的建议是从立项第一天起就把“演示脚本”和“压力测试脚本”分成两套东西。演示脚本用于汇报和评审压力测试脚本用于自测——里面专门放一些故意刁难的输入矛盾信息、超长上下文、缺参数请求、重复提交甚至直接让 Agent 面对它权限之外的操作。只有后者能真正暴露生产风险。1.2 三组对照Demo 环境与生产环境的系统性差异我梳理过一份对照表每次评审新项目都会拿出来逐项核对。维度Demo 环境生产环境输入语料清洗后的标准文档原始、矛盾、带口语和错别字的真实消息外部依赖本地 Mock 服务真实系统有鉴权、限流、超时和他人并发用户行为按演示脚本点击自由输入、任意追问、随时反悔评判标准人工看答案“像不像”业务指标正确率、时效、合规、成本数据规模几条精选案例全量历史数据分布不均匀模型版本固定版本可能被平台静默升级最容易被忽略的是最后一行。大模型服务商做版本迭代时哪怕只是底层模型微调也可能导致同一段 Prompt 的输出风格和工具调用习惯发生变化。你在周五 Demo 时用的还是旧版本周一上线时流量已经被切到新版本结果完全不一样。生产环境必须把模型版本固化和灰度升级纳入工程管理否则所有测试都建立在流沙上。1.3 从“测试通过”到“可上线”的标准转变Demo 阶段的验收标准是“单点正确率”抽十个问题答对九个就算不错。但生产环境的验收标准应该是“长尾风险”一百次对话里哪怕只有三次严重错误对客服、交易、工单这类业务来说都是事故级影响。我见过一个反例。有个团队做订单售后 Agent演示时准确率测到 95%管理层非常满意。上线一周后用户开始投诉 Agent 误判退货原因——那 5% 的错误集中在“用户描述模糊”的场景而这类场景恰恰占了真实流量的三成。原因是演示集里故意避开了模糊表达。后来我们重新定义了生产验收指标正确率只是基础门槛更关键的是“最坏情况下的表现”——超时了怎么办、意图识别置信度低怎么办、工具调用失败怎么办、用户反复纠缠同一个问题怎么办。如果系统在异常场景下没有安全停止机制那它就不具备上线资格。2. 第一道坎确定性——不能让同一个问题在关键业务上今天一个答案明天一个2.1 非确定性从哪来模型、输入、运行参数的三重叠加做惯了传统软件开发的工程师对“确定性”有一种本能的执念同一个输入必须得到同一个输出。但大模型天生不是这样。即便你设置了temperature0也无法完全消除随机性因为采样过程、浮点计算、批处理顺序都会带来微小差异。更现实的问题是用户输入本身就不是同一个——两句话语义相近但措辞不同可能在模型内部触发完全不同的推理路径。举一个我们踩过的真实例子。用户说“帮我取消订单”时Agent 能正确走到退单流程但改成“那个订单不要了”意图识别模块却把它归类成“咨询”回了一段无用的说明。模型层面的行为漂移还包括服务商平台更新某个周五所有请求的响应延迟突然升高查了半天才发现是模型服务商在灰度新版本部分推理行为发生了变化。生产系统必须把这种漂移当成常态而不是意外。2.2 “听起来对”不等于“路径对”只看结果会漏掉大事故Demo 评审时我们习惯看最终答案但生产事故往往藏在推理轨迹里。常见的问题包括参数拼接错误但语言表达流畅调用了一个多余的工具导致数据被意外修改在权限校验之前就执行了高风险的写操作甚至出现“答对了结果、用错了方法”的情况——比如用户问退款进度Agent 没有去查询退款单而是根据对话中的只言片语推测了一个日期。这种“幻觉式成功”最具迷惑性因为从回复文本看完全正常。所以我们在生产环境强制要求追踪“轨迹”而不是只看“结果”每次请求都记录下意图识别结果、工具调用序列、每个工具的参数、返回状态码、最终交付内容。一旦发生问题可以直接回放整个决策链路。现在很多 agent 开发框架都把 trace 当成标配但真正落地时还要结合业务字段去定义什么算“异常轨迹”。2.3 解法把自由发挥锁进允许区间关键路径用规则兜底我反对“用规则取代模型”的极端做法也反对“完全信任模型”的偷懒做法。正确的姿态是分层模型负责理解与生成规则负责校验与执行。类似下面的伪代码逻辑def refund_flow(user_input, session): intent llm_extract_intent( user_input, schemaREFUND_SCHEMA ) if intent.confidence 0.6: return handover_to_human(session.id, reason低置信度) if not validate_permission(session.user, intent.action): return forbidden_reply(session.user) if not validate_params(intent.params): return clarify_question(session.id, missing_fieldsintent.missing) # 幂等键由业务系统生成不依赖模型 return execute_refund_with_idempotency( session.id, intent.params )这套模式的核心思想是模型只输出结构化的意图和参数真正的执行由代码控制。关键节点必须加校验——参数是否完整、权限是否足够、状态是否允许、请求是否重复。意图置信度低于阈值时宁可转人工也不要硬答“不确定”在业务里是可以接受的答案比一个自信的错误安全得多。生产级 Agent 还要有一个“紧急回退开关”。模型服务异常时系统可以自动降级为固定话术、知识库检索模板或者直接转移给人工处理。这个开关在 Demo 里不会用到但在线上是救命用的必须提前接好。3. 第二道坎上下文与记忆——长会话是 Agent 的“记忆黑洞”3.1 上下文窗口再大也不等于真的记住了很多团队以为买了大上下文窗口多轮对话就不会丢信息。实际上上下文窗口只是“能放进去”不等于“能有效使用”。我见过一个典型案例用户在对话里先报了订单号中间聊了几轮售后政策最后说“把它改到下周”时Agent 已经找不到“它”指向的是哪个订单了。问题不在于窗口不够大而在于关键状态被中间过程挤出了有效注意力范围。另一个更隐蔽的问题是“记忆污染”。假设 Agent 第一轮把订单状态判断为“已发货”之后工具返回一条异常信息模型却把这条异常当成新的业务状态写进上下文然后基于这个错误状态继续推理。后续所有回答都被污染带偏。生产环境里这种错误状态一旦被模型以自然语言复述出来用户很难察觉但业务后果很严重。3.2 用“工作记忆 长期记忆”代替“全量塞历史”我们团队后来把记忆拆成了两层效果立竿见影。工作记忆Working Memory负责当前任务的状态用结构化 JSON 保存比如订单号、当前步骤、已收集字段、待确认事项。每轮对话结束后由代码更新这些字段而不是让模型随意改写。下一轮模型只读取关键状态字段不需要把整段历史都塞进 Prompt。这么做既省 Token又防止状态漂移。关键原则是工具调用失败时绝不更新状态只有成功返回才允许写入。长期记忆Long-term Memory负责跨会话的偏好和关键事实存储到向量库或常规数据库在需要时按语义检索注入。比如“用户上次保修单号是多少”“用户偏好电话联系”这类信息不需要每轮都带但用户主动提及时要能查得到。长期记忆的召回必须走工具让模型显式调用“记忆检索”而不是假装记得。这样能避免一个经典事故Agent 对用户说“根据您的历史记录”但实际上根本没查过库只是根据当前上下文编了一句。3.3 记忆回路里最容易翻车的两个操作第一是“摘要压缩”。长会话超过一定轮数后我们会把早期历史用模型生成摘要再和最近 N 轮原文一起作为上下文。但摘要本身也会有损失一旦摘要丢了关键参数后面就接不上了。所以我们在做摘要时要求模型按固定 Schema 输出关键字段比如用户诉求、涉及单号、当前状态、待办事项压缩的是表达不是信息。第二是“记忆回滚”。状态虽然是 JSON 结构化存储但也会存在写错的时候。所以我们给状态变更加了事件日志类似数据库的 WAL每轮都记录旧状态、新状态、变更原因、对应请求 ID。一旦发现状态异常可以直接回滚到上一个正确版本。这套机制听起来工程味很重但正是它让 Agent 从“聊天的玩具”变成“能办事的系统”。4. 第三道坎工具调用——Agent 与外部系统的信任危机4.1 能调通接口和能安全地调接口是两回事Demo 里调用工单系统、发邮件、建审批流网络通畅、鉴权到位、数据干净。生产环境的外部系统则随时可能超时、限流、返回 5xx、甚至因为数据不一致给出矛盾结果。更麻烦的是模型可能“发挥创意”把参数传错。我遇到过几个真实事故Agent 发邮件时把客户名字和订单号拼接错误导致收件人收到一封内容颠倒的邮件另一个 Agent 在外部系统重试机制不完善的情况下连续点击“创建工单”三次产生了三张重复单。后者提醒我们Agent 的工具调用一旦失败重试策略不能只考虑“再试一次”还要考虑“上一次到底成功没有”。这就是幂等性的问题。4.2 工具调用要过的六道防线我们把工具调用层做成了一个独立网关所有外部接口都必须经过它统一管控不能允许模型直接访问底层系统。网关上有六道防线防线具体措施防住的典型事故参数 Schema 校验调用前校验必填项、类型、枚举值模型漏传单号、传错日期格式超时控制每个工具设置独立超时时间禁止无限等待下游服务挂起Agent 卡死幂等键每次写操作携带业务幂等 ID重复建单、重复扣费重试与熔断指数退避重试超过阈值自动熔断打爆下游系统导致雪崩最小权限按用户上下文注入鉴权票据禁止全局权限越权读取他人订单、跨租户访问审计日志记录工具名、参数、结果、耗时、调用链出问题后无法定位责任这里面最容易被忽视的是幂等键。模型不是一个合格的客户端它不会天然给每次调用生成唯一 ID。所以我们在网关层强制所有写操作必须由业务系统生成request_id同一request_id多次提交只生效一次。这个机制让我们在排查问题时省掉了大量扯皮。4.3 用工具网关和“人机审批”守住 Agent 安全边界工具网关本质上是一个 BFF 模式对外屏蔽内部系统的实现细节对内做参数校验、鉴权、限流、审计。代理永远不会拿到数据库密码或核心系统的全局密钥它拿到的只是“当前用户上下文下的最小凭证”。这就把 Agent 安全事故的半径压缩了——即使模型被诱导输出恶意指令它能碰到的也只有当前用户权限范围内的资源。另一条重要的安全线是“风险分级人工审批”。我们把工具操作分成三类低风险查询、检索、知识库、中风险创建草稿、发起流程、高风险删除、退款、发送对外消息、修改权限。低风险自动执行中风险允许自动但要做审计追踪高风险必须在关键步骤前插入人工确认。这个设计可能会让业务觉得“不够智能”但上线后你会发现所有重大事故都发生在抛开人工确认的高风险自动路径里。宁可牺牲一点自动化率也要把不可控的写操作管住。5. 第四道坎并发、性能与可观测性——单线程 Demo 与真实负载是两种生物5.1 一次回答背后是 3 到 8 次串行模型调用很多人以为用户问一句Agent 就调一次模型。实际上一个稍微完整的业务场景要走“意图识别 → 工具选择 → 参数生成 → 执行工具 → 结果总结”甚至中间还要穿插两三层推理串行下来延迟是非常恐怖的。我们测过一个售后处理流程单次模型调用平均 1.5 秒工具调用 0.5 到 3 秒整个链路累计轻松超过 8 秒。而在线客服场景里用户超过 5 秒没收到回应耐心就已经开始崩了。处理环节单次耗时累计耗时意图识别0.8s0.8s工具调用查询订单0.6s1.4s二次推理生成回复1.4s2.8s可选再调一次补充工具0.5s3.3s最终结果总结1.2s4.5s所以生产落地时必须对链路做显式的延迟预算。能并行的工具调用就并行能提前预取的就预取不是每个问题都需要完整走完全部环节。更实用的办法是“流式输出 局部完成”先把 Agent 正在做什么展示给用户再逐字吐出结果用户的体感延迟会显著下降。异步任务模式也值得引入——如果某个任务确实要跑到几十秒与其让用户干等不如生成一个任务单完成后通过消息通知。5.2 并发之下的三把锁限流、预算、队列Demo 阶段只有一个人在用生产环境一上来就是几十、几百个并发这时候最先暴露的是资源配额问题。大模型服务商通常有 RPM每分钟请求数和 TPM每分钟 Token 数限制多个业务线共享同一个账号时互相抢额度会非常惨烈。我们现在的做法是按租户和业务线做 Token 预算高优业务线保底额度低优业务线可以排队超限时直接拒绝并返回友好提示绝不允许无限重试把下游打爆。队列和本地缓存也是并发场景的关键。常见问题重复率极高尤其是客服场景用户问的都是同一批高频问题。我们对输入先做归一化去重——语义相似度超过阈值的直接命中缓存答案不走模型。实测这个机制能打掉 20% 到 30% 的模型调用量既省成本又稳延迟。注意缓存要带时效和业务上下文标签政策变化后要能主动失效不能因为缓存把旧答案发给用户。5.3 没有可观测性的 Agent 等于闭着眼睛开飞机我见过太多团队把 Agent 上线后只能靠用户投诉反推问题。这是最被动、成本最高的排错方式。生产级 Agent 从第一天起就必须接入可观测性体系至少覆盖四个维度延迟分位数P50、P95、P99、错误率与工具失败率、Token 消耗与成本、关键业务成功率。每一次请求都要有一个全局trace_id从入口贯穿到模型调用、工具调用、记忆读取、最终响应。工具调用的失败要记录具体原因——是超时、参数校验失败、还是鉴权拒绝。模型调用要记录 Prompt 版本、模型版本、Token 用量。这样当某个业务的成功率下降时我们不是靠猜而是直接看 trace一秒定位到是哪个环节漂了。更进阶的做法是建立回归评测集。我们会从生产日志里挑选高频场景和已发生事故组成一个几百条的评测集每周自动跑一次输出正确率和工具调用成功率。模型服务商一旦发布新版本先用评测集跑分分数不降再灰度切换。这套机制不复杂但非常有效它把 Agent 的质量从“感觉还行”变成了“持续守护”。6. 落地清单从“跑通演示”到“敢开线上”的四步验证6.1 上线前必须回答清楚的四个问题我在评审任何 Agent 项目时都会逼着团队回答四个问题答不上来就继续改不要上线。第一最坏情况下怎么行动。意图识别错了怎么办、工具超时怎么办、数据查不到怎么办。系统必须有一条明确的安全停止路径而不是硬着头皮输出一个自信的错误。第二是否留了人工介入舱。人工兜底不是“出了问题再上”而是设计时就留好接管入口所有高风险操作前都有确认节点。第三能不能量化结果。成功率、平均解决时长、人工介入率、Token 成本这些指标在上线前就要定义清楚否则上线后根本无法判断是成功还是失败。第四是否有降级路径。模型服务不可用时能不能切到规则引擎、知识库模板甚至直接转人工。把降级当成正常功能来做而不是事后补救。这四条不是研发自嗨是要能写成文档、能评审、能演练的。我们内部有个不成文的规定没有演练过“模型服务中断 10 分钟”的 Agent不允许上正式环境。6.2 影子模式与人工协同的灰度策略“影子模式”是我最推荐的上线策略没有之一。具体做法是线上真实业务照常走人工流程Agent 在后台同步运行但它的输出不直接触达用户而是作为参考建议显示在坐席工作台。坐席可以采纳、忽略、修改所有行为都被记录。灰度阶段可以这样分四步走第一步离线评测用历史数据回放评测集跑分第二步影子运行Agent 只输出不接手人工操作时在旁边提示“系统建议”第三步人机协同Agent 直接输出初稿坐席审核后发送统计采纳率和修正率第四步自动化运行Agent 直接回复用户但保留人工接管按钮和高风险审批节点。每一步都要对比业务指标指标没有恶化再进入下一步。这条路看起来慢但实际上是上线最快的路——因为每个阶段出问题的影响半径都被牢牢控制住了。6.3 最后一条经验给 Agent 设计一个“不回答”的按钮这条经验是我踩过坑之后总结出来的值得单独拿出来说。很多团队追求 Agent 什么都能答但我现在会刻意在 Prompt 和流程里给 Agent 设计“拒绝回答”的权限当意图置信度低、权限不足、数据缺失、或者问题涉及到它不该承担的责任时它应该明确说“我暂时无法确认建议转人工”而不是硬编一个答案。在评测标准里我也增加了一个维度“在应该拒绝的场景下是否做出了正确的拒绝”。这个维度听起来简单但能拦住一大批上线事故。因为 Agent 出了问题往往不是它“不会答”而是它“太会答”——把不确定的事情包装成确定的结果才是生产环境最大的风险。我们做了这么多 Agent 项目之后最深的体会是Demo 展示的是模型能力的上限而生产要守护的是系统的下限。把不确定交给流程把确定交给代码把意外交给人来兜底这才是从“惊艳”到“能扛”的真正路径。希望这篇文章能帮你少踩两三个坑让 Agent 项目真正从会议室走到业务一线。