• 正文
  • 相关推荐
申请入驻 产业图谱

多智能体不是越多越强:我用100行Python,拆穿AI Agent协作的真相

23小时前
116
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

开场白:先泼一盆冷水

打开任何一个 AI 框架的官网,你都会看到类似的宣传图:五六个圆圈,每个圆圈里画个小机器人,箭头连来连去,配文“多智能体协作,模拟人类团队”。看起来特别高级,仿佛你只要把智能体拆成“产品经理”“程序员”“测试员”,它们就能像一家创业公司一样自动运转,你躺着收钱。

现实是什么?现实是我见过太多人把一个本来单智能体能干好的活,硬拆成五个角色,然后花两周时间调试“为什么产品经理说的话程序员理解错了”。最后系统跑通了,效果还不如原来那个单智能体,token 消耗翻了四倍。

所以这篇文章要讲三件事:

1、单智能体的能力边界到底在哪,什么时候真的需要拆;

2、手写一个最小多智能体系统,不用任何框架,让你看清里面到底是什么;

3、通信协议怎么设计,才能避免“传话游戏”式的信息损耗。

老规矩,去魅优先,代码说话。

01、单智能体的能力边界:大部分时候,你不需要拆

1.1 先想清楚:拆分的成本是什么

很多人以为多智能体是“免费的午餐”,多几个角色,能力更强,有什么不好?但每多一个智能体,你就多付出三样东西:

第一:token 成本翻倍

每个智能体都要带上自己的系统提示词、历史消息、工具定义。原来一次调用能解决的事,现在要三五次调用,每次还都拖着一坨上下文。

第二:信息损耗

智能体 A 把结果传给智能体 B,中间必然经过一次“总结—转述”的过程。就像你让同事帮你带话给老板,带三层之后,“周五前完成初稿”就变成了“尽快交终稿”。这个问题严重到我们要用整个第三节来讲它。

第三:调试地狱

单智能体出错,你看一条对话记录就知道哪句话没写好。多智能体出错,你要在五份对话记录里找“到底是谁先理解错的”,而且错误会传染:A 理解错了,B 基于错误结论继续推理,到 C 那里已经面目全非。

1.2 那单智能体到底卡在哪?

既然拆分这么贵,为什么还有人拆?因为单智能体确实有几堵墙,撞上了就是撞上了:

🧱 墙一:上下文污染

想象你让一个智能体既写代码又审代码。它刚写完一段代码,你让它审,它会审出什么?大概率是“写得不错,逻辑清晰”。因为审核所需的“挑刺视角”和写作时的“辩护视角”在同一个上下文里打架,而模型天然倾向于跟自己已经生成的内容保持一致,这是心理学上的承诺一致性在语言模型上的翻版。

这时候把“审核”拆成一个独立智能体,它看不到写作过程,只看到成品,挑刺就狠得多。

🧱 墙二:注意力稀释

系统提示词写到三千字以上,你会发现模型开始“选择性失忆”。你在第 47 条规则里写了“输出金额必须保留两位小数”,它偏偏就忘这条。因为一个上下文里塞的指令越多,每条指令分到的“注意力”就越少。拆分之后,每个智能体只带自己那一小份规则,谁也不用背全套家规。

🧱 墙三:角色冲突

有些任务天然要求“两种互相矛盾的性格”。比如营销文案生成:你既要一个“敢吹”的创意角色,又要一个“较真”的合规角色。让一个智能体同时“放飞”和“收敛”,结果往往是两头不到岸,文案平庸,合规也马虎。

1.3 一条实用的判断标准

我自己用的标准很简单,一句话:当你在系统提示词里写“先扮演 A 做 X,然后切换到 B 做 Y”的时候,就该拆了

如果你的提示词是“你是一个数据分析助手,请分析用户上传的表格”,不用拆,这就是一个角色干一件事。如果写成了“你先作为规划师拆解任务,再作为工程师执行,最后作为质检员用批判性眼光审查自己刚才的输出”,恭喜,你已经在一个上下文里硬塞三个人格了,拆吧。

