☰
AutoGen多智能体协作实战:从ConversableAgent到GroupChat的完整指南
2026/10/3 15:41:04 网站建设 项目流程

年初整理项目笔记时,翻到标着“6.2”的那一节,内容正好是我用 AutoGen 搭多智能体协作系统的完整记录。这个版本序列号其实是自己给教程章节编的号,对应的框架版本是 AutoGen 0.2.x 这条线,也是社区里用得最顺手、资料最全的一个版本。项目本身不复杂,但里面涉及的消息流转、角色分工、终止条件设计、代码执行器配置这些点,几乎每一个都能让新手卡上半天。于是想把这部分经验整理成一篇能直接照着做的文章,既讲清楚框架的运行逻辑,也把实际项目中踩过的坑和排查过程一并写出来。

这篇文章不打算做成 API 手册式的内容,而是从一个实际任务出发:让两个 AI 智能体协作,一个负责写 Python 代码,一个负责执行代码并反馈结果。整个过程中会涉及 AutoGen 的几个核心概念,包括 ConversableAgent、GroupChat、GroupChatManager、代码执行器和对话终止机制。适合正在学 AutoGen 但卡在“示例能跑、自己搭就报错”阶段的开发者,也适合想搞清楚多智能体框架内部调度逻辑的人。按我自己的经验,把这篇文章涉及的案例跑通一遍,比单纯看十遍官方文档都管用。

1. 为什么需要 AutoGen:把多个模型调用编排成协作小组

1.1 单模型调用的痛点

在聊 AutoGen 之前,先看一个很常见的场景。假如你想让大模型完成一个稍微复杂点的任务,比如“根据给定数据生成图表并保存为图片”,如果你用裸的 OpenAI API 或者任何一家模型服务商的 SDK,通常只能做到:把任务一次性丢给模型,让它返回一堆 Markdown 代码块,然后自己手动复制到本地跑。跑出来报错,再把错误信息粘回去,让它重新给一版代码。来来回回好几轮,本质上是人肉充当了“调度器”的角色。

这个模式的问题很明显。第一,任务描述越复杂,单次调用越容易漏细节,因为模型上下文里混杂了任务描述、代码片段、错误日志,顺序一乱,回复质量立刻下降。第二,多轮协作时,你需要自己写代码维护对话历史、判断什么时候该停、什么时候该让另一个角色介入,这就是在重复造轮子。第三,真实业务里的任务往往需要“多角色配合”,比如一个角色负责理解需求,一个角色负责写代码,一个角色负责检查质量,这种协作逻辑如果用普通的 if-else 去写,代码会变得极其冗长且难以维护。

1.2 AutoGen 解决的核心问题:对话即程序

AutoGen 的核心思路和上面这种“人肉调度”完全不同。它把整个协作过程建模成一段多角色对话。每个角色都是一个 Agent,有自己的身份设定、系统提示词和模型配置。Agent 之间通过互相发送消息来推进任务,消息内容可以是普通文本,也可以是执行代码的指令。你不需要自己写状态机去管理“现在该谁发言”,AutoGen 内部会通过 GroupChatManager 这类机制来调度发言顺序。

这里有个很关键的理念叫conversation-driven programming,翻译过来就是“对话驱动编程”。传统编程是函数调用函数,数据在变量里流动;而 AutoGen 把“谁该做什么”这个决策分散到对话流程中。比如用户发一句话:“帮我算一下斐波那契数列前 20 项”。写代码的那个 Agent 收到后生成一段 Python 脚本,以消息形式发出;执行代码的 Agent 收到后运行脚本,把结果以消息形式返回;写代码的 Agent 再根据结果判断任务是否完成,决定要不要回复 TERMINATE 终止符。

这种模式的妙处在于,它天然支持“动态拆解任务”。用户不需要一开始就把任务描述得无比精确,而是让多个 Agent 在对话过程中逐步把需求澄清、拆分、验证。这非常贴合现实中团队协作的方式,也是多智能体框架相比单模型调用最有价值的差异点。

1.3 最小可用示例:两个 Agent 的“对话流水线”

