给 AI Agent 装上"裁判眼"3:上下文之争——多智能体上下文管理的利弊博弈

admin · 2026-05-15 · context · Multi-Agent · MainAgent · Sub-Agent · EvalAgent · 上下文管理 · 利弊分析

给 AI Agent 装上"裁判眼"3:上下文之争——多智能体上下文管理的利弊博弈

给 AI Agent 装上"裁判眼"3:
上下文之争——多智能体上下文管理的利弊博弈

MainAgent 持续累积,Sub-Agent 独立隔离,EvalAgent 完全无状态——三种策略各有代价

作者: admin 2026-05-16 buddyMe v0.2.2

上一篇:从任务分解到评估反馈的多智能体协作全链路

01 一条消息,三种命运

用户说:"把它改成蓝色。"

三个智能体面对同一条用户消息,看到的东西完全不同:

同一条消息的三种"视野" [MainAgent 看到的] 用户 5 分钟前说 "做一个登录页面" 用户 3 分钟前说 "加上表单验证" 用户刚才说 "把它改成蓝色" ← 知道"它"是按钮,"蓝色"是 #3B82F6 [Sub-Agent 看到的] System Prompt: 你的任务是修改样式 前置结果: [子任务1生成了 HTML 文件] 用户需求: "把它改成蓝色" ← 能看到前置文件,但不知道用户为什么这么说 [EvalAgent 看到的] System Prompt: 评估执行质量 执行结果: [修改后的 HTML 文件] 原始需求: "把它改成蓝色" ← 只看最终产物,完全不知道之前发生了什么

这就是多智能体系统中上下文管理的核心矛盾:给得太多会污染,给得太少会丢失关键信息。buddyMe 用了三种截然不同的策略来应对这个问题,每一种都有不得不付出的代价。

本文将深入 MainAgent、Sub-Agent、EvalAgent 三层智能体的上下文机制,逐一拆解每种策略的设计动机、实现细节和利弊权衡。

02 四个角色,四个上下文边界

buddyMe 系统中共有 4 个活跃角色,各自拥有独立的 LLM 客户端和上下文边界:

角色类名LLM 客户端上下文策略
主 AgentAgentMain_client持续累积,跨轮次保留
子 Agent 执行引擎TaskRunner + TaskPipeline_client + _sub_client每任务独立,消息不继承
预执行审核器PlanReviewer_sub_client + _review_client完全独立,无状态对话
评估 AgentTaskEvalAgent_eval_client完全隔离,参数化传入

三个 LLM 客户端在 AgentMain.__init__() 中同时创建:

LLM 客户端初始化(agent.py:68-72)
self._client = self._create_client(model_name)           # 主模型
self._sub_client = basic_llm.create_client("sub_agent_code_plan")  # 子任务辅助
self._eval_client = self._create_client(model_name)       # 评估专用(独立)
三个客户端生命周期不同:_client 贯穿整个会话,_sub_client 用于上下文压缩和计划审核的辅助调用,_eval_client 仅在评估阶段使用。这种分离是上下文隔离的物理基础——不同的"脑子"天然不会串台。

03 上下文生命周期:从用户输入到评估报告

理解利弊之前,先看清上下文在整个系统中是如何流转的:

上下文流转全链路 ──────────────────────────────────────────────────► [规划阶段] │ ├─ 1. 对话历史 ← MessageHistory(最近 20 条) ├─ 2. 长期摘要 ← memory_summary.md(≤6000 字符) ├─ 3. 用户记忆 ← USER.md(偏好与画像) │ │ │ ▼ │ 拼接为 conversation_context 文本 │ │ │ ▼ ├─ 4. 任务规划 ← _client(主模型),一次性调用 │ │ │ ▼ ├─ 5. 预执行审核 ← _sub_client + _review_client(Generator-Evaluator 对审) │ │ │ ▼ │ 审核通过的任务计划 + 需求文档(冲刺合同) │ [执行阶段] ← 循环 N 个子任务 │ ├─ 6. 构建 System Prompt │ ├── SUB_AGENT.md(子 Agent 行为规则) │ ├── 技能元数据 │ ├── 需求文档(冲刺合同) │ └── 前置子任务结果(从 subtask_results.json 读取) │ ├─ 7. 构建 User Prompt │ ├── conversation_context(从 MainAgent 传入) │ ├── 当前子任务描述 │ └── 前置子任务结果(再次注入) │ ├─ 8. 独立执行 │ └── task_messages = [system, user] ← 全新构建,不继承历史 │ └── 工具循环(最多 11 步) │ └── 结果写入 subtask_results.json │ │ │ │ ▼ │ 合并后的最终结果 │ [评估阶段] │ ├─ 9. 参数化数据传入 │ ├── 用户原始需求 │ ├── 任务计划 │ ├── 子任务结果 │ ├── 工具调用记录 │ ├── 写入文件列表 + 文件内容 │ └── 真实执行验证结果 │ ├─ 10. 独立评估 │ └── messages = [_SYSTEM_PROMPT, eval_prompt] ← 固定 2 条消息 │ └── temperature=0.0 │ └─ 11. 评估报告 → 仅控制台输出,不回传

