AutoGen Agent 概念篇
一、Agent 的三种类型(本质区别)
🤖 1. OpenAIChatAgent —— "大脑"
本质:LLM 调用器
用户消息 → OpenAIChatAgent → 调用 API → LLM 返回 → 格式化输出
核心原理:
- 内部封装了
ChatClient(兼容 OpenAI 协议) - 负责把消息转成 API 能懂的格式
- 拿到 LLM 响应后,再转成 AutoGen 的消息格式
关键点:
- 必须配置
systemMessage(人设) - 必须调用
.RegisterMessageConnector()(格式转换) - 本身没有记忆,每次调用都是独立的
适用场景:需要 LLM 能力的任何地方
👤 2. UserProxyAgent —— "传话筒"
本质:人机交互接口
核心原理:
- 不调用 LLM
- 根据
HumanInputMode决定是否等待用户输入 - 把用户输入转成 AutoGen 消息格式
三种模式详解:
| ALWAYS | 每轮必等用户 | 收到消息 → 等待输入 → 转发给用户 → 继续 |
| NEVER | 完全自动 | 收到消息 → 返回默认回复 → 继续 |
| TERMINATE | 条件触发 | 收到消息 → 检查终止条件 → 满足则等待,否则自动 |
关键点:
- 用于人在回路(Human-in-the-loop)场景
- 可以随时介入控制对话走向
- 适合需要人工审核/决策的场景
适用场景:代码审查、内容审核、关键决策
🎯 3. AssistantAgent —— "简化版大脑"
本质:OpenAIChatAgent 的快捷封装
核心原理:
- 内部还是调用 LLM
- 但预设了默认配置(systemMessage、工具注册等)
- 代码更简洁,但灵活性降低
对比:
OpenAIChatAgent:需要手动配置 chatClient、systemMessage
AssistantAgent: 一行代码搞定,但配置项少
适用场景:快速原型、简单任务
二、消息格式(AutoGen 的通用语言)
📨 消息的本质
AutoGen 里所有消息都实现 IMessage 接口,核心字段:
┌─────────────────────────────────────┐ │ IMessage │ ├─────────────────────────────────────┤ │ Role: User / Assistant / System │ │ Content: 消息内容(文本/图片等) │ │ From: 发送者 Agent 名称 │ │ Metadata: 额外数据(时间戳、类型等) │ └─────────────────────────────────────┘
📦 常见消息类型
| 类型 | 用途 | 示例 |
|---|---|---|
TextMessage |
纯文本 | "你好" |
ImageMessage |
图片 | 图片 URL |
ToolCallMessage |
工具调用 | "调用 GetWeather" |
ToolCallResultMessage |
工具返回结果 | "晴天,25 度" |
MultiModalMessage |
多模态 | 文本 + 图片 |
🔄 消息流转原理
用户输入
↓
[UserProxyAgent] → 转成 TextMessage(Role.User)
↓
[GroupChat] → 添加到消息列表
↓
[OpenAIChatAgent] → 读取历史消息 → 调用 LLM
↓
LLM 返回
↓
[OpenAIChatAgent] → 转成 TextMessage(Role.Assistant)
↓
[PrintMessage] → 控制台输出
↓
消息列表追加
关键点:
- 所有 Agent 之间只通过消息通信
- 消息列表是对话的唯一状态
- 每次对话 = 读取历史 → 生成新消息 → 追加
三、中间件(Middleware)—— Agent 的"插件系统"
🧩 中间件是什么?
本质:AOP(面向切面编程)
请求 → 中间件 1 → 中间件 2 → Agent 核心 → 中间件 2 → 中间件 1 → 响应 ↓ ↓ ↓ ↓ ↓ 日志 认证 调用 LLM 后处理 格式化
🔧 三个核心中间件原理
1. .RegisterMessageConnector()
作用:格式转换器
原理:
- AutoGen 内部消息格式 ↔ LLM API 格式
- 没有它,Agent 无法理解消息
必须注册:所有调用 LLM 的 Agent 都必须有
2. .RegisterPrintMessage()
作用:调试输出
原理:
- 拦截消息
- 格式化打印到控制台
- 原样返回消息(不影响流程)
可选:生产环境建议去掉(减少 IO)
3. .RegisterMiddleware()
作用:自定义逻辑
原理:
RegisterMiddleware(async (msgs, agent, reply, ct) => { // ① 前置处理(收到消息后,调用 Agent 前) Console.WriteLine($"收到 {msgs.Count} 条消息"); // ② 调用下一个中间件或 Agent 核心 var response = await reply(agent, msgs, ct); // ③ 后置处理(拿到响应后,返回前) Console.WriteLine($"响应完成"); return response; // 必须返回 })
📊 中间件执行顺序
注册顺序:A → B → C
执行流程:
请求 → A(前置) → B(前置) → C(前置) → Agent → C(后置) → B(后置) → A(后置) → 响应
洋葱模型:先注册的最外层,最后注册的最内层
四、消息历史记录(对话的"记忆")
🧠 为什么需要历史记录?
问题:LLM 是无状态的,每次调用都是新的
解决:手动维护消息列表
轮次 1: 用户:"你好" → Agent:"你好!" 轮次 2: 用户:"我是谁" → Agent:❌ 不知道(没有历史) 解决方案: 轮次 2: 把轮次 1 的消息一起发给 LLM → Agent:"你是 XXX"
📝 实现原理
// ① 创建历史列表 var history = new List<IMessage>(); // ② 每轮对话后追加 history.Add(userMessage); history.Add(agentResponse); // ③ 下一轮对话时传入历史 await agent.GenerateReplyAsync(history, options, ct);
⚠️ 关键注意事项
| 问题 | 原理 | 解决方案 |
|---|---|---|
| 上下文超限 | LLM 有 token 上限(如 128K) | 定期摘要压缩历史 |
| 性能下降 | 消息越多,调用越慢 | 只保留最近 N 条 |
| 无关信息 | 早期消息可能不相关 | 按主题筛选历史 |
五、核心架构图
┌─────────────────────────────────────────────────────────┐ │ AutoGen 架构 │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ │ │ OpenAIChat │ │ UserProxy │ │ Assistant │ │ │ │ Agent │ │ Agent │ │ Agent │ │ │ └──────┬───────┘ └──────┬───────┘ └─────┬─────┘ │ │ │ │ │ │ │ └───────────────────┼───────────────────┘ │ │ │ │ │ ┌──────────▼──────────┐ │ │ │ Middleware Stack │ │ │ │ (消息转换器 + 日志) │ │ │ └──────────┬──────────┘ │ │ │ │ │ ┌──────────▼──────────┐ │ │ │ Message History │ │ │ │ (List<IMessage>) │ │ │ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘
六、总结(一句话理解)
| 概念 | 一句话本质 |
|---|---|
| OpenAIChatAgent | LLM 调用器,负责和模型对话 |
| UserProxyAgent | 人机接口,负责等待用户输入 |
| AssistantAgent | 简化版 OpenAIChatAgent |
| 消息格式 | Agent 之间的通用语言 |
| 中间件 | 消息处理管道,前后置逻辑 |
| 历史记录 | 对话状态的唯一载体 |
原理讲完了!核心就三点:
- Agent 是消息处理器(输入消息 → 处理 → 输出消息)
- 中间件是处理管道(可以插入自定义逻辑)
- 历史是对话状态(没有历史,LLM 就没有记忆)

浙公网安备 33010602011771号