我当时验证这个理念时,写了一个非常小的 Demo。用 ConversableAgent 创建两个角色,一个叫 coder,负责写代码;一个叫 executor,负责执行代码。用户只传入一句话,整个流程就自动跑起来了。先看最精简的代码,后面再逐段拆解:

from autogen import ConversableAgent llm_config = { "model": "gpt-4o-mini", "api_key": "你的API_KEY", } coder = ConversableAgent( name="coder", system_message="你是一个 Python 工程师。你只负责生成代码,不需要执行。", llm_config=llm_config, human_input_mode="NEVER", ) executor = ConversableAgent( name="executor", system_message="你负责执行代码并汇报结果。", llm_config=llm_config, code_execution_config={"work_dir": "coding", "use_docker": False}, human_input_mode="NEVER", ) result = coder.initiate_chat( recipient=executor, message="写一个 Python 程序,计算斐波那契数列前 20 项并打印结果。", max_turns=5, )

这个示例虽然简单,但它把 AutoGen 的骨架完整地体现了出来:两个具有不同职责的 Agent、一条初始消息、一个自动对话的回合上限。跑通这个以后,再去碰 GroupChat 和 GroupChatManager,就顺理成章了。

2. 核心机制拆解:Agent、GroupChat 与消息流转

2.1 三种基础角色:从 ConversableAgent 到 GroupChatManager

AutoGen 里的 Agent 体系,新手最容易混淆的就是 ConversableAgent、AssistantAgent、UserProxyAgent 和 GroupChatManager 这几个名字。

  • ConversableAgent是最通用的对话智能体,几乎所有自定义角色都可以从它派生。
  • AssistantAgent是内置的“助手”角色,默认系统提示词是“你是一个有用的助手,可以帮我解答问题”,适合作为普通问答场景的默认角色,但灵活性不如 ConversableAgent。
  • UserProxyAgent的角色比较特殊,它代表“用户”这个位置。它默认不接大模型,但可以执行代码、接收用户输入,作为对话的发起方或终止方存在。
  • GroupChatManager不是一个直接参与讨论的 Agent,而是负责管理一群 Agent 的“主持人”。它的职责包括决定下一个发言者是谁、消息是否该被广播给所有人、整个对话是否该终止。

实际项目中我几乎不用 AssistantAgent,因为 ConversableAgent 能完全接管它的功能,而且自定义程度更高。UserProxyAgent 则在需要人机协同或代码执行时特别好用,尤其是当你想让 AI 写代码、人来做最终确认时。

2.2 消息是如何“流转”起来的

AutoGen 的消息流转机制,本质上是一个“轮转链表”。GroupChat 中维护着一个 Agent 数组和一个消息数组。每一轮,GroupChatManager 会根据当前所有 Agent 的历史发言记录和任务描述,决定下一位发言者。你可以把这里想象成一场圆桌会议:会议主持人不一定懂所有专业内容,但他知道每个人的专长,也知道当前讨论进展,因此可以指定谁来说下一句。

更底层一些,每一轮发言其实是一次模型 API 调用。模型接收到当前 Agent 的系统提示词、对话历史和最新消息,生成回复,然后回复被追加到消息数组。这里面消耗 token 的地方有两个:一是每次调用都要把历史消息重新发送给模型,所以上下文越长,单轮成本越高;二是有些特殊消息(比如代码执行结果)如果太长,会进一步放大 token 开销。

为了控制这种开销,我通常会在 GroupChat 里设置一个 max_round 参数,限制最多对话轮数。类似这样:

group_chat = GroupChat( agents=[coder, executor], messages=[], max_round=10, speaker_selection_method="auto", )

max_round=10 意味着包括初始消息在内的最多 10 轮消息交换,超过后即便没有 Agent 说 TERMINATE,对话也会被强制截断。这个参数是防止“无限对话”兜底方案里最直接有效的一个。

2.3 终止机制:没有这个,你的钱包会哭