注意三个关键设计决策:

决策 1 · 对话隔离
子任务间 不共享 LLM 对话历史,每个任务从零构建 messages
决策 2 · 状态共享
SessionState 在 MainAgent / TaskRunner / TaskPipeline 间共享引用
决策 3 · 文件桥接
子任务间通过 subtask_results.json 传递结果,推理过程不传递

04 MainAgent:持续上下文的代价

MainAgent 是整个系统的"大脑",在整个会话生命周期内维护三层记忆:

L1
短期记忆(内存)
MessageHistory — 滑动窗口保留最近 20 条消息(首条固定 + 最新 19 条)
每轮 invoke() 自动追加用户输入和助手回复
│
▼
L2
中期记忆(磁盘)
memory_summary.md — 记录近 5 日对话摘要,最大 6000 字符
每轮执行前通过 _build_conversation_context() 注入子任务 prompt
│
▼
L3
长期记忆(磁盘)
USER.md + MemoryStore — 用户画像与偏好
会话结束时执行记忆衰减(低分记忆归档或删除)

利:为什么需要持续上下文

优势 1 · 意图连续性
多轮对话中用户常说"把它改成蓝色"、"加上刚才说的那个功能"。持续上下文让 MainAgent 能理解指代关系,无需用户重复背景信息。
Turn 1: "做一个登录页" → Turn 2: "加上表单验证" → Turn 3: "把它改成蓝色" — "它"指的是按钮
优势 2 · 纠错闭环
用户发现结果不符合预期时,直接说"不对,我说的是 XXX"。MainAgent 能在历史上下文中定位误解并修正,而不是从零开始。
优势 3 · 渐进式构建
复杂项目经常需要多轮迭代——"先生成基础版" → "加上动画" → "修复那个按钮"。持续上下文让每轮基于上一轮结果推进,形成自然的构建链。
优势 4 · 记忆累积
用户偏好(编码风格、常用框架、语言选择)在会话中被逐步学习并持久化到磁盘,跨会话生效。第二个项目的第一条消息就能利用第一个项目的经验。

弊:持续上下文的四个隐患

隐患 1 · 上下文膨胀
随着会话推进,_message_history 不断累积。虽然滑动窗口截断了消息数量,但注入到子任务的 conversation_context 文本会越来越长,消耗 Token 预算。
Turn 1: ~2K tokens → Turn 10: ~15K tokens
隐患 2 · 信息污染
早期对话中的错误决策、已废弃的需求可能残留在历史中,误导后续执行。尤其是当用户在会话中途改变方向时,旧上下文可能成为噪音。
Turn 3: "用 React" → Turn 7: "改成 Vue" → Turn 10 的上下文中仍有 React 相关的讨论
隐患 3 · 幻觉传播
MainAgent 没有在轮次间做上下文重置机制(仅有 reset_episode() 清理执行状态,不清消息历史)。如果某一轮产生幻觉,错误信息可能传播到后续所有轮次。
隐患 4 · 客户端生命周期错位
invoke() 末尾关闭所有 LLM 客户端,但 _message_history 仍在内存中。会话状态(消息)和连接状态(客户端)的生命周期不完全匹配——如果 REPL 循环在客户端关闭后继续调用,可能产生连接错误。
一句话总结:持续上下文是刚需——编码任务天然多轮、迭代、需要指代消解。但代价是 Token 预算随会话线性增长,且缺乏有效的信息衰减和重置机制。

05 Sub-Agent:独立上下文的得与失

每个子任务的执行都是一次全新的 LLM 对话。核心设计原则:消息不继承,状态通过文本注入。

子任务消息构建过程 子任务 #1: messages = [system_prompt_1, user_prompt_1] ← 全新构建 → LLM 调用 + 工具循环(最多 11 步) → 结果写入 subtask_results.json 子任务 #2: messages = [system_prompt_2, user_prompt_2] ← 全新构建 → system_prompt_2 包含子任务 #1 的结果(从 JSON 读取) → LLM 调用 + 工具循环 → 结果追加到 subtask_results.json 子任务 #N: messages = [system_prompt_N, user_prompt_N] ← 全新构建 → 包含子任务 #1~N-1 的全部结果 → 最终结果返回给 MainAgent

