
这个系列写到第9篇Agent框架这部分总算要收尾了。上一篇我们把框架的基础讲完了什么是Agent、模型接入和工具调用在框架里怎么配合、一个单Agent怎么跑起来。如果你只是想让Agent“能跑通一个demo”上篇的内容其实已经够用但如果你和我一样是把Agent塞进真实业务流程里——订单处理、售后答疑、数据分析、知识库问答——那这一篇才是要反复翻的。这篇不聊概念全是工程上绕不开的硬骨头核心有五块多Agent协作怎么编排、记忆和状态怎么不“失忆”、工具调用怎么保证稳定、上线之后怎么观测排查、以及从demo到生产环境的最后一公里怎么走。适合谁看已经跑通过至少一个Agent任务、准备往真实业务上推的人。第一次接触Agent的朋友建议先把上一篇基础看完再来否则很多坑你还没有概念。1. 多Agent协作从单兵作战到流水线调度1.1 为什么真正上线的系统很少用“超级Agent”先放一个结论真正落地的Agent系统绝大多数不是由一个超级Agent包办所有事而是多个专职Agent分工协作。原因有三个。第一上下文撑不住。一个Agent既要做意图识别又要查订单又要生成售后话术所有工具和业务背景都得塞进同一个系统提示词。我见过最夸张的项目系统提示词写到一万多token每次请求光提示词的消耗就比答案本身还贵。更麻烦的是任务混在一起之后模型注意力会被摊薄经常出现查订单的时候还在琢磨话术语气的串味情况。第二职责耦合导致回归风险。你只是改一个售后环节的提示词结果下单环节的行为也变了因为它们在同一个上下文里相互影响。一旦出问题你很难定位到底是哪段提示词导致的行为漂移。第三token白白浪费。每一轮都要把全部工具定义和业务背景带上真正用到的只有两三个。拆成多个Agent之后每个Agent提示词短、工具少、职责单一模型行为可控性明显提升。这里可以打个比方一个全能店员既要收银又要理货还要处理售后高峰期一定出错超市的做法是分岗位每个岗位只背自己那一块的SOP。多Agent本质上就是给Agent分岗位让每个岗位变得简单且专注。1.2 三种编排架构按业务形态选多Agent怎么排不需要那么多花活常见的就三种顺序流水线、中心化调度、辩论竞合。选型不看出身看任务结构。架构模式工作方式适用场景主要风险顺序流水线上一步输出是下一步输入流程固定、环节清晰比如意图识别到查单到话术生成错误会逐级传导中心化调度一个Planner拆任务发给Worker再汇总任务多变、需要动态规划Planner单点瓶颈拆错任务全盘错辩论竞合多个Agent独立作答仲裁者选中答案高价值问题、答案质量优先成本高、耗时翻倍顺序流水线最简单适合稳定的流程但每一级之间要有校验。上一级输出如果是结构化JSON还好如果是自然语言下一步的输入会很脏所以要在这里加一道字段提取的小逻辑而不是把文本直接往下传。中心化调度是目前最常见的形态。一个Planner负责拆解任务、下发Worker、回收结果再自己汇总。Planner的提示词质量基本决定了这个系统的天花板它的风险在于拆出来的子任务可能根本做不了。我在实际项目里遇到过Planner把一个“查天气”的任务拆成“获取用户位置”和“查询天气接口”两步看起来合理但第二步的接口参数它自己都填不对最后分支失败。所以中心化调度一定要设计兜底分支某个子任务执行失败时Planner要能重新拆解而不是把错误原样甩给用户。辩论竞合模式成本最高一般2到3个Agent就够不要搞五六个。我第一次做的时候让5个Agent各答一遍再由仲裁者选多出来的两个Agent并没有带来质量提升反而经常被仲裁者选中一个“看起来全面”但实际在编造细节的答案。后来给仲裁者加了评分规则要求答案里必须引用工具返回的原始事实这才把幻觉压下去。选型一句话总结流程固定的先上顺序流水线需要处理开放需求的再加Planner只有答案直接影响客户钱的才考虑辩论竞合。1.3 可落地的多Agent配置与数据流向给一个简化的编排配置示例。这个示例不依赖特定框架你可以在自己用的框架里找到对应的抽象。# 以类配置方式定义一个简化的编排router - workers - summary from agent_framework import Agent, Pipeline router Agent( nameintent_router, instruction判断用户意图输出严格JSON{\intent\: \order_query|policy_query|chat\}, tools[], ) order_worker Agent( nameorder_worker, instruction根据用户问题查询订单状态只使用order_query工具禁止回答其他问题。, tools[query_order], ) policy_worker Agent( namepolicy_worker, instruction根据用户问题检索售后政策只使用policy_search工具回答不要夹带业务判断。, tools[policy_search], ) summary_agent Agent( namesummary_agent, instruction把各worker返回的结果合并成一段自然语言给用户不要添加任何额外信息。, tools[], ) pipe Pipeline() pipe.add_router(router, keyintent, routing{ order_query: order_worker, policy_query: policy_worker, chat: None, # 直接结束不调用工具 }) pipe.add_last(summary_agent)这个示例的要点不在代码而在数据流。router只做分类不携带业务上下文worker只接收“任务描述加必要的业务参数”不接收完整对话历史summary只合并结果。整个链路做成单向流动排查问题的时候从开头看到结尾走到哪一步出错一眼就能定位。我踩过的坑第一次做多Agent的时候顺手把整个对话历史塞给每个worker结果三个Agent的回答都在重复用户开头那句话上下文全部污染。后来就强制规定下发任务时除了任务描述最多再带三个必要字段不能再多。给新人的建议是先用一个Agent把核心流程跑通一定要用多Agent就先从这种顺序加路由的模式开始。一上来就上Planner加辩论大概率两个月后连自己都看不懂日志。2. 记忆与状态Agent的“半条命”2.1 四层记忆分开设计先说一个判断Agent丢记忆大部分不是模型不行而是架构上没把记忆分层。推荐至少分四层处理。短期对话记忆当前会话的上下文相当于白板写完就能擦。工作记忆当前任务执行到哪一步、已经拿到了什么数据相当于挂在工位上的工单。长期记忆跨会话沉淀的事实、用户偏好、业务规则相当于档案柜。外部记忆知识库、数据库相当于图书馆。很多框架把这四层混在一个history对象里短期任务没问题业务一复杂就乱。最需要区分的其实是短期记忆和工作记忆短期记忆存的是“用户说了什么”工作记忆存的是“任务做到哪一步”。如果把中间步骤全塞回对话上下文上下文很快就会爆而且模型容易被中间数据带偏。常见的一个失败场景是Agent要生成一份数据分析报告中间步骤算了二十几个指标这些指标全被写进上下文结果模型在最后总结时开始惦记中间某个数字怎么算出来的反而忽略用户最初要的结论。正确做法是中间计算结果放在工作记忆里最后需要时再取关键字段拼进上下文。2.2 超长对话的淘汰与摘要策略上下文窗口不够用怎么办很多新手直接截断结果用户第一句话里提到的核心诉求被干掉模型后面对着一堆没头没尾的对话强行回答质量可想而知。标准做法是三级策略滑动窗口保留最近N轮原样数据超过阈值之后把更早的部分做摘要压缩关键的长期事实写入向量库按需取回。一句话概括最近的对话原文早期的对话留摘要跨越会话的事实进记忆库。max_keep_turns 20 # 最近20轮原始保留 max_context_tokens 8000 def build_context(session_history): recent session_history[-max_keep_turns:] older session_history[:-max_keep_turns] if estimate_tokens(older) 2000: return render(older recent) summary summarize(older) # 调用摘要Agent return render({_summary_: summary, history: recent})摘要动作本身也消耗token不能每次都做要设置触发阈值比如累计超过max_context_tokens的70%才执行一次。摘要提示词要单独写明确给出三个要素用户诉求、关键业务参数、未完成事项。不要拿系统提示词凑数否则摘出来的全是正确的废话。另外一个容易忽略的点摘要生成之后旧的原始记录不要立刻删至少要归档保留一段时间。摘要再怎么准也会丢细节排查线上问题的时候经常需要回头翻原始对话看用户到底说了什么归档是给自己留后路。2.3 状态持久化崩溃后从断点继续Agent任务不是一次请求就结束的。比如一个批量报表生成任务要跑十几个步骤跑到第七步模型接口超时了。如果没有状态持久化一切从头开始用户等不到结果体验极差。框架层应该支持把Agent的当前状态序列化保存至少包含session_id、current_step、collected结构化结果、retry_count和上下文摘要。恢复的时候从current_step继续而不是重新跑全过程。agent_state { session_id: sess_20240307_001, current_step: data_fetch, collected: {user_query: ..., order_id: M20240301}, retry_count: 2, }个人经验保存状态时一定要把已经获取的结构化数据原样保存不要只存一句“查询订单成功”这种自然语言描述。否则恢复时模型还得重新理解之前干了什么错误率会明显提高。最好把状态存储本身当作一个小数据库每次状态变更是一次update带上版本号防止并发覆盖。3. 工具调用的稳定性从“会调用”到“调不坏”3.1 工具定义质量决定模型不犯错Agent能不能正确使用工具首先取决于工具说明书的质量。在框架里挂一个工具不是堆上去就完事要把名字、参数Schema、错误返回三件事做扎实。名字要用动词加宾语比如query_order、send_reminder不要起名叫get_data或者foo这种含糊的名字。参数Schema要设好类型、必填、枚举值描述里写清楚参数语义。错误返回必须是固定结构比如字典形式的code加message不要一会儿返回字符串一会儿返回对象。tool def query_order(order_id: str) - dict: 查询订单状态。 Args: order_id: 订单编号必填。示例M20240307001。 Returns: 包含 order_status、recipient、purchase_time、tracking 的字典 查询不到时返回 {code: 404, message: 订单不存在}。 模型把参数填错绝大多数情况不是它笨而是你没告诉它参数该怎么填。我做过一个对比实验同一个工具在参数描述里加了“订单编号以M开头例如M20240307001”之后模型传参错误的次数下降大约一半。所以多花五分钟写清楚描述比事后加一堆重试逻辑划算得多。3.2 超时、重试与熔断缺一不可工具背后往往是真实业务接口接口一抖动Agent的连环调用会把问题放大。因为Agent可能在十秒内连续调用同一个接口好几次直接把弱接口打挂。三个机制一定要配齐单次调用超时最长等十秒超时算失败失败后最多重试两次并且要退避不要立即重试同一个工具在一分钟窗口内连续失败五次就临时熔断返回一个明确信号给模型。熔断信号的设计很关键。模型收到一个模糊错误比如直接的“500”它往往会换个参数再试几次造成死循环。更有效的做法是返回{code: 503, message: 工具暂不可用请勿重试改用其他方式或直接回复用户无法完成}这种明确的“不要重试”信号能让模型快速换路径。框架层同时设置连续失败计数器我常用的配置是单次超时10秒、最多重试2次、1分钟内连续失败5次熔断5分钟你可以根据自己接口的P95时延调整。3.3 模型执着于同一个工具怎么办模型调工具失败后非要用同一个工具换个参数继续试这是Agent落地中最常见的问题之一。控制手段有三层。一是全局最大工具调用轮数在框架层面硬限制比如一个任务最多调5次工具超了强制进入收尾。二是同一工具连续失败两次后由框架在下一次模型输入里附加提示“该工具连续失败不要再次调用”。三是在系统提示词里写明某个工具失败两次之后要么换工具要么如实告诉用户做不了不要反复尝试。提醒一句第三点只是辅助不要指望模型一定遵守框架层的硬拦截才是兜底。我见过一个真实案例Agent查询某第三方接口连续失败8次每次还把失败的返回拼进上下文费用烧掉正常会话20倍用户只看到一直转圈。加了最大轮数限制和熔断之后最多5轮就停了这个问题再没出现过。4. 可观测性没有追踪就别谈上线4.1 追踪一次Agent调用的完整轨迹Agent不像传统接口一次请求对应一次SQL。它内部有多次模型调用、多次工具调用、多次分支任何一个环节出错都要能被还原。每一条轨迹至少要记录trace_id、session_id、每个节点的输入输出摘要、模型名称、token数、耗时、工具名称、工具参数、工具结果、工具状态和单次调用成本。日志结构可以像这样{ trace_id: tr_20240307001, session_id: sess_20240307_001, nodes: [ {type: model_call, model: default_model, input_tokens: 1200, output_tokens: 300, latency_ms: 900}, {type: tool_call, tool: query_order, params: {order_id: M20240307001}, status: success, latency_ms: 200} ] }有一个省钱的方案工具返回结果可能很大全部写进日志成本高可以存前几百个字符加完整内容的哈希。排查需要原文时按哈希到离线日志里捞。另一个细节是所有日志都要有关联ID从用户请求进来的那一刻开始生成按这个ID串起所有Agent和工具记录。没有关联ID可观测性就是空谈。4.2 成本可视化搞清楚钱花在哪了模型调用成本大头往往不在最后生成回答那一下而在中间反复的工具调用和越来越长的上下文。建议按三个维度统计会话维度、Agent维度、工具维度。统计维度核心指标用于发现问题会话维度总token、总费用、调用轮数找到“烧钱会话”的共同特征Agent维度各Agent调用次数与token占比定位提示词过长还是循环调用工具维度工具调用次数、失败率发现工具缺陷或模型误导有一个真实案例我印象很深某个会话的费用是正常会话的20倍排查后发现Agent在循环调用一个查询接口每次把返回结果拼进上下文上下文越长后续输入token越高形成正反馈循环。这个问题不是靠换模型解决的而是靠可观测性先暴露出来加上轮数限制和上下文衰减策略才修掉。4.3 评估集给Agent建一个自动化测试基线提示词手调一次看着一两个例子感觉不错这不叫验收。线上行为可能随后台改动漂移没有回归测试根本发现不了。评估建议覆盖四个维度答案正确率、工具调用准确率、格式合规率、成本与延迟。每个业务场景准备至少50条有代表性的问题作为基线覆盖正常场景、边界场景、干扰场景。每次改提示词、换模型、改工具配置先跑一遍基线的通过率通过率下降超过5%就要把改动拉回来再审。这个习惯一开始很烦但坚持下来价值巨大。我把Agent交付给业务方的时候手里有基线数据业务方也会踏实很多至少能证明改动不是拍脑袋。4.4 日志里最值得盯的四个信号线上日志里这四种信号出现时不要放过工具连续失败多半是外部接口有问题也可能模型在反复尝试同一个工具空回复或敷衍回复用户明明有明确诉求Agent却回了几句“嗯嗯”就结束多半是意图识别把任务归到了聊天单会话token异常远超平均值要检查上下文是否没裁剪、是否有循环调用上下文接近上限的会话说明记忆管理策略没起作用得检查摘要触发条件。这四个信号背后对应的是我们在前面章节讲过的四类问题工具熔断、路径兜底、上下文管理、记忆策略。出现任何一个都要回trace找根因而不是只把当前会话清了继续跑。5. 生产落地把Agent塞进业务流程才是开始5.1 并发与限流队列比想象中重要Agent任务对后端的压力不是线性翻倍的。用户发一个请求进来它可能内部发起五六次模型调用加两次工具调用一个用户的请求相当于传统接口的五六倍压力。生产环境要做三件事入口排队化用户请求先进队列不直接压到模型服务单用户并发限制同一个用户同时最多跑两个Agent任务防止有人恶意刷接口模型调用做令牌桶限流避免高峰期集体冲破模型服务商配额。给不同任务设优先级也很实用。用户在线等待的查询类任务优先处理批量生成报表和离线分析类任务放到深夜低峰跑。这样既保证用户体验又错开成本集中时段。5.2 人工审批不要让模型独自承担高危动作退款、发短信、删数据、批量导入导出这些动作不能让模型全自动执行即使它的意图是善意的。原因很简单模型判断不了合规责任出了事也不能拿“它自己决定的”去解释。设计上Agent只负责生成“待审批动作”流程进入pending_review状态由有权限的人确认后再放行pending_review - approved / rejected审批不能沦为形式。我参与过一个批量邮件项目让Agent先生成全部邮件草稿业务人员在只读界面预览审核抽查没问题后再发送。这比“模型直接发出去出了事再补救”安全太多。工具定义里也要把危险操作拆开一个工具负责生成草稿另一个工具负责执行发送。执行发送的工具绑定审批状态没审批不执行。不要给模型开放一次性执行全部动作的能力。5.3 多环境分离与提示词版本化开发环境、测试环境、生产环境要严格隔离工具配置、提示词、Agent编排图都纳入版本管理。发版时按版本号拉取配置而不是直接在线上环境改。我有一次直接在线上环境改提示词调Bug调完没记录第二天线上行为完全不可解释最后花了两小时翻对话日志才找到原因。从那之后所有改动都走配置仓库留痕可回滚。提示词版本化不是把文本塞进Git就完事最好连触发条件一起版本化什么版本的系统提示词、什么版本的工具定义、什么版本的模型组成一个不可变快照。排查问题时直接看快照信息不用再问“之前线上跑的是什么提示词”这种悬案。5.4 缓存与复用不该烧的钱一分也不烧用户问重复问题很常见前面已经回答过类似问题下次完全不必再走一次完整的Agent链路。入口做相似度缓存把用户问题向量化和最近一段时间的高频问题做相似度匹配超过阈值直接复用历史答案。工具结果也可以做短时间缓存同一个订单号五分钟内重复查直接返回缓存结果不调外部接口。有两个禁区要牢记涉及实时价格、库存、用户个性化权限的数据不能乱缓存缓存一定要设置有效期不然会长期把旧数据当新数据用。把缓存当成一个带TTL的组件来设计不要无限缓存。省下来的成本很可观但引入的数据一致性问题也要有预案。某个环节数据更新了该失效的缓存要立即失效不能等TTL自然过期。6. 常见问题与排查技巧实录6.1 典型症状速查表把我在真实项目里遇到的五类高频问题整理成一张表遇到类似症状可以直接对号入座。症状排查思路常见根因推荐解法Agent陷入循环看trace里工具调用是否同一工具重复工具错误信息模糊模型认为还有机会最大轮数熔断明确错误提示多个Agent串话看状态存储是否共享了同一份上下文把完整对话历史塞给每个worker按session隔离上下文只传必要字段用户说“你好”也触发一堆工具看路由Agent的分类结果没有拒识路径所有输入都被当作任务加chat兜底分类直接回复不调工具记忆检索结果不相关看检索相似度分数阈值设太低低相关文本也注入上下文提高阈值加时间衰减权重并发返回别人会话的数据检查状态对象是否全局共用状态用了单例每次请求新建状态上下文随便展开一个案例订单查询Agent回答里出现了另一个用户的名字。第一反应是模型幻觉后来加了trace才发现是路由Agent把上一个会话的上下文一起传给了worker状态对象用了全局变量。这个教训说明Agent系统里的Bug往往不是模型问题而是数据流问题。排查方向一定要先看数据是怎么流的再看模型有没有犯傻。6.2 排查方法论三条铁律排查Agent问题我有三条经验。第一条第一步永远看trace不要凭感觉猜。Agent的行为是概率性的但日志里的每一步都是确定的看trace能快速锁定是该看数据流、工具调用还是模型输出。第二条第二步要复现。用线上同一条输入在测试环境里跑逐节点加日志通常几轮就能定位。不要直接在生产环境改东西试风险太大。第三条根因尽量修在框架层不要只靠改提示词。提示词容易漂移换个模型可能就失效而框架层的轮数限制、熔断、状态隔离与具体模型无关稳定可靠。最后聊点个人体会。到我写这篇为止Agent框架这部分总算闭环了。我做Agent项目的最大感受是框架只是脚手架决定系统能不能活下来的是工程细节——状态有没有持久化、工具调错了会不会兜底、上线后能不能说清楚每一笔token花在哪。demo跑得漂亮和线上稳定运行之间隔着的就是这五类工程问题。如果你正准备往生产推我只有一个建议从最小可用链路开始。先一个Agent跑通核心流程再加工具、加记忆、加第二个Agent每加一块就回归一遍评估集。不要一上来就组五六个Agent开会那种架构画图很好看排查问题起来是真的会让人头大。这篇把我在真实项目里踩过的坑提前标出来了希望你们能绕过去。