AutoGen 的对话不是永动机,它必须有一个明确的结束条件。我遇到过很多新手把 max_round 设得很大,结果两个 Agent 互相寒暄了几十轮,烧了一堆 token 还没完成任务。所以我强烈建议,在系统提示词里就把“何时结束”写清楚。

我自己常用的终止方案有三种:

  • 显式要求回复 TERMINATE:在系统提示词里写上“如果任务完成,只回复 TERMINATE,不要输出其他内容”。代码执行 Agent 确认结果无误后,就会触发这个标志。
  • 设置 max_round 兜底:不管任务是否完成,到达轮次上限就强制结束。这是保护机制,不是理想完成方式。
  • 代码执行成功即停:在 UserProxyAgent 场景下,代码执行完成后,框架会将执行结果发给对话方,由对话方自行判断是否需要继续。如果不需要继续,就回复终止。

比较推荐的做法是第一种和第二种结合。实际项目里我还会把“失败并重试”的逻辑也纳入系统提示词,比如“如果代码运行报错,最多尝试修复三次,三次后仍然失败,则回复 TERMINATE”。这样可以避免 Agent 陷入无休止的自我修复循环。

3. 实操:从零搭一个“AI 写码 + AI 跑码”的协作项目

3.1 项目目标与整体设计

这次要搭的系统,目标是让 AutoGen 自动完成一个常见的数据处理任务。具体需求是:给定一份 CSV 文件,AI 自动编写 Python 代码读取数据、做简单统计分析(比如计算平均值、最大值、最小值),然后运行代码并输出结果。

我把系统拆成两个 Agent:

  • Analyst Agent:负责理解任务、分析数据结构、编写 Python 代码,并把代码块发送给执行方。
  • Runner Agent:负责接收代码、在本地环境中执行、捕获输出,并把执行结果返回给 Analyst。

这里没有用 GroupChat,而是用一对一的 initiate_chat,是因为这个任务本身是典型的“写码 -> 执行 -> 反馈”线性流程,不需要三个人以上的复杂讨论。用 GroupChat 反而会因为调度逻辑带来额外的不确定性。这也是一个经验:并非所有多 Agent 任务都需要 GroupChat,越简单直接越好。

3.2 完整代码与逐步解析

先给出完整代码,再逐块解释。

import os from autogen import ConversableAgent llm_config = { "model": "gpt-4o-mini", "api_key": os.environ.get("OPENAI_API_KEY"), } analyst_prompt = ( "你是数据分析师。你会收到用户的数据处理需求。" "你负责编写 Python 代码来完成需求。" "代码必须完整可运行,不依赖未安装的第三方库。" "如果任务已经完成,请直接回复 TERMINATE。" ) runner_prompt = ( "你是代码执行员。你负责运行收到的 Python 代码。" "将执行结果完整返回给数据分析师。" "如果代码报错,请将错误信息原样返回。" ) analyst = ConversableAgent( name="analyst", system_message=analyst_prompt, llm_config=llm_config, human_input_mode="NEVER", ) runner = ConversableAgent( name="runner", system_message=runner_prompt, llm_config=llm_config, code_execution_config={ "work_dir": "data_analysis", "use_docker": False, }, human_input_mode="NEVER", ) task = "读取 data.csv,统计每列的平均值、最大值、最小值,打印统计结果。" result = analyst.initiate_chat( recipient=runner, message=task, max_turns=6, )

第一块是模型配置。OpenAI 兼容的 API 都能接入,你也可以通过 base_url 参数指向本地部署的服务或第三方兼容服务。api_key 建议通过环境变量读取,别硬编码在代码里,否则一旦代码被分享或提交到公开仓库,密钥就泄露了。

第二块是系统提示词。这里的关键是“如果任务完成,请直接回复 TERMINATE”这句话。没有这个,analyst 写完代码后可能还会自问自答一番,完全浪费 token。

第三块是创建两个 Agent。analyst 不配置 code_execution_config,意味着它只负责“写”不负责“跑”;runner 配置了 code_execution_config,工作目录是 data_analysis,并且 use_docker=False。这里 use_docker=False 表示代码在本机直接执行。如果要更安全的环境隔离,可以改成 True,前提是你本地装了 Docker。

