手敲三Agent串行流水线,我发现了多Agent协作的隐形杀手

手敲三Agent串行流水线,我发现了多Agent协作的隐形杀手

做个实验:让三个AI Agent串行协作(主编写稿→审稿评分→排版润色),你觉得最难的部分是什么?

不是模型能力不够,不是工具调用失败,不是权限控制太复杂------最难的是怎么让Agent之间传递信息既不丢重点,又不让垃圾撑爆上下文

我上周用LangChain4j跑三Agent代码审查流水线时就踩过这个坑:CodeReviewer输出1800字分析报告,塞进变量传给SecurityScanner,直接504超时------token爆炸了。这周我手敲了一个纯Python版的三Agent串行Workflow,从BaseAgent基类设计到Workflow编排器,从JSON结构化传递到上下文膨胀控制,跑通之后提炼出7条设计原则。

这篇文章把实战过程完整摊开:先讲多Agent协作的核心矛盾(上下文膨胀),再看四大生产级Agent架构怎么解决信息传递问题,最后展示我的手敲代码和踩坑经验。3个结论先放这:

  1. Agent间传递用JSON比自由文本好------结构化、精简、可解析,1800字全文→200字JSON,token消耗降90%
  2. 每个Agent必须上下文隔离------只看自己的system_prompt+本次输入,不继承上一个Agent的对话历史
  3. 上下文膨胀的本质是"信息粒度不对等"------Agent A输出的是"我想说什么",Agent B需要的是"我需要知道什么",两者不是一回事

多Agent协作的核心矛盾:信息传递不是"发快递"

很多教程讲多Agent协作,都停留在"让Agent之间对话"这个层面。但多Agent协作真正的问题不是"能不能对话",是"对话的内容到底是不是对方想要的"。

打个比方:你让三个同事串行写文章------主编写完初稿,把全文甩给审稿人;审稿人看完,甩了一份1800字审稿报告给排版师。排版师拿到的是什么?初稿全文+1800字审稿报告,3000+字的输入砸脸上。

但排版师真正需要啥?原文、评分、修改建议,就这三样。审稿报告里那些论证过程、背景分析、补充说明------对排版师来说全都是噪音。

说白了就是"粒度不对":上一个Agent说的是"我想说什么"(完整思考过程),下一个Agent要的是"我需要知道什么"(关键结论)。把"我想说什么"原样甩过去,上下文就膨胀了------一堆没用的token占满LLM窗口,推理质量直线下降,甚至直接超时。

我在Week5用LangChain4j跑代码审查流水线时,亲历了这个问题的严重性:

  • CodeReviewer输出1800字代码分析报告
  • 这1800字塞进{{codeReview}}变量传给SecurityScanner
  • SecurityScanner的prompt突然膨胀到3000+token
  • 直接504超时,LLM推理时间暴涨

解决方案贼简单:让Reviewer输出JSON,别写自由文本。{"score": 7, "issues": "开头没有核心结论", "suggestions": "前200字重写"}------200字搞定,token消耗直接降90%。

这不是我独创的,四大生产级Agent架构都在用不同方式解决这同一个问题。

四大架构怎么解决信息传递问题

我拆解了四个生产级Agent(OpenClaw、Hermes、Claude Code、Codex),发现它们解决"信息传递"的思路各有不同,但目标一致:只传必要的,不传全部的

OpenClaw:compaction压缩历史

OpenClaw的解决方案是上下文压缩。聊天20轮session历史爆满了?别原样塞给LLM------用compaction把历史消息压成summary,只留最近几轮完整对话。

这思路搬到多Agent协作:别把Agent A的整个对话历史甩给Agent B,只传"Agent A的结论"就行。我的三Agent流水线里,Formatter拿的是"评分+问题+建议"的精简摘要,不是Reviewer的全文审稿报告。

Java直觉:compaction就是日志聚合------别把每条日志原样存,按时间段聚合成摘要就行。Agent间传递同理,别把Agent完整输出原样甩过去,只传聚合后的关键信息。

Hermes:硬性容量管理

Hermes更狠------直接给记忆设硬上限。MEMORY.md最多2200字符/800token,USER.md最多1375字符/500token。不是"能塞多少塞多少",是"只留最重要的"。挺吓人的,800token就是MEMORY.md的全部容量------一个稍微长点的结论就塞满了。

搬到多Agent协作就是:每个Agent的输出也得有容量上限。Reviewer输出3个字段JSON,不是1800字自由文本。这不是"少写点",是"只写下一个Agent需要的"。