反过来说,以下情况不要拆

·任务本身很短(一两轮就能完成),拆分的通信开销比任务本身还大;

·各“角色”之间需要共享大量细节,拆开后传递成本极高;

·你只是觉得“多智能体听起来更酷”。

02、手写一个最小多智能体系统

好,假设你确认自己真的需要拆。现在我们不用 AutoGen,不用 LangGraph,不用 CrewAI,就用裸 Python + 一个 OpenAI 兼容接口,手写一个三角色系统:

·规划者(Planner):接到用户需求,拆成步骤;

·执行者(Executor):按步骤逐个执行;

·审核者(Reviewer):检查执行结果,不合格打回重做。

为什么要手写?因为框架把“消息怎么传、状态怎么存、循环怎么控制”全藏起来了,你只看到几个装饰器,出了问题两眼一抹黑。手写一遍之后你再去用框架,就知道那些装饰器背后是什么了。

2.1 基础设施:一个函数搞定模型调用

from openai import OpenAI

client = OpenAI(
api_key="sk-xxxx",
base_url="https://api.deepseek.com",
)

def call_llm(system_prompt: str, user_content: str) -> str:
"""所有智能体共用的底层调用。注意:每次调用都是全新上下文。"""
resp = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_content},
        ],
temperature=0.3,
    )
return resp.choices[0].message.content

看到没有,所谓“智能体”,在最底层就是同一个模型 + 不同的系统提示词。规划者和执行者用的是同一个 deepseek-v4-flash,区别只是开场白不一样。这是第一层去魅:多智能体不是多个大脑,是一个大脑反复“入戏”扮演不同角色。

2.2 定义消息格式:先立规矩,再开工

在写角色之前,先定义智能体之间传什么。这一步很多人跳过,直接传自然语言字符串,这就是后面“传话游戏”的祸根。我们用结构化消息:

from dataclasses import dataclass, field

@dataclass
class Task:
    step_id: int
instruction: str        # 这一步要做什么
context: str            # 做这一步需要知道的背景(原始信息,不是转述)
result: str = ""        # 执行结果
review_passed: bool = False
review_comment: str = ""

@dataclass
class Blackboard:
"""共享黑板:所有智能体都能读,避免层层转述"""
user_request: str                       # 用户原始需求,一字不改
tasks: list[Task] = field(default_factory=list)
    max_retries: int = 2

注意 Blackboard 里的 user_request:用户的原始需求会原封不动地传给每一个智能体,而不是让规划者“转述”给执行者。这是本文最重要的设计决策之一,第三节细讲。

2.3 规划者:只负责拆,不负责干

PLANNER_PROMPT = """你是任务规划者。把用户需求拆解成 2-4 个可独立执行的步骤。
只输出 JSON 数组,每个元素形如:
{"step_id": 1, "instruction": "具体做什么", "context": "这一步需要的背景信息"}
不要输出任何其他内容,不要 markdown 代码块。"""

import json

def plan(board: Blackboard) -> None:
    raw = call_llm(PLANNER_PROMPT, board.user_request)
    steps = json.loads(raw)
    board.tasks = [Task(**s) for s in steps]

规划者的系统提示词就五行。它不知道怎么写代码,不知道怎么审核,它这辈子只干一件事:拆任务。这就是“注意力不稀释”的具体体现。

2.4 执行者:拿到什么干什么

EXECUTOR_PROMPT = """你是任务执行者。根据给定的步骤指令完成任务。
直接输出结果,不要解释你在做什么,不要客套。"""

