代码获取:本文相关的完整 Demo 源码已开源,欢迎参考 GitHub 仓库:llm-dome/learn
核心思想
AutoGen 的核心思想是通过对话实现协作。它将多智能体系统抽象为一个由多个“可对话”智能体组成的群聊。开发者可以定义不同角色(如 Coder、ProductManager、Tester 等),并设定它们之间的交互规则(例如:Coder 写完代码后由 Tester 自动接管)。
任务的解决过程,就是这些智能体在群聊中通过自动化消息传递,不断对话、协作、迭代,直至最终达成目标的过程。
模型客户端配置
首先,我们先来了解一下模型客户端的配置。
1 | |
以上代码是实例化一个模型客户端的 Demo,指定了各种参数:使用的 LLM、最大 Token 数、模型信息等。
核心参数解析:Temperature
其中,temperature 是一个极其关键的参数,它控制着模型输出概率分布的“尖锐程度”。
简单来讲,在模型预测下一个词时,会把每个候选词的原始分数(Logits,
假设现在有三个候选词及其原始分数:
- 词 “好”:
3分 - 词 “棒”:
2分 - 词 “烂”:
1分
根据不同的
- 假设一:
(原始状态)
运算后的分数不变,原始分数直接参与后续概率计算。 - 假设二:
(趋于保守)
分数会变得苛刻,放大差距。运算后:
词 “好”(3/0.5=6分)> 词 “棒”(2/0.5=4分)> 词 “烂”(1/0.5=2分),“好”的优势被进一步拉大。 - 假设三:
(趋于随机)
分数变得宽容,缩小差距。运算后:
词 “好”(3/2=1.5分)> 词 “棒”(2/2=1分)> 词 “烂”(1/2=0.5分),各个词被选中的概率变得更加相近。
总结来说,temperature 值越大,各个词之间的原始差距被缩得越小,模型选词越偏向随机发散;值越小,差距被放大,模型越偏向确定保守。
注: 实际模型中,运算后的分数最终还会通过 Softmax 函数(指数归一化)转换为
的概率!上面的 3/2/1分只是为了方便理解是如何放大/缩小差距的。真实模型中,转换后的概率大致呈现下方表格所示的动态变化趋势:
| Temperature |
效果 | 概率分布示例长什么样 |
|---|---|---|
| 极度确定,永远选最高概率词 | 好: 99%,棒: 1%,烂: 0% | |
| 保守,高概率词碾压其他 | 好: 80%,棒: 18%,烂: 2% | |
| 平衡 | 好: 55%,棒: 35%,烂: 10% | |
| 使用模型原始分布,不做任何压缩 | 好: 45%,棒: 30%,烂: 25% | |
| 分布趋于均匀,各词概率接近,输出混乱 | 好: 36%,棒: 34%,烂: 30% |
核心参数解析:top_p
top_p(Nucleus Sampling,核采样)的作用简单来说就是**“砍尾巴”**。它通常和 temperature 配合使用:先由 temperature 处理数据得出概率分布,再根据 top_p 的阈值比例来砍掉尾巴,最后才进行选词。
假设经过 temperature 处理后,候选词的概率如下:
- “好”:45%
- “棒”:25%
- “绝”:15%
- “行”:8%
- “烂”:5%
- …(其他低概率词):合计 2%
如果我们设置 top_p = 0.9(即 90%),模型会按概率从高到低累加,只截取累积概率刚达到 90% 时的单词集合:
- 第一名 “好” (45%) —— 累计:45%(没到 90%,继续)
- 第二名 “棒” (25%) —— 累计:70%(没到 90%,继续)
- 第三名 “绝” (15%) —— 累计:85%(没到 90%,继续)
- 第四名 “行” (8%) —— 累计:93% (超过了 90%!停!)
结果:只有 【好、棒、绝、行】 这 4 个词留下来进入最终的抽签池(并且这四个词的概率会按比例重新放大归一化到 100%)。而排在后面的“烂”以及更糟的词,将被直接拦腰斩断、彻底除名。哪怕它原本还有 5% 的机会,现在的入选概率也是 0%。
temperature 和 top_p 的协同工作
在底层运行中,这两个参数可以说是“黄金搭档”。它们的协同工作流程如下:
- 算概率:先使用
temperature来决定候选词分差是被放大还是缩小,计算出一个带有特定“感情色彩”的概率分布。 - 砍尾巴:再用
top_p执行“截断”——看着调完之后的概率单,从上往下加,一旦累计达到阈值(如0.9)就划线,把排在后面的“弱势词”全扔掉。 - 终极抽签:从活下来的“精英”圈子里,按它们的相对概率随机抽取一个词输出。
核心区别总结:
| 比较维度 | Temperature (温度) | top_p (核采样 / 截断采样) |
|---|---|---|
| 手段 | 像**“滤镜”**:改变所有人分数的差距。 | 像**“剪刀”**:直接把排在后面的选项咔嚓剪掉。 |
| 态度 | 温柔:即使把 0.0001% 的机会,理论上没绝杀。 |
冷酷:只要你没挤进去前 |
| 解决的问题 | 防止输出过于死板(提高 |
剪除“长尾噪音”。语言模型词汇表有几万个词,往往有几千个极低概率的词(比如乱码或离谱生僻字)。top_p 的任务就是把这几千个垃圾选项永远关在门外,防止哪怕万分之一的“爆冷抽中”。 |
核心参数解析:json_output 与 structured_output
这两个参数通常一起出现,它们主要用于指导或强制大模型以结构化的 JSON 格式返回输出。
当
json_output=True时:
它就像是在提示里告诉模型:“请自觉一点,把结果按照 JSON 的格式发给我。”- 模型表现:它会尽力返回 JSON。
- 潜在问题:因为它只是“尽力”,有时候它可能会漏写一个逗号、多加一个引号、把字段名拼错(比如把
name写成了first_name),甚至在 JSON 外面还啰里啰嗦加一句“这是您要求的 JSON 代码”。 - 可能后果:当你的程序尝试去解析(Parse)这段文本时,极易因为格式或类型的细微错误而自动报错挂掉。
当
structured_output=True时:
这相当于直接给大模型发了一张“固定格式的机读答题卡”。此时,这不再仅仅是一个提示语的作用,而是从大模型底层的推理生成机制层面,严格限制了模型的输出概率分布。它能确保输出百分之百符合你预先定义的 JSON Schema(数据结构)约束,连多余的一个废话字符都不会出现。
**避坑指南:**虽然当前绝大多数模型都能听懂“输出 JSON”的指令(即支持
json_output),但底层引擎直接支持严格structured_output模式的模型并不算多。该特性目前更广泛且成熟地应用于 OpenAI 的官方原生模型(如GPT-4o等)。在上文的示例代码中,我们对接的是第三方 DeepSeek 服务,受限于 API 的原生支持情况,所以这里我们暂时将
structured_output设置成了False。
AutoGen 的 Agent (智能体)
1 | |
AssistantAgent
AssistantAgent 是 AutoGen 中最基础、最常用,也是最成熟的对话智能体类。它是任务的主要解决者,其核心是封装了一个大型语言模型(LLM)。
如果把 AutoGen 比作一个“虚拟外包项目组”,那么 AssistantAgent 就是这个项目组的核心脑力员工。它本身“没有手”(不具备直接在本地电脑上执行代码或操作的能力),但它拥有最强的思考与规划能力。你通过 system_message 给它分配什么角色(如:前端专家、文案策划、数据分析师),它就能胜任对应的工作。
除此之外,它还内置了以下强大的核心能力:
- 记忆功能(维护上下文):大模型本身是无状态、没有记忆的,需要每次对话时都把之前的对话历史统一发送给大模型。
AssistantAgent内部自动实现了历史消息的管理与拼接功能。 - 代码提取与格式化(Code Block Parsing):大模型在回答时,往往会将代码、代码注释、说明文字甚至一些废话混合在一起返回。
AssistantAgent内置了提取能力,它会自动扫描大模型回答的 Markdown 内容中的独立代码块,方便后续交给其他 Agent(如UserProxyAgent)去执行。 - 工具调用(Function Calling / Tool Use):可以给大模型注册一些 Python 函数(例如:
get_weather(city: str)、search_web(query: str))。当你问到某些模型不知道的实时/专有信息,且工具恰好可以获取时,模型会按照约定格式输出调用指令。底层机制捕获该指令后会执行对应函数,再把调用结果返回给大模型。 - 自动化的重试与纠错(Auto-Reply & Fallback):面对复杂的任务,代码往往一次性跑不通。这时别的 Agent(通常是
UserProxyAgent充当的执行者和测试者)在运行时如果抛出异常,会把异常信息反馈给它(例如:“你刚才写的代码抛出了 IndentationError,在第4行。”)。AssistantAgent底层自带auto-reply(自动回复)机制,不需要开发者写额外的while循环。它收到报错后会自动将报错信息塞进上下文,再次请求大模型:“根据报错,给我一个新的修复方案。” 只有当问题解决,或者达到最大重试次数(max_consecutive_auto_reply)时,交互才会停止。 - 终止条件判断(Termination Detection):AI 群聊很容易陷入无限死循环(例如两个 AI 互相发送“谢谢你”、“不客气”)。因此,
AssistantAgent经常被配置一种“终止词”识别能力。比如,你可以约定当任务彻底完成时,在回复末尾加上TERMINATE。框架一旦检测到该触发词,就会自动结束这轮多智能体对话任务。
UserProxyAgent
UserProxyAgent 可以理解为人类用户在 AutoGen 中的代理或化身,它通常与 AssistantAgent 构成最经典的协作模式。
UserProxyAgent 的三大核心使命:
- 信息输入层 (Human-in-the-loop):拦截对话,请求人类干预或提供决策。
- 物理执行层 (Execution):作为 AI 的“手”,负责在真实机器上跑代码或调 API(注意:这点在最新的 AutoGen 0.4 架构演进中发生了改变,引入了专门的
CodeExecutorAgent)。 - 流程控制层 (Orchestration):作为项目的绝对主导者,随时可以通过人类指令拉停失控的对话(例如发送
TERMINATE)。
信息输入层
在 AutoGen 0.2 版本使用时会有一个核心参数 human_input_mode,它的可能值及作用如下:
"ALWAYS":智能体每次收到消息都会提示人工输入。"TERMINATE":智能体仅在收到终止消息时,或自动回复次数达到max_consecutive_auto_reply时提示人工输入。"NEVER":智能体永远不会提示人工输入,完全自主运行。
但在 0.4 新版本中,废弃了 human_input_mode 这一参数,改用更加灵活的方式:
- 对于普通对话:
UserProxyAgent默认只要给了它任务,就会自动往下走。如果传入了input_func=...,就相当于在接管人类的输入。可以传入一个函数,函数最终返回一个回复,这样更加灵活。 - 对于高危操作(如:执行代码):放在了
CodeExecutorAgent(见下文)。通过approval_func=...这就相当于一种精准管控的 ALWAYS —— 我们允许它自由思考、自由对话,但只有在它打算执行代码时,才需要人类点头 y/n。
物理执行层
在过去的老版本中,没有 CodeExecutorAgent,只有 UserProxyAgent。这时候的 UserProxyAgent 是一个全能王,他既负责拦截对话让用户输入,又负责对大模型输出的结果代码进行测试。这样会有两个缺陷:
- 极度不安全:
UserProxyAgent代表了用户,那么他就有极大的权限,就可以在没有用户授权的情况下运行测试代码。 - 代码极难维护:概念混淆,配置参数极其臃肿。
在新的版本中引进了 CodeExecutorAgent。它是无情的代码执行机器,只用来执行测试代码,从而区分 UserProxyAgent 的工作,让流程更加清晰。
终止条件判断 (Termination)
流程控制层:作为项目的绝对主导者,随时可以拉停失控的对话(发送 TERMINATE)。
在整个流程中,如果说了 TERMINATE 那么 Agent 就会认为这次对话已经完成。初级玩法是给模型一段提示词,如:
1 | |
但这样也不能完全保证大模型的回答没有多余信息。更好的做法是不让大模型输出 TERMINATE,而是调用工具 (Function Calling) 或再引入一个 ReviewerAgent 来解决是否结束的问题:
- 接口约束(Protocol Constraint):收回文本终止权,强制大模型通过 Function Calling 提交确定性的结果来结束流程。
- 职责分离(Separation of Duties):打破“既当运动员又当裁判”的单体模型局限,引入 ReviewerAgent 进行交叉验证,通过多智能体间的博弈和审批来确保输出质量。
RoundRobin 固定轮询与 Selector 动态路由
AutoGen 的核心概念就是群聊。但在群聊中如果超过了两个 Agent,那“下一个谁发言”就成了一个必须解决的路由问题。
确定性(强流程控制):
RoundRobinGroupChat
基于数组索引的绝对轮询。例如[A, B, C]的顺序永远是 A → B → C → A。- 特点:不需要额外的
model_client,它本身不消耗 Token 做路由决策。 - 适用场景:
- 双角色的“乒乓球式”对话(如:师生问答、辩论)。
- 严格的工序流水线(如:数据分析师 → 解决方案专家)。
- 技术局限:缺乏灵活性。如果 B 发现了错误想退回给 A,在纯轮询中很难做到,它必须按顺序传给 C(这样反而会由于无效对话额外消耗 Token)。
- 特点:不需要额外的
自适应(大模型动态决策):
SelectorGroupChat
背后藏着一个隐藏的“LLM 主持人”。每一轮发言后,主持人会读取当前聊天记录,并分析所有参与者的职责,决定把麦克风交给谁。- 特点:不仅发声的 Agent 会消耗 Token,维持群聊路由本身的判定(
model_client)也会消耗 Token。 - 低 Temperature 的必要性:路由器的
temperature必须设置得极低(通常为 0.1 或 0),以保证它像齿轮一样基于逻辑精准判定,而不是“富有创意地瞎指派”。 - Prompt 编写技巧:在 Selector 模式中,参与者的
system_message不仅是给自己看的,也是给“主持人”看的。因此必须明确写明**“交接指令”**(例如:“如果你是 QA 发现了问题,请明确在回复中指出让 Developer 重新返工”)。
- 特点:不仅发声的 Agent 会消耗 Token,维持群聊路由本身的判定(
核心对比总结
| 比较维度 | RoundRobin (固定轮询) |
Selector (动态路由) |
|---|---|---|
| 控制力 | 绝对控制 | 交由大模型控制 |
| 成本 (Token) | 低 | 高,每轮多一次路由推理 |
| 适用复杂度 | 线性流 | 非线性网状协同 |
进阶探讨
- Selector 的状态图约束(Transitions)
在Selector动态路由中,默认是全互连网络(所有人都能传话给所有人)。但在真实的商业业务中,我们往往需要限制路由走向。
AutoGen 允许通过配置“状态转换边”或提示词来赋予群聊“状态机”式的严格跳转规则。比如限制Tester只能把任务传给Developer或PM,绝对禁止流转给其他无关 Agent,从而缩减路由 LLM 的选择空间,降低大模型幻觉概率。
1 | |
description属性的妙用
在SelectorGroupChat中,“主持人(路由 LLM)”其实优先看的是每个 Agent 的description来决定下一个是谁。如果不传description,框架会自动把超长的system_message喂给路由器。这不仅会无端浪费 Token,还会因为文字过多导致路由器“抓错重点,乱指派任务”。
1 | |
- 定制化路由(Custom Selector)
AutoGen 支持传入自定义的 Python 闭包/函数作为路由裁判。这就允许开发者完全跳过大模型,直接用纯 Python 语法(If-Else、正则提取等)结合上下文状态来拍板决定下一个发言者,实现灵活性与低成本兼顾的混合群聊系统。
1 | |
FunctionTool 工具集成
简单实例
1 | |
以上代码展示了工具的基本使用方法,主要包含以下几个核心要点:
工具的定义与包装
简单来说,就是使用FunctionTool将 Python 函数包装为大模型可调用的工具。在这个过程中,参数的类型提示(Type Hints)和文档字符串(Docstring)、description参数非常重要,它们能帮助大模型准确理解工具的功能和调用方式。工具执行的安全性与错误处理
脚本中的calculator没有直接使用危险的eval(),而是使用了ast.parse和受限的操作符字典(safe_eval)来确保数学表达式的安全性。
工具执行失败时不应导致整个程序崩溃,而是应该将异常捕获(try...except)并转为友好的文本(如"计算错误: ...")返回给 AI,让 AI 能够根据报错信息进行“纠错”并重试。多智能体工具链协作
不同职责的 Agent 应该配置不同的工具。例如,“数据分析师”(配备计算和数据分析工具)处理完数据后,交由“存储专家”(配备数据库读写工具)进行落盘保存,各司其职。
高阶用法
1. 异步工具支持
1 | |
在实际应用中,工具通常需要调用外部 API 或读写数据库(涉及网络和 I/O 阻塞)。AutoGen 完全支持使用 async def 定义异步工具,这对于提高多智能体并发对话的性能至关重要。
2. 使用 Pydantic 进行复杂结构化输入/输出验证
1 | |
通过集成 Pydantic 对象作为工具的参数类型,可以天然地限制和校验大模型的输入格式。如果大模型传入的参数不符合 Pydantic 模型定义的 Schema,执行工具前就会自动抛出校验错误,进而触发大模型的纠错重试机制。
3. 人类参与与审批拦截 (Human-in-the-loop / Tool Approval)
提及操作审批与权限拦截,往往会联想到前文提到的 UserProxyAgent 或 CodeExecutorAgent,它们可用于拦截代码执行并询问人类。不过,在较新的 AutoGen 版本中,将权限包装直接下沉到工具调用层(Tool Approval),往往粒度更细、也更直接有效。