Hermes还有个"nudge"机制------Agent会定期提醒自己写笔记,不是被动记日志,是主动挑重要的写。搬到多Agent协作:Agent得主动筛自己的输出,别把中间过程全甩给下一个。Reviewer的职责是"为下一个Agent提炼决策关键信息",不是"记录我的完整思考过程"。

Java直觉:这就像DTO(Data Transfer Object)的设计原则------微服务之间传递DTO,不是传整个Entity。DTO只包含对方需要的字段,Entity包含所有字段。Reviewer的JSON输出就是DTO,1800字自由文本就是Entity。

Claude Code:分层存储

Claude Code的记忆是分层的:CLAUDE.md(用户写的持久指令)+ Auto memory(Agent自己攒的笔记)。CLAUDE.md是"我告诉Agent必须遵守的规矩",Auto memory是"Agent从经验中自己学到的偏好"。

搬到多Agent协作:Agent间传递的信息也得分层。Formatter要的是原文+修改建议,不需要Reviewer的论证过程和思考链。_merge_for_formatter()只提取JSON里的score/issues/suggestions三个字段,其余全扔掉。

Codex:远程压缩

Codex的compact_remote挺有意思------压缩操作可以甩给远程服务器干,不用在本地压。这意味着压缩可以异步、可以分布式、可以按需触发。

搬到多Agent协作的启示:Agent间信息传递也可以异步按需。我现在是同步串行------editor写完立即给reviewer,reviewer审完立即给formatter。但compact_remote提示了另一种玩法:审稿意见太长?先压缩再传;多个Agent需要同一段信息?共享一个压缩后的摘要,别各自拿全文。

四大架构对比总结

架构 信息传递策略 核心思路 Java直觉
OpenClaw compaction压缩 只传摘要,不传全文 日志聚合
Hermes 硬性容量管理+nudge策展 只存必要的,主动提炼 DTO vs Entity
Claude Code 分层存储 显式知识+隐式知识分离 配置文件 vs 运行时数据
Codex 远程压缩 异步按需压缩 异步日志处理

共同原则:只传必要的,不传全部的。具体实现各有不同,但目标一致------防止上下文膨胀。

手敲三Agent串行流水线:从BaseAgent到Workflow编排

理论看完了,现在看代码。我手敲了一个完整的多Agent Workflow系统,从BaseAgent基类到三Agent定义到Workflow编排器,一共约120行代码。每个设计决策都来自上面的架构对比分析。

BaseAgent基类:Agent不是LLM

很多人把Agent和LLM搞混。Agent不是LLM,Agent = system_prompt + 输入格式 + 输出格式 + LLM客户端。LLM是引擎,Agent是驾驶员------不同驾驶员开同一辆车走不同的路。

python 复制代码
class BaseAgent:
    """Agent基类 --- 独立prompt + 输入输出约定 + LLM调用"""

    def __init__(
        self,
        name: str,
        system_prompt: str,
        llm: LLMClient,
        output_format: str = "text",   # text | json
        output_keys: list[str] = None,  # JSON模式时的字段名
        max_tokens: int = 800,
    ):
        self.name = name
        self.system_prompt = system_prompt
        self.llm = llm
        self.output_format = output_format
        self.output_keys = output_keys or []
        self.max_tokens = max_tokens

    def invoke(self, input_data: str) -> str:
        """调用Agent --- 组装messages→LLM推理→解析输出"""
        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": input_data},
        ]
        # JSON模式:追加格式约束
        if self.output_format == "json" and self.output_keys:
            messages[0]["content"] += f"\n\n{self._format_instruction()}"
        # LLM推理
        response_text = self.llm.chat(messages, max_tokens=self.max_tokens)
        # JSON模式:提取结构化输出
        if self.output_format == "json":
            return self._extract_json(response_text)
        return response_text

这个设计有几个关键决策值得展开讲:

决策1:output_format双模式------text和json

为什么Reviewer用JSON而Editor用text?因为Reviewer的输出要被下一个Agent消费------Formatter需要知道"评分是多少"、"问题有哪些"、"建议是什么",这些信息必须结构化、可解析。而Editor的输出是给人类看的文章初稿,自由文本更自然。

Claude Code也是这么干的------结构化JSON和自由文本分开处理。Reviewer的JSON是"对方能直接解析的数据",Editor的自由文本是"对方得全文理解的内容"。

决策2:output_keys字段约束

output_keys告诉LLM"你必须输出这些字段"。这不是多余的设计------没有字段约束,LLM可能输出一个包含20个key的JSON,其中15个是废话。有了字段约束,LLM只输出score/issues/suggestions三个字段,精简且可预测。

Hermes硬性容量管理也是这思路------别"能存多少存多少",只存必要的。output_keys就是Agent输出的容量上限。