第四块是发起对话。max_turns=6 给足两轮“写码 - 执行 - 反馈 - 修改”的余地,又不至于无限循环。实测下来,一次正常的数据统计任务,3 到 5 轮内基本都能完成。

这里我想特别讲一下 work_dir 的坑。如果你把它设成一个不存在的目录,AutoGen 在第一次执行代码前会自动创建它。但如果你在多个项目里复用自己的代码,一定要保证每次使用不同的 work_dir,否则旧项目的残留文件会干扰新项目的运行。

3.3 关键参数速查表

整理一个针对 ConversableAgent 和 GroupChat 的关键参数表,表格里的内容都是我实际用过的配置,直接抄作业也不会出大问题。

参数作用推荐配置
nameAgent 的唯一名称简短小写字母
system_message角色设定与行为约束写明职责、输出格式、终止条件
llm_config模型与密钥配置预留小模型(如 gpt-4o-mini)跑常规任务
human_input_mode是否打断对话请求用户输入NEVER 适合全自动场景
code_execution_config代码执行器配置work_dir 指定工作目录,use_docker 视情况而定
max_turns发起对话的最大轮数3 到 10 之间最合适
max_roundGroupChat 的最大消息轮数10 到 20 比较稳妥

还有一个容易忽略的参数是silent。在调试阶段把它设为 False,可以看到每个 Agent 的内部思考日志和工具调用记录。我几乎在所有项目的开发阶段都会打开这个开关,因为多 Agent 系统出错时,定位问题最快的方式就是看日志里“每一步谁说了什么”。