三条数据传递通道

通道媒介范围可控性
通道 1 JSON 文件subtask_results.json整个任务计划明确读写,可截断
通道 2 共享状态SessionState整个任务计划字段级控制
通道 3 上下文文本字符串拼接每次构建动态截断

利:独立上下文带来的稳定性

优势 1 · 上下文干净
每个子任务从零开始,没有历史消息的噪音。LLM 只看到当前任务需要的上下文,不会被之前的工具调用、中间结果、错误信息干扰。
代码证据:task_runner.py:227-230 — 每个子任务创建全新 messages 列表
优势 2 · 错误隔离
子任务 #2 的幻觉不会通过消息历史传播到子任务 #3。错误被限定在单个子任务的执行范围内。一个任务崩溃,不会拖垮整条链。
优势 3 · Token 可控
每个子任务的上下文大小通过截断策略(max_subtask_result_len、max_tools_compress_len)显式控制。不会因为前序任务的复杂度而无限膨胀。
优势 4 · 并行潜力
独立上下文意味着无状态依赖的子任务理论上可以并行执行。当前实现是顺序的,但架构上预留了可能性。
优势 5 · 压缩友好
当单个子任务的对话超过 20 条消息时,ContextCompressor 可以安全地压缩——保留前 2 条 + 压缩中段 + 保留最近 5 条。压缩后的摘要仍包含关键信息。

弊:独立上下文的五个盲区

盲区 1 · 推理过程丢失
子任务 A 决定用 CSS Grid 布局(排除 Flexbox),但这个决策过程不会传递给子任务 B。子任务 B 可能选了 Flexbox,导致风格不一致。
只传递结果文本,不传递推理链 — SubtasksManager.update() 只记录 result,不含中间推理步骤
盲区 2 · 信息截断损失
上下文传递依赖文本摘要,截断不可避免:result[:max_subtask_result_len] 可能丢失关键细节——长代码输出、复杂错误信息、细微的上下文线索。
截断阈值: 默认 max_subtask_result_len
盲区 3 · 重复理解开销
每个子任务都要重新理解项目结构、文件内容、需求背景。虽然通过 prompt 注入缓解,但 LLM 需要重新"消化"这些信息,增加了推理开销和延迟。
盲区 4 · 全局视图缺失
子任务 #3 看不到子任务 #1 的工具调用过程和中间推理。如果子任务 #1 的结果依赖于某个隐含假设,子任务 #3 可能无法察觉,导致后续输出与前置假设冲突。
盲区 5 · 双重注入冗余
read_completed() 的结果同时注入到 system prompt 和 user prompt(task_pipeline.py:354 和 :392),对同一信息的双重注入浪费了 Token 预算。
一句话总结:独立上下文换来了错误隔离和 Token 可控,但代价是子任务间的推理断层——后续任务不知道前面的 Agent "为什么"做出某个选择。

06 EvalAgent:最干净的评估者

EvalAgent 是系统中最"干净"的智能体——每次评估都是一次无状态的单次 LLM 调用,固定 2 条消息,temperature=0.0。

EvalAgent 的极简上下文(verify.py:266-271, 316-320)
# 构造函数 — 唯一的实例属性是客户端
def __init__(self, client):
    self._client = client  # 独立客户端,防止上下文冲突

# 每次评估 — 固定 2 条消息
response = await self._client.chat(
    messages=[
        {"role": "system", "content": self._SYSTEM_PROMPT},
        {"role": "user", "content": prompt},
    ],
    temperature=0.0,
)

评估数据来源

所有数据通过方法参数传入,从 SessionState(每轮重置)和磁盘文件(实时读取)获取:

数据来源说明
用户原始需求user_input 参数直接传入
任务计划SessionState.task_plan每轮重置
子任务结果SessionState.last_episode每轮重置
工具调用记录SessionState.used_tools每轮重置
文件内容磁盘实时读取_read_written_files()
真实执行结果子进程执行_run_verification()

利:绝对独立的评估

