上一篇文章里,我搭过一个客户工单多智能体系统:质检智能体、摘要智能体、路由智能体三个角色协作处理工单。第一次跑通很兴奋,第二轮就发现问题了——摘要智能体和质检智能体给出的结论经常不一致,A看到的证据和B看到的证据对不上,我把提示词改了七八版,该冲突照样冲突。后来我才意识到,问题不在于提示词怎么写,而在于上下文在智能体之间流动时,缺少一套透明的机制来约束它该从哪来、该去哪、什么时候失效。这个认知转变,是我写这一篇的动机。
这一篇专门聊多智能体系统上下文工程:为什么必须超越提示词层面,上下文与推理的透明架构引擎到底指什么、核心组件怎么设计,以及落地过程中我实实在在踩过的坑。内容会偏底层一些,适合正在做多智能体编排、推理链路治理、Agent框架设计的工程师,也适合刚接触Agent但已经被上下文问题折磨过、想搞清楚根源的同学。
1. 提示词摆不平的东西:上下文必须独立出来
1.1 提示词在单任务上是利器,在多智能体协作里成为瓶颈
先给结论:提示词工程解决的是"单次任务中,如何让模型更准确地执行指令",它默认上下文是给定的、静态的、可控的。这个假设在单轮对话或简单RAG场景里成立,但在多智能体协作里会迅速崩塌。
为什么?因为多个智能体协作时,每一个智能体都需要知道其他智能体"做了什么、结论是什么、依据是哪份材料"。这些信息属于运行时动态状态,如果全部靠提示词文本传递,会撞上几堵实墙。
第一堵墙是上下文线性膨胀。每多一个交互轮次,每个智能体下一轮都要重复携带上一轮的全部状态。协作链一长,Token消耗和模型注意力衰减一起恶化,最后系统为了塞进窗口,只能粗暴截断历史。
第二堵墙是线性文本装不下网状依赖。多智能体协作本质上是一张信息依赖图:这个结论依赖哪个事实、那个事实又被谁引用、谁在什么时候更新了什么。提示词是一条按顺序排列的文本流,引用关系、优先级、时效性在里面全都表达不清。
第三堵墙是可归因性变差。输出一旦出错,你分不清是提示词引导问题、是历史信息污染判断、还是某个智能体的结论被另一个智能体错误引用。提示词变成夹带所有状态的黑洞,越调越玄学。
所以我的结论很直接:上下文必须从提示词里拆出来,独立成层。提示词只负责"告诉模型怎么处理上下文",而不负责"把上下文塞进文本里"。
1.2 拆分上下文:环境、协作、私有三层分离
独立出来的上下文层,第一件事就是分层。我在项目里常用的拆分方式是三层:环境上下文、协作上下文、私有上下文。
环境上下文由系统注入,描述任务目标、可用工具、权限边界、当前时间、外部约束等。它通常不随对话轮次变化,或者变化很慢,属于"全局常量"。
协作上下文放在共享区,是所有智能体都可见的状态。比如当前任务进度、已确认的事实列表、待对齐的问题、分工安排。它是协调多方行为的关键,所以必须结构化、必须定版、必须能追踪改动人。
私有上下文是一个智能体内部的推理过程、临时猜测、部分结论。其他智能体不可见、也不能访问。把这个区分出来之后,上下文总量不会随着智能体数量线性爆炸——你完全不需要把每个智能体的思考过程广播给所有人,大家只共享最终结论和必要状态。
这三层分离给我最大的收益不是省Token,而是分工边界变清晰了。每个智能体维护哪些上下文、不该看哪些上下文,变成了像模块接口一样明确的东西,而不是靠自觉或者靠提示词里写一句"请忽略无关信息"。
2. 多智能体上下文的五大现实问题到底怎么治
2.1 上下文吞噬:从窗口爆炸到关键信息截断
做过Agent开发的人肯定有经验:系统跑几轮之后,上下文里层层叠叠堆着模型输出、工具返回、用户最新消息、历史消息摘要。为了塞进窗口,只能往回删,删着删着,一个对当前决策至关重要的早期事实,可能就被当成旧消息截掉了。
这个我管它叫"上下文吞噬"。很像一个仓库只知道堆货,不做仓位规划,最后要用的时候,真正有用的货反而被压在深处。
治理方式是为每类上下文定义优先级与占用上限。环境上下文优先级最高,常驻不动;协作上下文按"最近使用加当前任务相关度"做滚动保留;私有上下文默认随任务结束清空。这套规则本质上是给上下文做分仓管理,没有分仓的上下文工程,最后一定会退化成长文本拼接。
2.2 上下文冲突:多个信源各执一词,系统不知道听谁
多智能体系统里最常见的翻车场景是:一个智能体从历史对话里读到某个结论,另一个智能体从数据库查询里读到相反结论,两个信息同时进入上下文。模型基本不会自己发现矛盾,它更可能直接采信其中一方,然后顺着错误方向推理,甚至会把两个矛盾结论都保留下来,在最终回答里"和稀泥"。
这个问题不能靠提示词解决。你得在上下文写入阶段做冲突检测:冲突信息进入共享区之前,必须带上信源类型、采集时间和置信度,上下文服务检测到不同来源的结论不一致时,把冲突部分单独拎出来,交给人工或专门的裁决智能体处理。关键点是,冲突检测要发生在上下文写入阶段,而不是等到提示词组装阶段才发现。
2.3 上下文漂移:过时信息还在影响决策
另一个高频事故是:某个结论在时间点T1成立,到了T2,因为外部数据更新已经不成立了,但上下文里没有任何失效机制,系统继续拿旧结论做推理,还衍生出一串错误动作。这就是上下文漂移。
我现在的做法是给上下文加"时效域":每段上下文登记valid_from和valid_until,同时登记它依赖的事实版本。一旦底层数据发生版本变更,所有基于它的结论自动降级为"待验证"。这个机制比在提示词里写"注意使用最新信息"可靠得多——提示词没有能力约束系统底层的状态更新逻辑,但数据版本是上下文服务可以直接感知的。
2.4 死链引用:推理结果没有来源凭证
多智能体协作里,一个智能体给出的结论经常被另一个智能体直接引用。如果这个结论没有附带来源凭证,这个引用就是死链。最终解释链里会出现"根据前面的分析"这种话,但没有任何人能说清,前面的分析到底出自哪个智能体、哪个数据版本。
我在上下文模型里强制要求,每个共享结论必须带source_ref,它由三部分构成:产生结果的智能体ID、输入上下文版本ID、置信度。有了source_ref,错误定位就不需要把整条链路重跑一遍,可以直接回溯到最原始的触发点。这个机制是后面谈推理透明化的基础。
2.5 遗忘失控:没有衰减机制的上下文只会腐烂
最后一类问题最容易忽略:上下文只增不减。模型窗口有限,就算物理上没超,注意力也早散了。长上下文并不是"都看得见",恰恰相反,越后面的信息对结果影响越大,早期信息被严重淡化。
所以上下文工程里必须设计遗忘策略。不是把所有历史都删掉,而是分类处理:已经定版且不再变化的信息转存到持久层;临时的中间状态标记为可过期;对当前任务没有直接影响的碎片不再进入在线上下文。如果之后可能用到,就交给外部检索系统按需召回。总结成一句话:在线上下文要保持尽量小,记忆放在外部,要用的时候再捞回来。
3. 透明架构引擎的核心:上下文状态机与引用模型
3.1 一条上下文从出生到废弃的状态机
把上面那五类问题的治理方式落成代码,透明架构引擎的核心就出现了。我采用的方案里,每条上下文都有一个状态机,总共四个状态:
| 状态 | 含义 | 可引用性 |
|---|---|---|
| draft | 起草中,信息还未经确认 | 不可参与推理 |
| active | 生效中,可正常被各智能体引用 | 可参与推理 |
| archived | 已归档,不再参与推理,仅保留审计记录 | 不可参与推理 |
| invalidated | 曾生效,但被判定不可信 | 可被引用,但必须打"引用作废上下文"红标 |
这里最关键的是状态转移规则:只有两条合法迁移路径,active到archived,active到invalidated。禁止对active状态的上下文原地修改内容。要更新,就必须生成新版本,旧版本只能走archived或invalidated。
这个设计看起来简单,却是消除上下文混乱的关键。它把"改信息"变成了"换版本",所有智能体在任何时刻都在跟某个固定的版本对话,而不是面对一份随时在变的文本缓冲。简单说,就是让共享上下文具备数据库事务那样的隔离性和可追溯性。
3.2 定版与发布:共享区里的信息必须全员一致
在协作上下文里,我还引入了"定版发布"的概念。一个事实要进入active状态,必须经过一定数量的确认,否则它只能停留在draft,不能作为模型的推理依据。每一条active上下文都记录版本号和发布时间,引用方必须用fact_v12这种引用方式,而不是模糊地说"前面那条消息"。
这个机制的收益,是显著降低两个智能体同时看一段历史却得出相反结论的概率。当双方需要对齐时,它们对齐的是同一个fact_v12,而不是各自基于不同理解拼接出来的历史记忆。多智能体协作最怕的就是A以为B知道A知道的事情,结果两边掌握的事实版本根本不一样。
3.3 source_ref与上下文血缘:从结论回溯到源头
前面提到source_ref,这里展开讲。完整设计不只是一个字符串,而是一张上下文血缘图。每条active上下文除了记录自己的内容,还要记录它引用了哪些上游上下文、被哪些下游结论引用。这样一个多智能体系统跑完一次任务后,你看到的不是杂乱的日志,而是一棵清晰的推理树:根节点是原始数据,中间节点是各智能体的中间结论,叶子节点是最终输出。
血缘信息最大的价值在Debug阶段。比如一个工单被错误分级了,你可以顺着血缘图一层层往下查,很快找到是哪个智能体在哪个版本的数据上做了错误判断,而不是靠运气翻几十条日志碰运气。
3.4 从上下文服务到透明架构引擎
把状态机、定版发布、source_ref、血缘关系整合起来之后,你得到的就不再是一个"上下文数据库",而是一个透明的架构引擎。它对外提供四个接口能力:
- 写入接口:接收上下文并做冲突检测、时效校验、优先级评估,决定是否进入draft;
- 发布接口:把确认过的上下文从draft提升为active,并生成版本号;
- 查询接口:按版本、按引用关系、按时效条件精确取上下文,而不是全量塞给模型;
- 审计接口:输出血缘图和状态迁移记录,支撑调试和复盘。
这四个接口让每一个上下文相关的问题都可以被精确回答:这条结论从哪来?基于哪个版本?什么时候失效?被谁引用过?所有答案都可查、可追溯。
4. 推理透明化:不只看结果,还要看路径
4.1 追踪ID:一条ID串起所有上下文与推理调用
状态机和引用模型解决的是"上下文怎么管",但推理过程要透明,还需要另一个关键机制:全链路追踪ID。
具体做法是,在一次完整的任务中,入口生成一个trace_id,每个智能体的每次推理调用都带这个trace_id,同时记录parent_id。每个节点记录它消费了哪些上下文版本、输出了什么结论、引用了哪个上游节点。这样一来,整个多智能体协作过程本身就是一个有向图,哪里断链、哪里慢、哪里引用了错误上下文,一眼就能定位。
这个设计带来的直接好处是,最终输出可以附带一条剪枝后的因果链:只保留真正影响最终结论的节点和引用关系,直接展示给用户。用户不再面对黑箱结果,而是能看到"工单被分级为高优先级,因为质检智能体在fact_v12中发现客户明确提到生产事故,该事实的置信度为0.92"。这种解释能力,是提示词工程不可能提供的。
4.2 推理日志不是只记结果,要记过程
很多人把推理日志设计成"输入一条、输出一条"的JSON,这基本没有Debug价值。透明架构引擎要求日志记录推理期间的切片选择过程:
- 模型实际输入了哪些上下文片段;
- 哪些片段在注意力计算中权重最高;
- 模型输出的中间推理步骤里,每一步引用了哪个版本的事实;
- 置信度评估是什么;
- 采样参数、时间戳、模型版本。
不要觉得这样日志量太大,真出问题的时候,一份带过程细节的日志,能帮你省下几小时的复盘时间。我自己就靠这样的日志,定位到过"某个智能体因为误读了一条过时的工具返回结果,导致整个路由决策偏移"的线上问题。没有过程日志,这类问题基本只能靠猜测。
4.3 推理引擎的优化要放在上下文治理之后
技术团队经常会花大力气对比各种推理引擎,比如研究vLLM、nano-VLLM这类方案的单卡吞吐、推理速度、显存占用,追求Token生成效率的极致。这当然是值得做的,但在多智能体系统里,我的经验是:推理引擎只是运行载体,端到端的质量瓶颈几乎都在上下文层。
我见过一个团队,把推理引擎的吞吐优化了一倍,但任务成功率一直上不去。一排查,发现智能体每次请求都在用旧版本的上下文,或者携带着大量与任务无关的历史碎片。上下文选错版本、带了无关碎片、缺失必要信息,这三个问题任何其中一个,都足以把推理引擎辛苦提升的性能浪费掉。
反过来说,先把上下文治理做对,引擎性能优化的边际收益才真正体现得出来。这也是为什么我一直强调,透明架构引擎要先于推理引擎的性能调优。
4.4 用因果链反推提示词的修正方向
推理透明化还有一个非常实用的场景:反推提示词该怎么改。没有trace时,改提示词是盲改,改完跑一遍,好坏了全靠运气。有trace后,你可以看权重较高的上下文片段和模型中间步骤,直接看出模型更依赖哪些信息、忽略哪些信息。
举个例子,我发现路由智能体在做工单分类时,高权重片段几乎全部来自用户最新一条消息,工单历史背景的权重很低。这说明提示词对历史信息的引导不够,模型天然更关注最近的输入。我调整了提示词结构,把历史事实列表前置,并显式要求模型先评估背景再响应最新消息,问题立刻改善。没有trace数据,这个调整方向根本没法定位。
5. 落地踩坑后的实战建议
5.1 别一上来就搭全套架构,先打穿一条链路
第一个坑是我自己踩的。刚开始做透明架构引擎时,我直接规划了管理后台、完整状态机、冲突检测、血缘图展示,开发了两周多,端到端任务还没跑通。后来我把方案砍到最简:一个共享的JSON对象,每个智能体往里写结构化键值,再加上一个最原始的trace_id字段,先让协作链路跑通。
结果发现,只靠共享JSON和一条trace_id,就已经能解决80%的上下文混乱问题。状态机和血缘图是在链路稳定、也确实出现多版本冲突之后,才陆续补上的。功能设计要按需生长,不要提前做整套。
5.2 上下文越少越好,能删的就不留
第二个坑是"多提供点背景总没坏处"的想法。我在一个任务里给智能体塞了完整的历史对话、用户画像、外部知识库片段,心想信息越全越有利于决策。结果任务成功率反而下降了。原因很明显:无关片段稀释了关键信息的权重,模型注意力被分散,更容易被噪声带偏。
现在我对上下文总量的原则是:能删则删,能用摘要不用全文,能查不用存。上线前我会做一次上下文瘦身测试,把不直接影响输出质量的上下文全部移除,看任务成功率是否受影响。多次测试下来,精简上下文后的成功率不仅没降,还经常上升。
5.3 忽略source_ref,就会付出debug代价
筹建初期为了省事,我没有严格给共享上下文加来源域,结果有一次对账结果错误,我花了整整一个下午去追踪一条结论到底是哪个智能体、在哪一轮、基于什么数据产生的。问A说记不清,问B说数据不是它查的,最后只能靠翻原始日志反复比对。
那次之后,我把source_ref变成了写入共享区的强制校验项:没有来源ID、没有版本号、没有置信度的信息一律不允许进入active状态。这个约束看起来反人性,类似给每条信息都搞"身份证",但它在Debug阶段省下的时间远超固化它那一点成本。
5.4 每个状态转移都要设计失败路径
最后一个建议,多智能体系统里,无论是上下文状态机、发布机制还是追踪链路,都要把异常路径考虑进去。比如draft状态的信息一直没人确认怎么办,active的信息在推理过程中被invalidated怎么办,trace_id丢失节点怎么办。
我的习惯是给每个状态转移都写一个兜底动作:状态卡了很久就自动降级为待处理;推理引用到invalidated上下文就直接中断并触发溯源;trace丢失节点就记录一个warning并把前后链路继续串起来。这套处理不一定消灭问题,但能保证系统出错时,错误是显式的而不是隐性的。显式错误好排查,隐性错误才是真正的时间黑洞。
最后再回到最初那个工单系统。现在我知道,提示词再怎么精细,也补不上上下文失控的窟窿。如果你也正在做多智能体相关系统,我建议从这三件事开始:给每条共享上下文加source_ref,给每次任务打trace_id,把上下文总量压到最少。先完成这三步,再谈状态机和血缘图,你会发现很多之前玄学的问题,一下就变成数据问题了。