决策3:max_tokens按角色差异化

Editor需要max_tokens=2000(写1500字文章),Reviewer只需要800(JSON输出不需要太多token),Formatter需要2000(润色后的文章可能比初稿更长)。这不是随意设的------每个Agent的输出需求不同,token限制也应该不同。

Codex的compact策略也是这思路------别让所有Agent用统一的上下文窗口,按角色差异化配置。

_extract_json:LLM不听话怎么办

LLM经常不听指令。你让它输出JSON,它可能:

  1. 包裹三个反引号的markdown标记
  2. 在JSON前后加废话
  3. 输出无效JSON
python 复制代码
def _extract_json(self, text: str) -> str:
    """从LLM输出提取JSON"""
    cleaned = text.strip()
    # 去三个反引号包裹
    if cleaned.startswith("```"):
        idx = cleaned.find("\n")
        if idx > 0:
            cleaned = cleaned[idx + 1:]
        if cleaned.endswith("```"):
            cleaned = cleaned[:-3].strip()
    try:
        parsed = json.loads(cleaned)
        return json.dumps(parsed, ensure_ascii=False)
    except json.JSONDecodeError:
        print(f"[{self.name}] JSON解析失败,返回原始文本")
        return text

这个方法在实战中确实捕获了LLM不听话的情况------GLM-5.1有时候会在JSON前面加一句"好的,以下是我的审稿意见:",导致json.loads失败。_extract_json做的是:先去掉markdown标记,再尝试解析,失败就fallback返回原始文本。

这是生产级Agent必须处理的"LLM输出不确定性"问题。四大架构也都处理了:OpenClaw用compaction兜底(压缩失败就用原始历史),Codex用compact_remote远程重试。

三Agent定义:每个角色做什么

python 复制代码
EDITOR_PROMPT = """你是一位技术文章主编...写一篇技术文章初稿...
要求:标题要有钩子、开头200字放核心结论、至少1500字、口语化风格"""

REVIEWER_PROMPT = """你是一位严厉的技术审稿人...
审查维度:标题吸引力/开头结论/技术深度/口语化程度/逻辑连贯
你必须以JSON格式输出,包含字段: score, issues, suggestions"""

FORMATTER_PROMPT = """你是一位技术文章排版润色师...
润色要点:根据suggestions逐条修改、去AI味、确保开头200字是核心结论"""

注意Reviewer的prompt最后那句"你必须以JSON格式输出"------这是output_format="json" + output_keys="score", "issues", "suggestions"的prompt层约束。技术上,_format_instruction()方法会自动追加更详细的格式说明,但prompt本身也要先提一遍,双重约束确保LLM输出符合预期。

Hermes的nudge机制也是这思路------别被动等LLM自己决定输出格式,主动约束。nudge是"提醒自己写笔记",output_format是"提醒LLM输出JSON",本质都是主动策展而非被动记录。

Workflow编排器:串行的核心是_merge_for_formatter

整个Workflow最关键的方法不是run(),是_merge_for_formatter():

python 复制代码
def _merge_for_formatter(self, draft: str, review_json: str) -> str:
    """合并原文+审稿意见 → formatter输入"""
    try:
        review_data = json.loads(review_json)
        score = review_data.get("score", "N/A")
        issues = review_data.get("issues", [])
        suggestions = review_data.get("suggestions", [])
        review_summary = f"评分: {score}/10\n问题: {issues}\n建议: {suggestions}"
    except json.JSONDecodeError:
        review_summary = review_json  # fallback

    return f"## 文章初稿\n{draft}\n\n## 审稿意见\n{review_summary}\n\n请根据审稿意见润色这篇文章。"

这个方法做的事情是:把Reviewer的1800字自由文本(如果JSON解析失败的话)或200字JSON摘要,提炼成"评分+问题+建议"的精简摘要,和原文合并后传给Formatter

对比Week5踩坑:LangChain4j的AgenticScope里,CodeReviewer的1800字输出原样塞进{{codeReview}}变量,SecurityScanner的prompt膨胀到3000+token,直接504超时。而这里,Formatter拿到的输入是:原文 + 评分/10 + 问题列表 + 建议列表------精简、可解析、不会膨胀。

说白了就是OpenClaw compaction思想在Agent间传递的应用------别把所有历史消息原样塞给LLM,压缩后只传摘要。Hermes硬性容量管理同理------Reviewer输出从1800字压到200字JSON,只留"下一个Agent需要的"。

Java直觉:_merge_for_formatter就是DTO组装器------从Entity(Reviewer的完整输出)中提取字段,组装成DTO(score/issues/suggestions),传给下一个微服务(Formatter)。