优势 1 · 绝对客观
无历史评估记忆意味着每次评估都是独立判断,不会因为之前的评估结果(好评/差评)产生锚定效应。第三次评估和第一次评估标准完全一致。
优势 2 · 零污染
EvalAgent 使用独立的 LLM 客户端实例(_eval_client),与主 Agent 的客户端完全隔离。不存在上下文交叉污染的任何可能性。
优势 3 · 确定性可复现
temperature=0.0 + 固定 system prompt + 纯数据驱动的 user prompt,使评估结果在相同输入下高度一致,便于调试和回归测试。
优势 4 · 真实执行验证
不仅评估代码质量,还真实执行生成的文件并验证运行结果。入口点识别有 5 级优先级:智能体输出中提到的文件 > 标准入口文件名 > __name__ == "__main__" > HTML 文件 > Shell 脚本。
"能跑"比"看起来对"重要得多——这是代码评估中最硬的证据

弊:最干净也最孤独

代价 1 · 无学习反馈
评估结果不回传给 MainAgent,不持久化,不影响后续行为。如果同一类错误反复出现,EvalAgent 每次都独立发现但无法形成"教训"。
EvalAgent 的评估报告是 fire-and-forget:打印到控制台,然后消失
代价 2 · 评估视野受限
单次调用只能看到当前任务的快照数据,无法评估跨任务的累积质量——多个任务的代码风格是否一致?是否重复造轮子?是否存在依赖冲突?这些问题 EvalAgent 无从回答。
代价 3 · 重试机制粗糙
MAX_RETRIES = 2 的重试只是简单重复相同调用,不会根据失败原因调整 prompt。如果第一次评估因为 JSON 解析失败,重试也不会改变策略。
代价 4 · 系统提示词静态
_SYSTEM_PROMPT 是类级常量,无法根据项目类型(前端/后端/数据科学)或任务复杂度动态调整评估标准。用同一套标准评估"Hello World"和"分布式系统",精度必然打折。
一句话总结:EvalAgent 的独立性保证了评估的客观和一致,但也意味着它是一个没有记忆的裁判——每次从零开始评判,无法积累经验,也无法将发现反馈给系统。

07 跨框架对比:buddyMe 不是唯一的解法

buddyMe 的上下文管理策略并非唯一选择。不同框架对"隔离 vs 共享"的取舍截然不同:

上下文策略光谱

上下文策略光谱 ──── 从完全共享到完全隔离 完全共享 完全隔离 │ │ ├─ AutoGen ──── Agent 间共享完整对话历史 │ 利: 全局视野最强 弊: Token 消耗爆炸 + 污染风险极高 │ ├─ LangGraph ── State 在节点间流转,可自定义共享范围 │ 利: 灵活可控 弊: 配置复杂,需要显式管理状态 Schema │ ├─ buddyMe ──── 对话隔离 + 状态共享 + 文件桥接 │ 利: 错误隔离 + 稳定性高 弊: 推理断层 + 信息截断 │ ├─ CrewAI ────── 每个 Agent 独立上下文,通过任务输出传递 │ 利: 角色边界清晰 弊: 上下文传递依赖用户设计 │ └─ 单次调用 ── 每次请求完全独立(如 EvalAgent) 利: 零污染 + 确定性高 弊: 无连续性 + 无记忆

详细对照

维度AutoGenLangGraphbuddyMeCrewAI
对话历史共享(群聊模式)可配置隔离(独立构建)隔离
状态传递消息内嵌State SchemaSessionState + JSON任务输出
上下文压缩无内置可插拔ContextCompressor无内置
评估隔离无内置无内置独立客户端 + temperature=0无内置
记忆系统无Checkpointer三层记忆(短/中/长)短期记忆
Token 可控性低高高中
推理连贯性高中低低
错误隔离性低中高高
关键洞察:不存在"最优"策略。推理连贯性和错误隔离性是天然矛盾的——共享上下文提升连贯性但增加污染风险,隔离上下文保证稳定性但牺牲推理深度。选型的核心是理解你的应用场景更怕哪个:信息丢失还是信息污染。

08 五个设计张力点

buddyMe 的上下文架构中存在五个核心张力,每个都是在工程实践中不得不做的取舍:

张力 1 · Main 持续 vs Sub 独立
Main 累积的对话历史通过文本摘要注入 Sub,但截断不可避免。
长会话中 Sub 获得的上下文越来越"稀释"——像把一杯浓茶不断兑水
张力 2 · Sub 间 JSON 文件通信
结果被截断后写入 subtask_results.json,后续子任务可能丢失关键代码细节。
子任务 1 生成了 500 行代码,传递到子任务 2 时只剩 200 行摘要
张力 3 · EvalAgent 无反馈闭环
评估结果不回传,不持久化,不影响后续行为。系统无法从评估中"学习"和改进。
EvalAgent 发现同一个错误第 10 次,和第 1 次一样惊讶
张力 4 · 共享客户端风险
主 Agent 和子任务执行共用 _client,客户端层面的资源(连接池、速率限制)是共享的。
子任务的大量工具调用可能触发 API 速率限制,影响主 Agent
张力 5 · 双重注入的 Token 浪费
前置子任务结果同时注入到 system prompt 和 user prompt(task_pipeline.py:354 和 :392)。对同一信息的双重注入浪费了 Token 预算——相当于让学生在考卷上把题目读两遍。
优化方向:按需注入——system prompt 放摘要,user prompt 放完整数据

