引子这篇不讲概念讲分法先如实交代来源。这是一套跑了一年多的生产系统一个多平台电商客服 Agent接了七个店铺端桌面客户端、网页后台、移动工作台三类端形态都有日均进来三四千条买家消息。它只对一件事负责——任何一条进来的消息必须在平台考核的响应时限内被处理掉要么被正确地自动回复要么被干净地交到人手里。关于 Agent 架构能查到的概念我基本都读过。概念都对但往业务里落的时候没有哪一个能拿来就用。这篇复盘不重复教科书只回答三个问题这条链到底怎么切的、切错的时候出过什么事故、修完之后沉淀下来的设计模式长什么样。先给一张全景图四个环节消息从左到右流过人工接管到期交还知识库超范围幂等回写消息流入感知与判重指纹账本决策与路由四档漏斗执行与记账确定性步骤协作与移交人机协同链路避让锁对应五个在事故里长出来的设计模式漏斗式意图路由、AI 只写不做、转人工是链路不是按钮、指纹账本加按会话卡状态、带边界与确认闸的长期记忆。下面逐个展开。一、意图路由不是二选一是四档漏斗1.1 为什么全问大模型行不通把消息直接丢给模型是架构上的省事解四个现实理由让它不成立成本。生成按条计费而进店消息里七成以上是高频简单问题——“哪里发货”“什么时候到”“能便宜点吗”。让模型去回答一条配好答案的话是在给账单注水。延迟。规则判定是毫秒级模型往返是秒级。响应时长是平台给店铺打分的硬指标省下的每一秒都直接体现在考核上。确定性。价格、库存、发货时效这些数字答案必须一字不差。模型意思对就行的天性恰恰是客服场景里赔不起的东西。风险面。售后承诺、赔偿口径这类话术没有闸的生成等于替店铺当场许诺事后是要兑现的。所以架构上的结论很明确**模型不是路由器模型是漏斗的出口之一。**路由的职责不是把问题分对而是把能拦下的消息在上游拦下。1.2 四档的定义档判据来源产出典型触发一档·自定义规则商家自己配置的关键词与话术毫秒级 canned 回复商家配过的发货地、尺码建议二档·工单档操作性意图识别生成工单并决定归属“改地址”“催发货”“申请售后”三档·转人工规则服务红线关键词表进入人工链路买家明说找人工、投诉升级类词四档·AI 档模型生成清洗后的回复文本长尾咨询、寒暄、复杂表述顺序原则规则在前、模型在后红线夹在中间兜着。一条消息每往上走一档被拦下就少一次模型调用、少一分幻觉风险。三档的位置有讲究——必须在工单档之后执行见 1.3 的事故又必须在 AI 档之前执行红线内容永远不该进入生成环节。1.3 档序的学费哨兵机制出过这么一次事故。系统里有一个工单转交 AI 处理的开关命中工单档的消息默认行为是建工单转人工开关打开后允许 AI 直接接手。开关上线当天就发现不对劲——改地址类消息一条都没进 AI 档全被转人工了。翻链路日志才看清因果转人工规则表里“订单修改类目下配着改地址这个关键词。消息在工单档被判定可以 AI 处理之后继续往下走结果被三档的关键词截走。也就是说二档的决策被执行顺序否决了——上游刚决定这条我来办”下游反手把它塞进了人工队列。修法不是调整关键词表关键词本身没错错的是它不该在有明确归属决策时再生效而是引入了一个哨兵标记function route(msg): # 一档商家自定义规则 if hit : match_custom_rules(msg): return canned(hit) # 二档工单/操作性意图 ticket classify_ticket(msg) # 改地址 / 催发货 / 售后申请 ... if ticket and ticket.policy AI_TAKEOVER: ctx.skip_below SENTINEL # 哨兵显式跳过余下规则档 enqueue_ticket(ticket, ownerai) # 三档转人工红线见到哨兵直接放行 if not ctx.skip_below and hit : match_transfer_rules(msg): return handoff(hit) # 四档AI 生成 reply llm_generate(prompt(msg)) return sanitize(reply)两条教训值得抄进任何 Agent 项目的规范里一个开关的真实影响面等于整张路由表。工单转 AI听起来只动工单档实际上改变了每条相关消息在下游三档的去向。上线前必须拿全部关键词表对每条消息做链上模拟——这也是为什么这类开关默认关闭、灰度打开。**档与档之间的状态传递必须显式。**靠我在这一步处理过了下游应该自觉的默契在多一个档、换一次顺序之后必然失效。哨兵是个不起眼的标记位但它把跳过下游从默契变成了契约。1.4 AI 档的内部注入位次与三道清洗闸进了第四档也不等于放任生成。系统提示的组装位次是有讲究的人设 → 服务目标 → 回复风格 → 安全红线 → 知识库资料 → 买家消息红线排在知识库资料之前因为教材案例是有语气的模型会不自觉地模仿例子的胆量。把约束放在例子后面例子会稀释约束。生成出来的文本离发送还差三道闸超范围信号闸模型被明确要求不知道就输出特定占位信号。这个信号如果裸发出去就是事故闸的职责是拦截信号、改走兜底路径工单或转人工。标点与换行归一回复尾部标点按固定字符集剥离——注意这个字符集是历史行为契约半角感叹号就不在剥离集内那是当初对齐既有话术习惯定下的对齐历史行为优先级高于更正确。买家侧的多行消息则在喂给模型前折叠成逗号避免生成端模仿出多段排版。特殊字符过滤存在少数特殊字符会让发送环节静默失败发不出去、也不报错过滤闸的卡点是且仅是生成之后、发送之前位置不能换。二、工具调用与确定性步骤AI 只负责写代码负责做2.1 切分表Agent 项目里工具调用四个字容易让人产生一种错觉模型想调用什么就调用什么。真实业务里我给的纪律是反过来的——先列出整条链把每一步标注成确定性或概率性概率性的步骤一律不赋予副作用步骤性质执行方拉取消息、算指纹、记账确定性代码意图归类、工单归属判定概率性但输出只作为候选模型代码复核回复文本生成概率性模型清洗三道闸、长度闸确定性代码发送、重试、降级确定性、带副作用代码转接、加锁、交还确定性、带副作用代码模型不得触碰所谓函数调用在这套架构里的真实语义是**模型申请代码执行代码记账。**模型的申请同样要过路由——不合规的工具请求比如没有权限的售后操作会被直接改写进转人工链路。2.2 硬闸发送成功必须等于真的发送了一次深夜排障留下的规则。用户报发了图片对面看不到我方日志却显示这条消息全程零错误走完——典型的假成功。定位到两个叠加原因发送动作在某个环节没生效而代码只看接口没报错成功路径又只打 info 级日志正式包的日志级别下这段完全盲区。修法是给所有带副作用的动作补事后采样执行动作 - 连续采样三次结果状态 三次均明确未生效 - 重放一次 - 仍未生效 - 报错中止绝不回 OK 存在采样不到 - 维持放行宁可放过不可误杀这条纪律背后的原理**重试上限的存在以成功判据诚实为前提。**如果成功本身会撒谎那重试策略就是在给一个薛定谔的结果计数——你以为重试了三次其实三次都没发生而系统认为都完成了。2.3 拆气泡与长度闸一次一进四出为了让长回复在手机上好读发送前会把长文本拆成多个气泡。听起来是人性的细节直到它和逐条回复见 4.2叠加买家连发几条、每条都触发一轮回复、每轮回复又拆成几段——结果买家问了一个问题收到四条消息像被机器轰炸。对策是两条硬上限拆分恒定两段封顶宁可不拆同时在系统提示的稳定前缀段里加说话分寸约束短句、口语、一次只回一件事——约束必须放在不动的前缀里否则每轮提示都在变下游的提示缓存全部失效。教训很一般化**局部优化之间是乘法关系。**分段发送、逐条回复、语音应答每一个单看都合理叠在一起就是灾难。2.4 重试上限与降级链失败点重试预算降级方向模型调用超时2 次回落到一档 canned 话术同时记工单语音回复执行失败1 次降级为普通文字回复合成音频时长低于下限0 次直接文字太短的语音是噪音平台转接执行失败0 次进人工任务面板队列见第三章表情/语气符号事前过滤语音播报会把符号名原样念出来预算原则**降级方向在设计期枚举不在事故现场发明。**临时决策的降级是二次事故的开始。还有一个反原地打转的细节需要预处理才能理解的消息例如语音转写如果本轮转写失败就把这条标记为当轮搁置绝不在同一轮里对着同一条失败操作反复重试——重试预算是数出来的不是在循环里漏出来的。三、人机协作转人工是一条链路不是一个按钮多数 Agent 教程把转人工画成架构图边上的一个箭头。生产里它占了我花时间的头一段因为它同时涉及决策、执行、避让、交还四个子系统。3.1 入队的四种来源来源触发方行为优先级知识库守卫代码消息超出资料范围或命中售后红线判转人工AI 不回复高买家主动要求关键词档先发安抚话术随后尝试平台内真实转接加急响应时限逼近定时器创建人工任务 前置安抚话术加急转接执行失败执行层兜底进面板队列并提示普通但带故障标记队列本身有三个常被省略的字段优先级四档售后类默认加急、原因机器判定依据人工接手时先要看的东西、备注值班客服可以改写。同一会话去重已经在人工队列里的第二、三条疑难不再重复入队——否则值班席会被同一客户的连环追问刷出十个任务。3.2 两级语义先转接失败再入队买家的期望是转人工之后对话直接由真人接走理想路径是在平台会话里完成真实转接指定在线客服、顺位轮转、负载均衡。但这条路径依赖端类型与窗口形态在部分端上根本走不通。架构上我把转人工做成两级语义先尽力真实转接失败不报错给客户降级为面板任务。人的体验从我被转走了退化为值班席出现一个待认领任务 提示音 客户端角标闪烁。这层降级是整条人机链路永不掉单的关键——如果转接失败就止步于失败那四种入队来源里要命的那两种买家点名要人工、时限逼近恰恰会漏。3.3 避让锁AI 知道自己什么时候该闭嘴人机协作的事故簿上头一页写的就是抢答人工已经打开会话正在打字AI 把下一条消息回掉了或者反过来人工处理完走开AI 永远沉默等一个不会来的交还指令。解法是一个带锁状态的调度协议# 三个进锁入口 on_manual_click(): lock(人工接管, 60s) on_human_reply_seen(): lock(人工刚回复, 300s) # 重复检测到续期 on_hard_case_signal(): handoff_enqueue(); lock(60s) # 调度循环每一轮 if now locked_until: mark pending_soothe # AI 不回但记下这里有个被晾着的客户 if wait soothe_threshold: send_soothe_script() # 安抚话术 return skip # 锁自然到期 自动交还 AI人工点结束 立即交还两个时长都是吵出来的人工接管早期配 30 秒客服切出去查个订单回来发现 AI 已经开始抢答——定到 60 秒真人回复检测的锁给到 5 分钟因为真人的处理节奏是按分钟算的。进锁后 AI 沉默期间客户不能没人理所以有安抚轮询交还必须是自动的指望人工每次点结束不现实。还有一个补上的洞疑难入队早期不上锁结果入队后的下一条消息被 AI 抢答——等于人工队列里躺着任务、客户那边机器人已经乱承诺了。教训入队和进锁是同一个动作的两半缺一半就是链路缺口。3.4 面板恒空事故数据契约有一次值班反馈待人工面板是不是坏了从来没内容。查下来写入侧存的接管状态值是human面板查询条件里写的是manual——一个单词的偏差面板已经空了半个多月。期间没有任何报错没人发现是因为所有人都默认没有任务是正常状态。这类 bug 在 Agent 系统里以隐蔽著称程序不崩、日志不红只是两个模块各信各的。修复分三层枚举值收进一个定义处、读写共用同一个访问函数、加一条入队后必须查得到的端到端测试。这条测试本身只有几行但它看住了所有类似的未来偏差。四、幂等与去重指纹账本与单飞闸连坐去重与幂等的通用原理键选输入侧、先占位再处理、落盘原子替换我在前篇《消息去重与幂等》里已经拆过这章只讲这套客服链路上后修的三件事文本变体、连发消息、跨会话污染。4.1 变体判据精确指纹之外的第二道同一句话在不同时刻被系统读到的文本可能带细微差异——一个字没变还好问题是消息经过若干环节后会产生抖字内容明显同源字符串却不相等。精确指纹对它是失效的于是账本之外加了一层变体判定def same_message(a, b): if len(a) 10 or len(b) 10: return False # 短句不参与,防误伤 overlap 字符多重集重合率(a, b) # 而非编辑距离 return overlap 0.55 and length_ratio(a, b) 1.5三个条件同时满足才判同一条。短句不入变体判定是血泪——“在吗”好的这类高频短语互相之间重合率天然高一律比对会把新消息误杀。这条判据的定位要说实话它是降损不是根治。一段真被改得面目全非的同源文本它接不住。所以主次关系不能倒——精确指纹是主判据变体判据只在指纹 miss 之后补一次。这个顺序一旦倒了误杀率立刻抬头。4.2 连发多条消息三个死点一次很典型的复合事故。买家连发三条消息中间夹着语音系统只回了头一条后面几条永远沉底。翻真机日志复原出三个互相掩护的死点死点一取消息的函数只盯靠末的那条。增量逻辑永远返回当下新到的单条上面那几条从来没进过任何判断。修法是补一个全量提取函数再配一个纯函数选靶从新往老扫遇到账本说回过的消息当边界就停取边界内靠前未回的那条越早到越优先账本为空则整轮放弃不翻旧账候选数封顶六条。纯函数的好处是可以拿任意历史现场回放测试。**死点二单飞闸连坐。**闸按一轮处理记账一轮里还躺着未回消息时它也把这一轮记成已完成下一轮直接早退。修法是改记账条件**只要本轮之后仍有未回消息就不记账。**账的粒度从这一轮触发上移到这个会话是否干净。**死点三排干不能指望触发器。**直觉上剩下的消息会各自触发下一轮。实测连发时并非每条都产生触发事件有合并、有去抖。必须在一轮触发内自己排干给一个排干预算单次触发连处理四轮封顶然后交回调度。这里要专门说单飞闸的教训。闸的语义是同一会话同一时刻只跑一轮本身没错——错的是记账粒度跟着轮走。**锁太粗的故障不炸是整条整条地吞。**这类静默丢失比崩溃贵得多因为没有任何告警指向你。4.3 按会话卡状态跨会话污染系统替多个店铺轮询未读、逐个切进去处理切换过程中账本曾串味处理 B 会话时把 A 会话的旧未读消息一并回了。根因是账本按容器标识记账——窗口标识、进程号这类物理 ID而它们和会话没有对应关系切会话时物理容器是复用的。修法是让账本记录归属会话身份命中判据变成双条件内容指纹相等且归属会话与当前会话一致。抽象成一句规则**凡是语义上属于会话的状态必须以会话身份为键物理容器标识永远不是会话。**这个洞在单机单会话时永远不会暴露多店铺轮询一上就炸。4.4 逐条回复的取舍让位给体感预算的工程让步4.2 那套选靶排干是为了实现连发逐条回。有人问过为什么不把多条消息攒齐了合并成一条综合回复——从工程角度合并回复 token 更省、上下文更完整、去重面更小几乎全面占优。业务侧拍板的理由非常具体连发本来就是小概率事件而某些消息类型语音的预处理本身耗时为了等攒齐让头一条消息的等待时间不可控地拉长损失的是确定的时延去赌一个少见的优雅。这笔账算下来逐条。这件事留下的方法论是遇到方案分歧先算两条曲线——每个方案的等待时间分布和漏回风险量化出来放到做决策的人面前。五、长期记忆知识库的投喂、清洗与边界5.1 自动补全商品资料以及它的确认闸七个端的商品上下架非常频繁纯靠人工录资料喂知识库更新速度永远追不上。学习链在业务上只做一层事情定期从店铺自己的后台把商品标题、价格、属性、发货信息拉下来生成候选资料条目。关键设计是候选之后必过待核对这道闸自动学习产出的是资料不是结论。首批从各端拉回三十七条商品记录三十六进入待核对队列人工逐条确认后才成为回复依据。这个比例恰好说明了闸存在的意义——抓取本身很能干但价格改没改、活动挂没挂只有店主知道。自动学习解决覆盖率确认闸解决正确率两者不可互相替代。5.2 复读事故以及模型被喂料的四条通道一次很难忘的事故买家问有优惠吗机器人反复回复一句和当前语境毫无关系的这张图 23:35……像背自己的梦话。排查模型从哪学到了脏东西时发现往模型嘴里喂料的通道不是一条是四条知识库资料、检索建议、历史存储以及常被忽略的——会话上下文。它是那条隐藏通道每一轮对话前几轮记录都会拼进请求。前三条洗干净了只要脏样本还躺在对话上下文里模型照样复读。修复是一套组合闸先定义资料脏指纹带时间戳的转述类文本、系统通知模板这类明显不是商品信息的形态然后入口与出口双保险——写入通道时拒收脏指纹拼装请求时再过滤存量收尾加开机自愈任务清历史且清理动作设计成幂等第二次跑清洗数为零。5.3 把服务红线写成常量表不写成运气红线清单和人格、风格一起按固定位次进系统提示人设 → 服务目标 → 风格 → 安全红线 → 资料 → 买家消息。清单节选红线防的事故形态禁止主动带入无关话题模型对半句话自由联想短消息不得重复追问连续三条请问需要什么帮助轰炸售后不得变相承诺应该可以退这类要兑现的暗示不得给出权限外承诺替店主应承补发、补偿回复不得触碰隐私信息从资料里带出手机号等字段无法回答必须输出超范围信号强行编造答案位次和清单都要当常量管理改动走版本——它是行为契约不是文案措辞随手一润线上行为就漂移一次。5.4 “100% 必回”产品决策如何反向改写架构链路上曾有一个对面身份预判模块用节奏启发式猜当前会话对面是不是真人防止机器和机器对聊。它经历了完整的三级跳后被整体删除。头一回判疑似就拦截不回复交人审。很快被否——误拦的代价是真人客户被机器人冷处理而误拦率压不下去因为启发式本质是在猜。第二回改成只弹提醒不拦截。还是被否——提醒没人看得过来等于没做。第三回代码零残留全部移除。产品侧给出的铁律只有一句优先保证对面每一条消息 100% 得到回复。于是链路上留下两条互相制衡的纪律一方面必回是底线去重判据宁可松偶尔重复回不可紧漏回另一方面必回不等于硬答——知识库超范围时不许编信号进守卫、守卫转工单或人工沉默永远有兜底接住。看似矛盾的两句话合起来才完整每一条消息都要有去处但不是每一条都要模型开口。模块从拦降级为不拦链路分支复杂度肉眼可见地下降。这个结论工程上很难独立得出因为它本质是在两类错误之间选权重——误拦真人被晾与误放回复了机器业务对前者的容忍度接近于零。教训抄下来当判据本身不可靠时把架构向错误可补救的方向倾斜而权重的选择权在背投诉的人手里。六、全链一览与指标6.1 一条消息的生命周期到达 - 指纹占位(processing) - 选靶(连发场景) - 四档路由 - [AI档] 生成 - 清洗三闸 - 长度闸/拆气泡(≤2) - 避让锁检查 - 发送执行 - 采样校验 - 记账(done) - 指标上报 \- [红线] 守卫 - 工单/转人工 - 真实转接 or 面板兜底 - 锁 - 交还 \- [超范围] 兜底话术 建任务6.2 指标看板指标定义目标异常意味着什么必回率被处理消息 ÷ 全部来量100%红线去重误拦或链路挂死首响中位数到达至首条回复规则毫秒级 / AI 秒级模型往返变慢或排队重复回复率同义连发对数≈0指纹与变体判据漏复读率同模板在会话内重复≈0喂料通道有污染转人工占比人工入队 ÷ 来量缓降但不压零降太猛可能守卫失效转接降级数转接失败转面板只看不压端形态变了适配该跟上七、FAQQ1为什么不微调一个模型直接覆盖前三档还省得维护规则表A规则档的价值不在模型能力在确定性与毫秒级。改价格话术规则档改一行配置当天生效微调模型要攒样本、重训、回归两周起步而且没人能保证它不把发货时效说顺嘴。规则表是负债没错但它是一笔看得懂、改得动、不会半夜行为漂移的负债。Q2重试上限到底怎么定A按失败性质分三类瞬时失败网络抖动给有限重试确定失败被明确拒绝的调用重试预算为零直接走降级未知失败补一次即止、然后中止上报。真正致命的是未知失败配无限重试——它等价于把一个假成功判据放大成雪崩。上限数值2 次、1 次、4 轮全是实测出来的照抄没有意义但在设计期枚举这个动作必须照抄。Q3转人工占比高是不是说明 Agent 做得失败A反过来。转人工占比是这套架构的体温计它应该下降但压到接近零一定是病——要么守卫在失效让模型硬答要么人工队列漏了。健康的曲线是长尾问题持续回流成知识库资料占比缓降。Q4小团队、日几百条的量需要照搬这四档和排干预算吗A不必全套。优先级排序分档路由、输入侧去重、转人工兜底、资料确认闸——这四件是骨架量再小也要有变体判据、排干预算、跨会话卡状态这类是特定事故教出来的事故没发生就先不建留好日志现场即可。Q5知识库喂了错资料导致错回复责任怎么闭环A技术上靠确认闸与清洗指纹挡大头流程上必须留这条回复引用了哪条资料的痕迹。出过事回放资料出处比回放模型输出有用得多——模型不会错得离谱它只会忠实地复读你喂给它的错误。收尾总结复盘下来真正跑住的五个模式没有一个来自论文四档漏斗加哨兵、写与做的切分、转人工的两级语义、指纹账本加按会话卡状态、带确认闸与四通道清洗的知识库。它们共享两条更底层的原则。**原则一每个失败点都要有设计期就存在的去处。**发送失败去降级、转接失败去面板、超范围去守卫、误判方向选可补救的那边。Agent 系统的鲁棒性不是模型给的是错误不悬空这条纪律给的。**原则二判据不可靠时权重的选择权交给业务。**工程上我能证明两个方案各有代价但哪边代价更大不是技术命题。把等待曲线、误判率、漏回率量化着摆上桌让每天看投诉的人拍板再把他拍的方向翻译成拦与不拦“记与不记”“锁多久”。再说一句不讨喜的话热点文章里的架构图通常画的是应该长什么样这篇里写的是为什么会长成这样。中间那些哨兵、上限、确认闸、降级队列单拎出来都平庸但少了哪一个线上都出过事故。Agent 架构的壁垒不在模型选谁而在这些笨办法有没有一次一个地补全。相关阅读《LLMOps发布原子不是模型是提示词/语料/模型三元组》《消息去重与幂等从指纹账本到布隆过滤器的三级防线》《大模型技术全景AI Agent 的架构与设计模式》本篇的对标理论篇