analyst = ConversableAgent( name="analyst", system_message=analyst_prompt, llm_config=llm_config, human_input_mode="NEVER", silent=False, # 调试时开启,方便观察内部过程 )

讲个真实的调试经历。有一次 runner 执行完代码后,analyst 仍然不停发消息,排查了很久才发现是系统提示词里没有明确结束条件。加上 TERMINATE 指令后,问题立刻消失了。这种问题如果你不开 silent 日志,完全看不出原因,因为表面上只是“对话没有停”,但不知道是哪个环节没有触发终止。

4. 常见问题与排查技巧实录

4.1 Agent 陷入死循环:明明是完成状态,还在互相回复

这是多智能体系统里最经典的问题。表现是:某个 Agent 已经给出了正确答案,另一个 Agent 还在回复“感谢你的回复,我再看一眼”之类的废话,然后整个对话进入无限循环。根本原因通常是系统提示词里没有明确的终止条件,或者终止条件被写得太模糊。

我自己的排查顺序是:

  1. 打开 silent 日志,确认最后一个有意义的回复是谁发的。
  2. 检查该 Agent 的 system_message 里有没有“任务已完成则回复 TERMINATE”的指令。
  3. 检查 max_turns 或 max_round 是否设成了很大的值,导致兜底失效。
  4. 看看是否配置了 overly 严格的“完成标准”,比如要求代码运行零错误零警告,这在现实中很难满足。

修复后我一般会把 max_turns 调低一些,避免再被类似问题打到。如果是因为任务确实复杂导致需要更多轮次,我宁愿拆分成多个小的子任务,也不愿意放宽兜底上限。

4.2 上下文窗口过载:长对话直接把 token 预算打爆

AutoGen 的对话会累积全部历史消息。只要任务稍微复杂一点,几轮之后历史消息可能就达到数千 token。如果代码执行结果很长(比如打印了一大堆日志),很容易把上下文窗口撑爆,报出 context length exceeded 之类的错误。

对付这个问题,我通常从三个方向解决:

  • 精简每轮消息内容:在代码执行 Agent 的系统提示词里,明确要求只返回结果摘要,比如“最多返回 500 字符”。这样能大幅压缩历史体积。
  • 调整模型:把 llm_config 里的模型换成支持更大上下文的版本,比如 128k 上下文的模型。但这只是治标,不是治本。
  • 拆分子任务:如果任务天然很长,就把它切分成多个独立阶段,每个阶段单独发起一次对话。AutoGen 里不同的 initiate_chat 调用之间,历史消息是隔离的,这样就不会互相拖累。

我踩过一次很深的坑:让两个 Agent 合作分析一份大型 CSV,结果每隔几轮就 context length exceeded,然后整个对话中断,前面的工作全部白费。后来我把“数据预览 + 全量分析”拆成了两个 Agent 和一个常规脚本,逻辑上反而更清晰了。

4.3 代码执行器相关:本地路径、依赖环境与超时

代码执行器在设计上有几个容易被忽视的点。第一,work_dir 尽量用绝对路径,尤其是当你用相对路径时,进程的工作目录和你的预期可能不一致,导致找不到文件。第二,执行 Python 代码的 Agent 默认会使用当前的 Python 环境,如果你代码里用了某些第三方库而环境里没装,执行会直接报 ModuleNotFoundError。第三,有些代码会阻塞,比如等待用户输入、启动一个不结束的进程,如果没有超时控制,整个对话就会卡死。

针对阻塞问题,一个比较实用的技巧是给代码执行器配置执行超时。在 code_execution_config 里可以加 timeout 参数,比如 timeout=60,表示单次执行最多 60 秒。超过就直接终止这次执行并返回超时信息,避免把整个任务拖死。

code_execution_config={ "work_dir": "data_analysis", "use_docker": False, "timeout": 60, }

如果项目经常需要执行重型计算,建议使用 local 的 conda 环境或者 Docker 镜像,把要用的依赖都预先装好。用 Docker 时注意挂载目录,否则容器内看不到本地的 CSV 文件,也会让你排查半天。

4.4 常见问题速查表

现象可能原因解决方案
对话永远不结束系统提示词缺少终止条件增加“任务完成回复 TERMINATE”
模型报 context length exceeded历史消息过长缩短回复、换大上下文模型、拆分子任务
执行代码后找不到文件work_dir 相对路径错误使用绝对路径,或检查运行时工作目录
ModuleNotFoundError当前环境缺少依赖用预装依赖的镜像或 conda 环境
代码执行一直卡住代码阻塞或执行时间过长配置 timeout 参数
Agent 角色串台两个 Agent 的 system_message 职责重叠明确每个 Agent 的职责边界
输出内容被截断max_turns 太少适当调大,但不超过 10
API 调用报错 401api_key 无效或环境变量读取失败检查环境变量,确保没硬编码错

4.5 一个隐藏得很深的坑:Agent 之间的角色串台

多 Agent 系统里很反直觉的一个问题是:如果两个 Agent 的 system_message 写得不够有区分度,模型很容易把两个角色当成同一个人。比如 analyst 的系统提示词里你写了“你负责分析数据”,runner 的提示词里你写了“你负责执行代码”,但它们没有明确“你不负责分析”“你不负责写代码”的否定边界,模型有时就分不清。

解决办法是在每个 Agent 的系统提示词里同时写清“该做什么”和“不该做什么”。比如 runner 的提示词可以加上“你绝不编写代码,你只负责运行代码并返回结果”。这个小技巧后期帮我省了很多排查成本。

5. 框架定位与扩展:什么时候该选 AutoGen

很多人问我 AutoGen 和 LangChain、CrewAI 这些框架有什么区别,哪个更好。坦白说,没有绝对的“更好”,只有“更适合”。我用过一个比喻:LangChain 像多功能的瑞士军刀,什么都能干一点,但多智能体协作不是它的主场;AutoGen 更像一套专门为多人协作设计的会议室系统,里面的角色、发言顺序、结束规则都是围绕“团队对话”来设计的。CrewAI 则是另一个方向的尝试,它更强调“角色扮演 + 任务清单”的抽象,门槛低但灵活性稍弱。

三个框架各有各的物理边界。AutoGen 的优势在于对“对话流转”这种编程模型的支持非常原生,尤其是 GroupChat 的发言者选择机制,可以做到动态决定下一个说话人;而 LangChain 的 Agent 更多是“单 Agent + 工具调用”范式,如果任务需要多个 Agent 互相辩论、逐步评审,用 AutoGen 会更自然。CrewAI 的优势在于配置式开发,适合快速原型验证;内部如果要介入复杂的状态逻辑,反而会受限。

当然,这里也要提醒一下版本问题。AutoGen 0.2.x 和 0.4.x 的 API 差异非常大。0.4 之后的架构更像一套“事件驱动 + 异步任务”的系统,组件的命名和组织方式都变了,很多网上教程的代码在 0.4 里已经跑不起来。我在文章里用的是 0.2.x 系列的写法,这是目前社区资料最多、最不容易踩坑的版本线。如果你是新项目,建议优先参照官方最新稳定版文档,同时保持对 API 变化的敏感。

5.1 扩展方向:注册函数、集成 RAG 与人工审批

AutoGen 真正强大的地方不局限于让两个 Agent 聊天。我实际用得比较多的扩展方向有三个。

第一个是注册自定义函数,让 Agent 可以在对话中调用你的业务函数。它和 LangChain 里的 tool 概念类似,但 AutoGen 把它融入到了对话流程中。比如你可以注册一个 send_email 函数,Agent 需要发邮件时直接调用,而不是生成一堆代码让别人去执行。这比“代码执行 + 解析输出”要稳定得多。

from autogen import register_function def send_email(to: str, subject: str, body: str) -> str: # 实际发送逻辑 return f"邮件已发送给 {to}" register_function( send_email, caller=analyst, executor=runner, description="发送邮件通知,参数包括收件人、主题和正文。", )

第二个是集成检索增强生成(RAG)。当任务需要读取本地知识库或文档时,可以先通过向量数据库检索出相关内容,再把检索结果拼接到某个 Agent 的系统消息里。这样那个 Agent 就相当于一个“带资料库的专家角色”,回答问题时可以引用具体文件内容,而不是全靠模型内部知识。

第三个是人工审批环节。在自动化流程里,如果某个步骤涉及不可逆操作(比如删除文件、发送邮件、修改数据库),我倾向于让相关 Agent 的 human_input_mode 设为 ALWAYS,强制它在执行前征求用户确认。这个设计虽然牺牲了一些自动化程度,但能避免很多不可挽回的事故。

5.2 我的选型建议

简单总结一下我现在的选型逻辑:如果任务重点是“让 AI 和用户反复迭代完成任务”,比如用户描述需求,AI 写代码并不断修改,那么 AutoGen 非常合适;如果任务重点是“按规则串联多个工具调用”,比如抓取网页、调用搜索、发送消息,LangChain 的链式调用更方便;如果目标是快速搭建一个“角色扮演 + 任务分配”的演示项目,CrewAI 上手更快。

多个框架混用也完全可行。比如用 LangChain 做文档加载和分割,用向量库做检索,再把检索结果交给 AutoGen 的 Agent 做最终决策,这种组合在实践中很常见。不要把框架当成互斥选项,多智能体项目本质上拼的是对任务的拆解能力,框架只是工具。

5.3 一点版本经验

最后说一句版本经验:网络上很多 AutoGen 的实战文章和代码已经过时,尤其教程里如果出现from autogen.agentchat import ConversableAgent,那大概率是 0.2 系列的写法;如果用到了agentchat目录下的AssistantAgent和偏事件驱动的接口,就要小心是不是 0.4 系列了。初学者最好直接锁定官方最新稳定版文档,跑通一个最小示例后,再回头对比网上过时代码里的差异,反而能收获不少。

我在这个项目里最大的体会是,多智能体系统调试和传统程序调试有个本质区别:你很难靠断点单步跟踪一个“模型正在推理”的过程,所以日志和终止条件就是你的全部抓手。想清楚“对话在什么情况下一定结束”,比想清楚“每一步具体怎么回答”重要得多。先让系统能收住,再让它跑得聪明。这篇文章里的代码实例只是一个起点,后续把函数注册、人工审批、RAG 这些模块加进去,你完全可以根据自己的业务场景拼出一套更顺手的自动化系统。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询