四个实战踩坑:每个都是"上下文膨胀"的变种

跑通三Agent流水线的过程中踩了4个坑,每个说白了都是上下文膨胀的不同表现形式。

坑1:LLM超时------urllib的同步等待陷阱

最开始用urllib同步调用LLM API,timeout=30秒。EditorAgent写1500字文章,GLM-5.1需要40-50秒才能吐完所有内容------30秒超时直接报错。

第一反应是加timeout到60秒,还是超。后来分析了一下:问题不是网络差,是urllib不支持流式响应。长文章生成要等全部内容吐完才能返回,这就像你让厨师做一道菜,非要等他全部做完才能开始吃------不能边做边吃。

解决方案:

  1. 动态超时------timeout = max(120, max_tokens * 0.1),editor 2000 token→200秒超时
  2. 重试机制------最多2次重试,间隔3秒,不再一超时就返回错误

说白了也是上下文膨胀问题------"等待时间膨胀"。四大架构都处理了:OpenClaw用流式输出(streaming replies),Codex用异步生成器(realtime_conversation.rs),Claude Code用/compact手动压缩来缩短响应时间。

坑2:飞书渲染------反引号变成HTML标签

这个坑看起来是"显示问题",但说白了就是"信息在传递过程中被意外修改"------也是上下文膨胀的变种。飞书消息里的三个反引号被渲染成HTML标签,手敲时原样敲入导致Python语法错误。

这说明:Agent间传递的信息在经过不同通道时可能被意外修改。OpenClaw处理这个问题的方式是multi-channel routing------不同通道(飞书/Telegram/Discord)有不同的消息格式处理逻辑,确保信息在传递过程中不被损坏。

坑3:invoke缩进错误------上下文隔离的实现缺陷

漏了一个缩进层级,invoke方法变成模块级函数而不是类方法。这导致"上下文隔离"失效------如果invoke是模块级函数,它就无法访问self.system_prompt、self.output_format等Agent私有属性,每个Agent的"独立身份"就被破坏了。

四大架构都强调子Agent隔离------Claude Code的Subagent有自己的上下文窗口、system prompt和tool access;OpenClaw的sessions_spawn隔离子session的上下文。隔离一旦失效,所有Agent共享同一个上下文,上下文膨胀的老问题就回来了。

坑4:max_tokens太小------"容量管理"配置不当

Editor用max_tokens=800写不出1500字文章。Hermes也遇到过这个容量管理问题------MEMORY.md限制2200字符,USER.md限制1375字符。容量太小写不出东西,太大浪费token和推理时间。

解决方案是按角色差异化:editor 2000、reviewer 800、formatter 2000。Codex的多策略compaction也是这思路------别让所有Agent用统一的上下文窗口,按角色差异化配置。

踩坑总结

表现 本质 四大架构的解法
LLM超时 urllib 30秒超时 等待时间膨胀 OpenClaw流式输出/Codex异步生成器
飞书渲染 反引号→HTML标签 传递过程中信息被损坏 OpenClaw multi-channel路由
缩进错误 invoke变模块函数 上下文隔离失效 Claude Code Subagent隔离/Codex spawn隔离
max_tokens太小 800 token写不出1500字 容量管理不当 Hermes硬性容量上限+按角色差异化

四个坑,四种表现,但说白了都是同一回事:上下文膨胀的不同变种------时间膨胀、信息损坏、隔离失效、容量不当。

7条可迁移的设计原则

从四大架构拆解到实战踩坑,提炼出7条原则。每条都经过代码验证:

原则1:对话循环必须串行化

多Agent串行Workflow里,editor→reviewer→formatter顺序不可变。这不是因为"并行不好",是因为串行有天然的数据依赖------reviewer必须等editor写完才能审稿,formatter必须等reviewer审完才能润色。

OpenClaw的per-session串行化也是这个逻辑:同一个session同时只处理一条消息,防止竞态条件。Java直觉:Kafka partition顺序消费------一个partition同一时刻只被一个consumer处理。

原则2:权限必须渐进式信任

这个原则在多Agent协作里体现为:不同的Agent有不同的"权限级别"。Editor有"写权限"(max_tokens=2000,允许长输出),Reviewer有"读权限"(max_tokens=800,只输出精简JSON),Formatter有"写权限"(max_tokens=2000)。

Claude Code也是这思路------三层分级权限:Read-only无需批准、Bash需批准但永久记住、File修改需批准且只到session结束。别一刀切,按风险等级区分。

原则3:记忆必须Agent主动策展+容量管理

