1. 多智能体协作架构到底在解决什么问题
第一次接触“Loop Engineering”这个词,很多人会以为是某种循环语句的工程化封装,或者跟事件循环、消息循环沾边。实际上,它描述的是一套让多个智能体在同一个任务闭环里反复协作、互相校验、逐步收敛的架构方法论。核心不在“循环”本身,而在于把一次性的任务执行,改造成一个可迭代、可观测、可回滚的协作回路。
我最早接触多智能体协作是在做自动化内容生产流水线的时候。单个智能体写出来的东西,质量波动极大,同一个提示词跑十次能出十种水平。后来把任务拆成“规划—执行—评审—修正”四个角色,让它们在一个回路里互相拉扯,输出质量才稳定下来。这就是 Loop Engineering 最朴素的样子:不是让一个智能体变强,而是让一组智能体在循环中互相补位。
它适合谁?如果你正在做以下任何一件事,这套东西就跟你有关:
- 用多个模型或同一模型的多角色完成复杂任务(代码生成、报告撰写、数据分析)
- 需要任务结果有自检和修正能力,而不是“一次成型听天由命”
- 想把智能体协作从“演示 demo”推进到“工程可用”
- 团队里已经有人在喊“多智能体”,但实际跑起来就是几个 prompt 串在一起
这篇文章我会按真实项目落地的顺序来讲:先拆架构设计的取舍逻辑,再讲每个核心环节的实现细节,然后是完整实操流程和参数选择,最后把我踩过的坑和排查方法整理出来。全程按“能直接抄作业”的标准写,不搞概念堆砌。
2. 协作架构的整体设计与选型逻辑
2.1 为什么是“回路”而不是“流水线”
最常见的多智能体实现是流水线:A 做完交给 B,B 做完交给 C,结束。这种结构实现简单,但有个致命问题——错误会沿着流水线一路传递,且没有任何环节负责回头。A 输出里有个事实错误,B 基于它继续加工,C 再润色一遍,最后交付的东西错得很精致。
Loop Engineering 的核心改动是引入回边。评审环节发现问题的,不是打个分就完事,而是把问题结构化地退回给执行环节,执行环节带着反馈重新生成,再进入评审。这个回路可以跑固定轮数,也可以跑到评审通过为止。
我用一个实际对比说明差异。做代码生成任务时:
| 结构 | 首次通过率 | 平均修正轮数 | 最终可用率 |
|---|---|---|---|
| 单智能体直出 | 约 40% | 0 | 40% |
| 流水线(生成→格式化→输出) | 约 45% | 0 | 45% |
| 回路(生成→评审→修正) | 约 40% | 1.8 | 82% |
| 回路+多角色评审 | 约 38% | 2.1 | 91% |
数据是我自己项目里统计的,样本量几百次任务,不追求绝对精确,但趋势很清楚:回路结构牺牲了首次通过率(因为评审会挑刺),换来了最终可用率的大幅提升。这就是选它的理由。
2.2 角色划分的三种主流方案
角色怎么分,直接决定架构复杂度。我试过三种,各有适用场景。
方案一:三角色最小回路(规划者、执行者、评审者)
这是我最推荐的起步方案。规划者负责把模糊需求拆成可执行步骤,执行者按步骤产出,评审者对照原始需求检查产出。三个角色可以用同一个模型加不同系统提示词实现,成本低,调试直观。
方案二:五角色扩展回路(增加协调者和记忆管理者)
当任务步骤超过 10 步、需要跨轮次记住上下文时,加一个协调者负责调度,加一个记忆管理者负责压缩和检索历史信息。这个方案适合长周期任务,但调试难度明显上升,角色之间的通信开销也大。
方案三:动态角色(按任务类型临时生成角色)
让一个元智能体根据任务描述动态决定需要哪些角色。灵活但不可控,我只在探索性任务里用过,生产环境不敢上。
提示:新手直接上五角色,大概率会陷入“角色之间互相甩锅”的困境。先用三角色跑通一个完整回路,再按需扩展。
2.3 通信机制:共享状态还是消息传递
角色之间怎么交换信息,是架构里第二个关键决策。
共享状态是指所有角色读写同一个结构化对象(比如一个 JSON),每个角色只修改自己负责的字段。优点是实现简单、状态可追溯;缺点是并发写容易冲突,且角色容易“越界”改别人的字段。
消息传递是指角色之间通过显式的消息队列通信,每条消息有发送者、接收者、类型和载荷。优点是解耦彻底、可审计;缺点是需要设计消息协议,前期投入大。
我的实际选择是混合模式:主状态用共享对象,关键决策用消息传递留痕。具体来说,任务状态、当前轮次、评审结果这些放共享对象;评审意见、修正指令这些走消息,方便回溯“谁在什么时候说了什么”。
# 共享状态的最小结构示例 task_state = { "task_id": "t-001", "round": 0, "max_rounds": 5, "plan": None, "artifact": None, "review": {"passed": False, "issues": []}, "history": [] # 每轮的消息摘要 }这个结构看着简单,但每个字段都有讲究。round和max_rounds是回路的刹车,防止无限循环;review.issues必须是结构化的列表而不是一段文字,否则执行者没法精准修正;history只存摘要不存全文,控制上下文长度。
3. 核心环节的实现细节与实操要点
3.1 规划环节:把模糊需求变成可执行步骤
规划者的输出质量,决定了整个回路的天花板。我见过太多项目在这里偷懒,直接让规划者“列个步骤”,结果执行者拿到一堆没法落地的空话。
好的规划输出应该满足三个条件:步骤可独立执行、步骤之间有明确依赖、每步有可验证的完成标准。我通常要求规划者输出这样的结构:
{ "steps": [ { "id": 1, "action": "提取需求中的核心功能点", "depends_on": [], "done_criteria": "列出不少于3个功能点,每个有明确输入输出" }, { "id": 2, "action": "为每个功能点设计接口", "depends_on": [1], "done_criteria": "每个接口有参数列表和返回结构" } ] }done_criteria这个字段是精髓。没有它,评审者只能凭感觉判断“做完了没”;有了它,评审可以逐条对照,把主观判断变成客观检查。
实操心得:规划者的系统提示词里一定要加一句“如果需求信息不足以规划,先输出需要澄清的问题,不要臆测”。我吃过亏,规划者自作主张补全需求,结果整个回路都在为一个错误假设服务。
3.2 执行环节:带着反馈重新生成
执行者最容易被低估。很多人以为执行就是“把步骤翻译成结果”,实际上执行者要处理的是带约束的生成——既要完成当前步骤,又要吸收上一轮的评审意见,还不能破坏已经通过的部分。
我的做法是给执行者三段式输入:原始需求、当前步骤定义、上一轮评审意见(如果有)。评审意见必须转成“问题—位置—建议”的结构,而不是一段自然语言吐槽。
def build_executor_prompt(requirement, step, last_review): prompt = f"原始需求:{requirement}\n" prompt += f"当前步骤:{step['action']}\n" prompt += f"完成标准:{step['done_criteria']}\n" if last_review and not last_review["passed"]: prompt += "上一轮评审发现以下问题,请针对性修正:\n" for issue in last_review["issues"]: prompt += f"- 位置:{issue['location']},问题:{issue['problem']},建议:{issue['suggestion']}\n" return prompt这里有个细节:只把未通过的问题传给执行者,已通过的部分不要重复提。否则执行者会过度修正,把本来对的地方改坏。我早期没注意这点,导致回路震荡——这轮改好 A 弄坏 B,下轮改好 B 又弄坏 A。
3.3 评审环节:从“打分”升级为“定位”
评审者是整个回路的守门人。最差的评审是“给个分数”,好一点的评审是“列出问题”,最好的评审是“定位问题并给出可执行的修正方向”。
我要求评审者输出固定结构:
| 字段 | 含义 | 示例 |
|---|---|---|
| passed | 是否通过 | false |
| score | 0-100 分 | 72 |
| issues | 问题列表 | 见下 |
| issues[].location | 问题位置 | 第2步的接口定义 |
| issues[].problem | 问题描述 | 缺少错误返回结构 |
| issues[].suggestion | 修正建议 | 补充 error 字段及错误码 |
location字段是关键。没有它,执行者要自己猜“你说的问题在哪”,猜错就白跑一轮。有了它,修正可以精准打击。
注意:评审者的标准要跟规划者的
done_criteria对齐。我见过评审者用自己的一套标准挑刺,结果执行者按评审改完,规划者那关又过不了,来回扯皮。解决办法是评审提示词里明确写“以每步的 done_criteria 为唯一通过标准”。
3.4 回路控制:什么时候停,什么时候退
回路不能无限跑。我设三道刹车:
- 最大轮数:一般设 3-5 轮。超过 5 轮还没通过,说明任务定义或角色能力有问题,继续跑是浪费。
- 分数阈值:评审分数连续两轮不升反降,立即停止,标记为“需要人工介入”。
- 震荡检测:如果同一组问题在相邻两轮反复出现,说明执行者改不动,停止并输出诊断信息。
def should_stop(state): if state["round"] >= state["max_rounds"]: return True, "达到最大轮数" scores = [h["score"] for h in state["history"] if "score" in h] if len(scores) >= 2 and scores[-1] < scores[-2]: return True, "分数下降,疑似震荡" return False, ""震荡检测这个逻辑,是我被坑了好几次之后加的。有一次回路跑了 8 轮,每轮评审都说“接口定义不完整”,执行者每轮都改,但改的地方都不是评审真正在意的,纯属无效循环。加了震荡检测后,这种情况会在第 2 轮就被拦下来。
4. 完整实操流程与关键参数选择
4.1 从零搭一个三角色回路
下面是我实际项目里的搭建顺序,按这个走能少走弯路。
第一步:定义任务状态结构。先把task_state定下来,所有角色都围绕它工作。字段宁少勿多,后面按需加。
第二步:写三个角色的系统提示词。每个提示词只干一件事,不要试图让一个提示词兼顾多个角色。规划者提示词的核心是“拆解+可验证”,执行者核心是“按步骤+吸收反馈”,评审者核心是“对照标准+定位问题”。
第三步:实现回路主循环。伪代码逻辑如下:
def run_loop(requirement, max_rounds=5): state = init_state(requirement, max_rounds) state["plan"] = planner(requirement) while True: state["artifact"] = executor(requirement, state["plan"], state["review"]) state["review"] = reviewer(requirement, state["plan"], state["artifact"]) state["round"] += 1 state["history"].append(summarize(state)) stop, reason = should_stop(state) if state["review"]["passed"] or stop: state["stop_reason"] = reason break return state第四步:加日志和可观测性。每轮把输入输出完整落盘,包括提示词、模型返回、耗时、token 消耗。没有这些,出问题只能靠猜。
第五步:小样本调优。先拿 10 个任务跑,看通过率和轮数分布,再决定要不要调提示词或加角色。
4.2 参数选择:轮数、温度、模型搭配
这几个参数我调了很久,直接给结论。
最大轮数:3-5 轮是甜点区。设 2 轮太紧,很多任务需要两轮修正;设 8 轮以上,边际收益极低,还容易震荡。我现在的默认值是 4。
温度设置:规划者和评审者用低温度(0.1-0.3),保证输出稳定;执行者可以用稍高温度(0.5-0.7),保留一定创造性。但如果是代码生成这类严谨任务,执行者也压到 0.2。
模型搭配:不一定要用同一个模型。我的常用搭配是规划者和评审者用推理能力强的模型,执行者用生成速度快、成本低的模型。这样整体成本能降三到四成,质量损失很小。
| 角色 | 温度 | 模型倾向 | 理由 |
|---|---|---|---|
| 规划者 | 0.2 | 强推理 | 拆解质量决定上限 |
| 执行者 | 0.5 | 快且便宜 | 要跑多轮,成本敏感 |
| 评审者 | 0.1 | 强推理 | 判断要稳定一致 |
4.3 上下文管理:别让历史撑爆窗口
回路跑起来后,历史信息会快速累积。第 4 轮的时候,如果把前 3 轮的完整内容都塞进提示词,token 消耗会爆炸,而且模型注意力会被稀释。
我的做法是分层压缩:当前轮完整保留,上一轮保留评审意见和修正点,更早的轮次只保留一行摘要(“第1轮:接口定义不完整,已修正”)。这样上下文长度基本恒定,不会随轮数增长。
def build_context(state, current_round): ctx = [] for h in state["history"]: if h["round"] == current_round - 1: ctx.append(f"上一轮评审:{h['review_summary']}") elif h["round"] < current_round - 1: ctx.append(f"第{h['round']}轮摘要:{h['one_line']}") return "\n".join(ctx)这个压缩策略实测下来,4 轮任务的总 token 消耗比全量保留低 60% 以上,质量没有可感知的下降。
5. 常见问题与排查技巧实录
5.1 回路震荡:改了又好像没改
现象:评审每轮都提类似的问题,执行者每轮都改,但问题依旧。
排查思路:先看评审意见是否具体到位置。如果评审只说“质量不高”,执行者根本不知道改哪。再看执行者是否真的理解了意见,有时候是提示词里反馈信息被淹没在长上下文里。
解决:把评审意见单独成段,加粗标注;限制每轮只修最多 3 个问题,避免执行者顾此失彼;如果连续两轮同一问题,直接停止并输出诊断。
5.2 评审放水:什么都通过
现象:评审通过率异常高,但人工检查发现明显问题。
排查思路:检查评审提示词是否给了“通过”的默认倾向。很多提示词写“如果基本满足就通过”,模型会倾向于放水。
解决:改成“必须逐条对照 done_criteria,任何一条不满足即不通过”;给评审者加一个“找出至少一个可改进点”的强制要求,哪怕通过也要提改进建议。
5.3 执行者过度修正
现象:这轮改好了 A,下轮把 A 又改坏了。
排查思路:看是否把已通过的部分也传给了执行者。执行者看到“已通过”的内容,有时会手痒去动。
解决:只传未通过的问题;在提示词里明确“已通过部分不要改动”。
5.4 常见问题速查表
| 问题 | 可能原因 | 快速处理 |
|---|---|---|
| 回路不收敛 | 任务定义模糊 | 让规划者先输出澄清问题 |
| 评审标准漂移 | 评审提示词未对齐 done_criteria | 统一以 done_criteria 为准 |
| token 消耗过高 | 历史全量保留 | 启用分层压缩 |
| 角色互相甩锅 | 职责边界不清 | 每个角色只改自己负责的字段 |
| 首轮通过率低 | 评审过严 | 检查 done_criteria 是否合理 |
| 执行者输出格式乱 | 缺少输出结构约束 | 提示词里给 JSON schema |
5.5 几个我踩过的坑
坑一:角色用同一个提示词模板。我一开始图省事,三个角色共用一个模板只改角色名,结果规划者开始执行、执行者开始评审,乱成一锅粥。每个角色的提示词必须独立写,职责描述要具体到“你只做 X,不做 Y”。
坑二:忽略失败案例的归档。回路跑失败的任务,比成功的更有价值。我后来专门建了一个失败案例库,每次震荡或超轮数都归档,定期复盘,提示词优化全靠它。
坑三:过早追求多模型。一开始就想用不同厂商的模型搭配,结果接口适配、格式差异、超时处理耗掉大半精力。先用同一个模型把回路跑通,再考虑模型搭配降本。
坑四:没有人工兜底。回路不是万能的,有些任务就是需要人。我在流程里加了一个“人工介入”出口,当回路停止且未通过时,自动生成一份诊断报告推给人,而不是硬跑。
6. 从能跑到好用:几个进阶优化方向
回路跑通之后,想让它真正好用,还有几件事值得做。
评审者分级。我后来把评审拆成两级:一级评审做快速筛查,只判断“有没有明显硬伤”;二级评审做深度检查,对照 done_criteria 逐条过。一级不通过直接退回,不浪费二级评审的算力。这样整体成本降了约三成。
执行者缓存。相同步骤定义加相同反馈,执行者的输出可以缓存复用。在批量任务场景下,命中率能到 20% 左右,省下的调用很可观。
回路指标监控。我盯三个指标:首轮通过率、平均修正轮数、震荡率。首轮通过率突然下降,通常是任务类型变了或提示词被改坏了;平均轮数上升,可能是评审变严了;震荡率上升,说明执行者能力跟不上了。这三个指标比任何日志都直观。
任务类型路由。不是所有任务都值得跑回路。简单任务(格式化、翻译)单次调用就够,跑回路纯属浪费。我加了一个前置判断,根据任务复杂度决定走单次还是走回路,整体效率提升明显。
这套东西我从最初的三角色回路,迭代到现在带分级评审和任务路由的版本,前后改了十几版。核心体会就一句:多智能体协作的难点从来不在“多”,而在“协作”——让每个角色清楚自己该干什么、不该干什么,让回路有明确的收敛条件和退出机制,比堆角色数量重要得多。