def execute(board: Blackboard, task: Task) -> None:
# 关键:把用户原始需求 + 本步骤指令一起给它,而不是只给转述后的指令
content = f"""## 用户原始需求
{board.user_request}

## 你当前要完成的步骤
{task.instruction}

## 相关背景
{task.context}

## 之前步骤的成果
{format_previous_results(board, task.step_id)}"""
if task.review_comment:  # 如果是被打回重做,带上审核意见
content += f"\n\n## 上次被打回的原因(请修正)\n{task.review_comment}"
task.result = call_llm(EXECUTOR_PROMPT, content)

def format_previous_results(board: Blackboard, current_id: int) -> str:
    done = [t for t in board.tasks if t.step_id < current_id and t.review_passed]
if not done:
return "(无)"
return "\n".join(f"步骤{t.step_id}:{t.result}" for t in done)

2.5 审核者:铁面无私,因为它没有包袱

REVIEWER_PROMPT = """你是质量审核员。判断执行结果是否满足步骤要求和用户原始需求。
只输出 JSON:{"passed": true/false, "comment": "不通过时给出具体、可操作的修改意见"}
审核标准:宁可错杀,不可放过。"""

def review(board: Blackboard, task: Task) -> None:
    content = f"""## 用户原始需求
{board.user_request}

## 步骤要求
{task.instruction}

## 执行结果
{task.result}"""
raw = call_llm(REVIEWER_PROMPT, content)
    verdict = json.loads(raw)
    task.review_passed = verdict["passed"]
    task.review_comment = verdict.get("comment", "")

审核者最大的优势是什么?它没看过执行者的“心路历程”。它不知道执行者纠结了什么、妥协了什么,它只看到需求和结果,像一个刚进门的客户一样冷酷。这就是第一节说的“上下文隔离带来的批判性”。

2.6 调度循环:把三个角色串起来

def run(user_request: str) -> Blackboard:
    board = Blackboard(user_request=user_request)
    plan(board)
print(f"规划完成,共 {len(board.tasks)} 步")

for task in board.tasks:
for attempt in range(board.max_retries + 1):
            execute(board, task)
            review(board, task)
if task.review_passed:
print(f"步骤{task.step_id} 通过(第{attempt+1}次尝试)")
break
print(f"步骤{task.step_id} 被打回:{task.review_comment}")
else:
print(f"步骤{task.step_id} 重试耗尽,带病放行")
            task.review_passed = True  # 实际项目里这里应该上报人工
return board

# 跑起来
board = run("帮我写一份介绍AI Agent的 300 字文章,要求提到Loop和Multi-Agent,风格严谨")

不到一百行,一个带“规划—执行—审核—打回重做”完整闭环的多智能体系统就跑起来了。没有魔法,没有黑箱:

· 所谓“协作”,就是一个 for 循环;

· 所谓“角色”,就是不同的系统提示词;

· 所谓“记忆”,就是那个 Blackboard 数据类;

· 所谓“反思重做”,就是把审核意见拼进下一次调用的提示词里。

框架帮你做的,无非是把这些封装得更优雅、加上并发和容错。理解了这一百行,你看任何多智能体框架的文档都不会再头晕。

03、通信协议:如何避免“传话游戏”

现在讲全文最值钱的部分。多智能体系统翻车,九成翻在通信上。

3.1 什么是“传话游戏”式损耗

小时候玩过传话游戏吧?第一个人说“小明明天早上八点在东门集合记得带伞”,传到第五个人变成“小明带伞”。每一次转述都是一次有损压缩。多智能体系统里,这个游戏每时每刻都在上演:

用户:“帮我分析这份销售数据,重点看华东区 Q3 环比下滑的原因,如果是渠道问题就单独列出来。”

规划者转述给执行者:“分析销售数据中华东区的下滑情况。”

看到了吗?“Q3”“环比”“渠道问题单独列出”三个关键约束全丢了。执行者拿着残缺的指令认认真真干活,产出一份“华东区全年销售概览”——每个智能体都没犯错,但系统整体答非所问。

更阴险的是,这种损耗不报错。代码写错了会抛异常,信息传丢了系统照常运行,只是结果越来越歪。你 debug 半天,发现每个环节的模型输出“看起来都挺合理”。