09 三层利弊全景对比

维度MainAgentSub-AgentEvalAgent
上下文类型持续累积完全独立完全独立
状态存储内存 + 磁盘JSON 文件(临时)无状态
消息继承跨轮次继承不继承,文本注入不继承
错误传播范围整个会话单个子任务单次评估
Token 可控性低(滑动窗口仅截数量)高(显式截断策略)极高(固定 2 条消息)
并行可行性不可(状态依赖)可行(独立上下文)可行(无状态)
学习/改进能力有(记忆系统)无无
推理连贯性强(多轮累积)弱(仅结果传递)最弱(仅看最终产物)

通俗化类比:公司项目组

MainAgent = 项目经理
维护项目档案,记住客户每次沟通的细节。好处是能理解"上次说的那个"指什么,坏处是档案越积越厚,找东西越来越难。
持续上下文 = 项目经理的笔记本
Sub-Agent = 执行团队
每个人只看自己的任务单 + 前面人的工作成果。专注、不被打扰,但不知道同事在想什么。前面人为什么选了方案 A 而不是 B?不知道。
独立上下文 = 每个人的独立工位
EvalAgent = 质检部门
完全独立于项目团队,不参与任何执行过程,只看最终成果 + 执行记录。会真实运行交付的代码看能不能跑通。但质检报告不会反馈给项目组——下次同样的问题会再次出现。
无状态评估 = 外部审计,审计完就走

10 改进路线图:从"够用"到"优秀"

当前设计在 buddyMe 的应用场景(AI 辅助编程)中是合理且务实的。但在更复杂的多 Agent 场景下,以下改进方向值得探索:

短期优化(1-2 周)

优化 1 · 决策摘要传递
在 SubtasksManager 中增加 decisions 字段,记录关键设计决策及其理由
减少重复探索,提高跨任务一致性
优化 2 · 双重注入去重
避免 system prompt 和 user prompt 中重复注入相同的前置子任务结果
system 放摘要,user 放完整数据
优化 3 · 独立子任务客户端
为子任务执行创建独立的 LLM 客户端实例,避免速率限制干扰
隔离客户端层面的资源竞争

中期改进(1-2 月)

A
结构化上下文传递
用 JSON Schema 定义子任务间的上下文格式,替代纯文本拼接。减少信息损失,提高可解析性。
│
B
EvalAgent 反馈闭环
将评估报告中的关键发现(代码风格不一致、常见错误模式)持久化到记忆系统,供后续任务参考。
让裁判不仅能发现问题,还能帮团队记住教训
│
C
基于重要性的消息选择
取代简单的滑动窗口截断,引入重要性评分机制——保留对后续任务最相关的消息,而非最近的 N 条。

长期演进(3-6 月)

多轮评估反馈闭环
评估结果反馈给 MainAgent → 触发针对性修复 → 重新评估
实现自我改进循环
上下文预算管理
为每个阶段分配 Token 预算,动态调整上下文压缩策略
精细化 Token 使用
Agent 间通信协议
定义标准化的 Agent 间消息格式,支持更灵活的多 Agent 编排
为更复杂的多 Agent 场景奠定基础
核心原则:不是传递更多上下文,而是传递更有质量的上下文。buddyMe 当前的设计是在"信息丢失"和"信息污染"之间选择了前者——对于文件生成类的确定性任务,这是正确的取舍。未来的改进方向是缩小两者的距离:既不丢关键信息,也不引入噪音。
下一篇预告:我们将深入 buddyMe 的上下文压缩引擎——ContextCompressor 如何在保留关键信息的前提下将 20 条消息压缩为 7 条?压缩策略的参数如何影响 Agent 的表现?这是一个关乎 Token 预算和任务质量的精密工程问题。
一句话引用:MainAgent 持续累积、Sub-Agent 独立隔离、EvalAgent 完全无状态——三种上下文策略各有代价。深入拆解每个智能体的上下文机制,分析利弊权衡,对比 AutoGen / LangGraph / CrewAI 的不同解法。