Hermes的nudge机制------Agent主动提醒自己写笔记,别被动记录。搬到多Agent协作:Reviewer得主动筛自己的输出,只写score/issues/suggestions三个字段,别写1800字完整思考过程。

容量管理也是必须的:MEMORY.md限制2200字符、output_keys限制JSON字段数、max_tokens限制输出长度。没有容量管理,Agent就会"能写多少写多少"------然后下一个Agent的上下文就爆炸了。

原则4:Skill必须自描述可热加载

这条在本次实战中体现为:每个Agent的system_prompt就是它的"Skill定义"------Editor的prompt定义了"写文章"这个Skill,Reviewer的prompt定义了"审稿"这个Skill。这些Skill是自描述的(prompt本身就告诉你这个Agent做什么),也是可热加载的(改prompt就改了Agent的能力)。

OpenClaw和Claude Code的SKILL.md也是这么玩的------Skill用Markdown文件自描述,放特定目录就能被Agent自动加载。

原则5:上下文必须有压缩机制

_merge_for_formatter就是压缩机制------把Reviewer的完整输出压缩成"评分+问题+建议"的精简摘要。OpenClaw的compaction就是这个思路------把session历史压成summary。

关键洞察:压缩不是"丢失信息",是"只保留决策关键信息"。Formatter不需要知道Reviewer为什么觉得标题不够好(论证过程),只需要知道"Reviewer觉得标题不够好,建议改一个"(决策结论)。

原则6:子Agent必须隔离上下文

每个Agent只看自己的system_prompt+本次输入,不继承上一个Agent的对话历史。invoke()方法只传两个message:system和user,不传之前Agent的完整对话记录。

Claude Code的Subagent就是这么干的------每个有自己的上下文窗口、独立system prompt、独立tool access。Codex的spawn_child()也一样------子进程隔离上下文,不共享父进程的session。

原则7:Agent间传递必须精简

这条是我自己踩坑总结的,四大架构的文档里没人直接说,但它们的compaction/容量管理/分层存储说白了都是在解决这同一个问题。

精简别"少写点",只写对方需要的。Reviewer输出JSON不是因为懒,是因为Formatter只需要score/issues/suggestions这三个字段来做出润色决策。1800字自由文本里的论证过程、背景分析、补充说明------这些对Formatter来说是噪音,不是信号。

Java直觉再次出现:DTO vs Entity的差别。微服务之间传DTO,只包含对方需要的字段;Agent之间传JSON,只包含下一个Agent需要的字段。

说白了

多Agent协作的核心矛盾就一句话:怎么让Agent之间传递信息既不丢重点,又不让垃圾撑爆上下文

四大生产级Agent架构各有各的解法:OpenClaw靠compaction压缩历史、Hermes靠硬性容量管理+nudge策展、Claude Code靠分层存储+渐进式权限、Codex靠远程压缩+平台自适应沙箱。我的实战验证结论:Agent间传递用JSON比自由文本好,上下文隔离是必须的,_merge_for_formatter是整个Workflow最关键的方法

下一步计划:给Mini Harness加Agent Memory三层模型(短期记忆/工作记忆/长期记忆),对比Hermes的封闭式记忆+MEMORY.md索引和OpenClaw的session+workspace机制。

下周我会拆解Agent Memory的设计------为什么"记忆策展"比"记忆存储"更重要。

有问题评论区聊。

相关推荐
稷下元歌1 小时前
七天学会plc加机器视觉之AI 接入 外设模块开发全详细操作文档(全程配套视频按文档实操)
python·sql·qt·贪心算法·r语言·wpf·时序数据库
春风野草1 小时前
第七章 多Agent协作,不是加人就能解决问题
agent·ai编程
葫芦和十三1 小时前
设计坐标系|别把 Agent 模式学成名词表
架构·agent·ai编程
doiito1 小时前
我选了 Oxigraph 做 AI 的大脑,然后整个系统开挂了
agent
隐层漫游者1 小时前
深度解析LangGraph:从入门到动态工作流,彻底搞懂它与LangChain的爱恨情仇(含节点/边/状态管理/条件路由核心源码)
agent
tabzzz1 小时前
我眼中的 ReAct:从推理到行动的 Agent 循环
agent
KaMeidebaby1 小时前
卡梅德生物技术快报|细胞周期检测抗原流式分析:参数调试、软件拟合与问题排查
网络·人工智能·python·网络协议·tcp/ip·算法·机器学习
莫逸风1 小时前
【AgentScope】4.会话(Session)详解
java·llm·agent·agentscope
zmzb01031 小时前
Python课后习题训练记录Day124
开发语言·python