3.2 对策一:原始需求直通车

这就是为什么第二节的代码里,user_request 被原封不动塞进了每个智能体的输入。原则一句话:

转述可以有,原文不能丢。任何下游智能体都必须能看到用户的原始需求

规划者拆解出的 instruction 是导航,用户原文是地图。导航可能算错路线,但只要地图在手,执行者还能自己校正。成本呢?多几百个 token 而已,比起返工重跑,便宜到可以忽略。

3.3 对策二:结构化消息,别传自由文本

智能体之间用 JSON(或 dataclass)传消息,而不是一段自然语言,好处有三:

字段是强制的

定义了 {"step_id", "instruction", "context"},规划者就必须为每个步骤填 context,想偷懒都不行。自由文本呢?模型今天心情好写得详细,明天就给你省略。

丢失是可检测的

结构化字段缺了,json.loads 直接报错,或者你可以用一行断言拦住。自由文本丢了信息,鬼知道。

下游解析零歧义

“先做 A 再做 B,不过如果 C 的话也可以先做 B”——这种话让下游模型再理解一遍,又是一轮损耗。结构化之后没有“理解”环节,只有“读取”环节。

3.4 对策三:黑板模式 vs 接力模式

多智能体通信有两种基本拓扑:

接力模式

A 的输出作为 B 的输入,像流水线。优点是简单、隔离性好;缺点就是传话游戏——链条越长,损耗越大。

黑板模式

所有智能体读写同一块“黑板”,每个人都能看到全局状态。优点是没有转述损耗;缺点是黑板越写越大,且“谁都能写”需要规矩。

实战建议:小系统用黑板,大系统黑板 + 接力混合——原始需求和关键结论上黑板,过程性的中间产物走接力,用完即弃。我们第二节的代码就是这个混合结构:user_request 常驻黑板,而“之前步骤的成果”只挑审核通过的传给下一步。

3.5 对策四:审核者要对齐“原始需求”

一个隐蔽的坑:审核者如果只对照“步骤指令”检查,那规划者拆错了,审核者会把错误的执行结果判为“通过”,因为它确实完美符合那个错误的指令。

所以第二节代码里,review 的输入同时包含“用户原始需求”和“步骤要求”。审核者不只是流水线上的质检员,还是用户在系统内部的“代言人”。这一个设计,能拦住大量“每步都对、整体全错”的诡异翻车。

3.6 一个反直觉的结论

最后说一个很多人不愿意接受的事实:

多智能体系统的上限,取决于通信协议的设计,而不是智能体的数量

三个角色 + 严谨的消息协议,吊打十个角色 + 自由文本乱传。就像公司里,五个人加一套清晰的文档规范,干得过二十个人在微信群里吼。加智能体是加法,修通信是乘法。

结语:三件事总结

总结一下这篇讲的三件事:

1、别为了拆而拆。单智能体撞上“上下文污染、注意力稀释、角色冲突”这三堵墙时,才值得拆。判断标准:提示词里出现“先扮演 A 再扮演 B”,就是拆分信号。

2、多智能体没有魔法。一个模型 + 几段系统提示词 + 一个调度循环 + 一个共享数据结构,一百行裸 Python 就能写出完整的“规划—执行—审核”闭环。框架只是把这一百行封装得漂亮些。

3、通信协议决定上限。原始需求直通车、结构化消息、黑板与接力的混合拓扑、审核者对齐原始需求——四条对策,专治“传话游戏”。

下一篇预告:多智能体的错误处理与自我纠正

多智能体系统跑起来之后,智能体可能在真实环境中会失败:工具报错、格式错误、逻辑死循环。我们将引入"反思"机制:让智能体检查自己的输出,判定输出结果是否正确。我们下篇见。

 

我是写代码的中年人,长期专注于 AI 算法、智能体、以及 AI 工程化落地。

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录