前言
前面了解了 什么是MAS。那么到底在日常开发中怎么用呢?其实就像 Agent 开发有 Langchain框架一样,MAS 也有成熟的框架。今天就介绍一个叫做 AutoGen 的 MAS 开发框架。
概念
AutoGen 是一个由微软开源的、基于对话驱动的多智能体(Multi-Agent)协作框架。
- 目标:解决单一 Agengt 在应对复杂、多步骤任务时存在的局限性,如能力边界、上下文窗口瓶颈和缺乏自我纠错
- 本质:组建一个由多个专业化AI智能体构成的 “虚拟团队” ,让它们像人类团队一样通过自然语言对话进行分工、讨论、审查与协作,共同完成任务
虽然 AutoGen 作为一个独立框架的重大更新已在2025年末停止,并并入了 Microsoft Agent Framework (MAF)。但是,设计其理念,对于掌握现代 MAS 的编排思想至关重要。
组件
-
AutoGen Studio:提供用于构建多代理应用程序的无代码 GUI。就像 Xcode 的storyBoard 等组件拖拽构建页面。 在 AI 里类似 Coze、FastGPT 等低代码平台,这里我们暂且略过。
-
AutoGen Bench:提供了用于评估代理性能的基准测试套件。
-
AgentChat:代码层面用于构建多代理应用程序的高级API,基于 AutoGen-Core 之上。也是我们这里要讲的重点。
AgentChat
AgentChat 封装了智能体创建、消息路由、对话管理、团队协作等复杂逻辑,让开发者无需从零开始处理底层的异步事件、消息订阅等,能像搭积木一样快速组建AI团队。
-
AssistantAgent:思考者/规划者。负责生成方案、编写代码、回答问题。但不直接执行任何具体事项。
-
UserProxyAgent:执行者与人机接口。负责执行
AssistantAgent生成的代码、调用外部工具,并能在关键节点请求人类输入(Human-in-the-Loop)。 -
GroupChat:会议室。负责管理多个 Agent 之间的对话上下文。
-
GroupChatMAnager:主持人。负责调度发言顺序、控制对话流程,决定下一个由谁发言(可基于轮询、随机或LLM决策)。
以一个最基础的 Agent 模型为🌰,其工作流程为:
当任务需要多个专家(多个 AssistantAgent)共同参与时,就会启用 GroupChat模式。
helloAutoGen:第一个程序
LLM 实例和 Agent 构建
model_info:固定格式,按实际选择key和value
def get_ag_ali_model_client():
return OpenAIChatCompletionClient(
model=ALI_TONGYI_MAX1_MODEL,
api_key=os.getenv(ALI_TONGYI_API_KEY_OS_VAR_NAME),
base_url=ALI_TONGYI_URL,
model_info={
"vision": True, # 支持多模态(图片+文本)输入
"function_calling": True, # 支持工具/函数调用(核心Agent能力)
"json_output": True, # 支持返回JSON格式数据
"family": "unknown", # 模型所属系列(未分类),由于AutoGen最开始是兼容OpenAI的,这里除了OpenAI到的几个类型就都填"unknown"
"structured_output": True # 支持严格结构化输出
})
# 创建AssistantAgent
agent = AssistantAgent(
name="assistant",
model_client=model_client,
tools=[web_search],
system_message="使用工具来解决任务。"
)
run
run()是一个高级、封装好的高级方法。
-
根据任务字符串,自动构建初始的系统提示和用户消息。
-
可能隐式地创建并管理一个内部会话,自动维护对话历史。
-
在特定配置下(如启用了
code_execution_config),会自动调用代码解释器并整合结果。 -
为了一次性完成“任务”,它可能在内部多次调用类似
on_messages的流程。
普通调用
同步(或异步)阻塞执行,直到任务完成返回最终结果。他抽象了整个任务执行过程,对外提供一个简单的异步调用接口。
response = await agent.run(
task = TextMessage(content="查找关于 AutoGen 的信息", source="user"),
cancellation_token=CancellationToken(),
)
print(response.messages)
流式调用
流式执行,可以实时获取中间结果。返回一个异步迭代器,每次迭代产生一个事件,这些事件代表了任务执行过程中的各个阶段。可见其设计哲学是“透明和可观测”,我们可以实时了解任务进展,并可能根据中间结果做出相应处理。
agent.run_stream(
task = TextMessage(content="查找关于 AutoGen 的信息", source="user"),
cancellation_token=CancellationToken(),
)
运行结果也可看出他们的不同:
那么,既然 run_stream 返回的是一个任务迭代,我们怎么去就某个步骤做一些处理呢?
async def assistant_run_stream_custom() -> None:
# 流式处理stream_result,这时自定义处理不得使用Console
stream_result = agent.run_stream(
task=TextMessage(content="查找关于 AutoGen 的信息", source="user"),
cancellation_token=CancellationToken()
)
full_response = "" # 用于累积完整回复
# 使用 async for 循环从生成器中逐个取出块(chunk)
async for chunk in stream_result:
# chunk的类型可能是多种,我们需要从中提取文本
if hasattr(chunk, 'content') and isinstance(chunk.content, str):
text = chunk.content
full_response += "-->"
full_response += text
# 也可能包含其他信息,如工具调用,这里简单处理
elif isinstance(chunk, dict) and chunk.get("type") == "text":
full_response += "-->"
full_response += chunk.get("text", "")
print(f"\n\n【完整回复已接收】\n{full_response}")
print("===============【on_stream_custom】===============")
asyncio.run(assistant_run_stream_custom())
on_messages
相比 run(),on_messages 是一个更底层、原子性的操作。
- 接收一个消息列表,让Agent基于其自身的系统提示、能力、工具配置以及对话历史(如果支持),处理 这“一批” 输入消息,并生成一个响应。
- 它只负责“处理这一轮”的对话。开发者需要自行管理对话的轮次、历史消息的拼接以及可能的复杂流程(如多个Agent的交替发言)
response = await agent.on_messages(
[TextMessage(content="查找关于 AutoGen 的信息", source="user")],
cancellation_token=CancellationToken(),
)
print(response.inner_messages)
print()
print(response.chat_message)
inner_messages
inner_messages:Agent 处理请求过程中的完整、原始的内部消息日志。
-
系统提示(如果以消息形式注入)。
-
原始的用户输入消息。
-
Agent调用工具(如函数)的请求和结果消息。
-
大模型多次推理产生的中间链式思考消息。
-
最终生成的助理回复消息。
-
是一个
List[Dict]。
chat_message
chat_messages:从inner_messages中提取、过滤、格式化后得到的一个更简洁、更适合呈现或继续下一轮对话的消息列表。
-
过滤掉系统提示、工具调用等内部机制消息。
-
只保留标准的
user、assistant角色消息。 -
将工具执行结果等,以自然语言形式整合到对话上下文中。
-
同样是一个
List[Dict]。
model_client_stream:LLM 流式
model_client_stream:LLM 客户端采用流式,是属于底层的流逝调用
streaming_assistant = AssistantAgent(
name="assistant",
model_client=model_client,
system_message="你是一位得力的助手",
model_client_stream=True, # 流式传输模型客户端生成的文本
)
那么,他和我们上面了解到的 agent.stream 有什么区别呢?
| 维度 | agent.stream() (框架级流式) | model_client_stream (客户端级流式) |
|---|---|---|
| 控制层级 | AutoGen 框架层面的高级API | 底层LLM客户端配置参数 |
| 流式粒度 | 粗粒度:Agent的完整响应(包含思考、工具调用、最终回复) | 细粒度:仅LLM生成的token流 |
| 返回内容 | 包含多种类型的chunk:文本、工具调用、思考过程等 | 仅文本token |
| 使用场景 | 需要实时展示Agent完整执行过程的用户界面 | 需要从底层控制LLM响应的生成过程 |
| 代码复杂度 | 简单,框架已封装复杂逻辑 | 较复杂,需要自己处理底层流 |
结合 on_messages_stream
async for message in streaming_assistant.on_messages_stream(
[TextMessage(content="请说出两个南美洲城市的名字", source="user")],
cancellation_token=CancellationToken(),
):
print("---------- on_messages_stream ----------")
print(message)
可见,答案就像打字机一样是一点一点蹦出来的。
结合 run_stream
run_stream 达到的效果是一样的。
# 使用run_stream()产生同样的效果
async for message in streaming_assistant.run_stream(task="请说出两个南美洲城市的名字"): # type: ignore
print("---------- run_stream ----------")
print(message)
结合 run
以上两种调用都是结合 agent 框架级的流式调用一起,那么如果在 model_client_stream = True下调用 agent.run 会发生什么呢?
response = await streaming_assistant.run(task="请说出两个南美洲城市的名字")
print("---------- run ----------")
print(f"返回类型: {type(response)}")
print(response)
咿,为什么这回的答案是一下蹦出来的?底层大模型我们也设置了流式调用(model_client_stream)啊。我们重新回到 run 的定义:
- 同步(或异步)阻塞执行,直到任务完成返回最终结果。他抽象了整个任务执行过程,对外提供一个简单的异步调用接口。
对,他在上层抽象了整个过程,只返回一个结果。
总结
| 特性 | model_client_stream=True + agent.run() | agent.stream() | 两者都启用 |
|---|---|---|---|
| 核心模式 | 网络层优化模式 | 用户体验优化模式 | 极致控制模式 |
| 工作原理 | 底层HTTP流式传输,但框架缓冲所有数据,一次性返回完整响应 | 框架级流式,实时返回处理chunk(文本、工具调用等) | 同时启用底层和框架流式,提供完整数据流和控制 |
| 返回内容 | 完整响应对象(如AgentResponse),包含.output或.content | 异步生成器,逐个返回chunk(文本块、工具调用消息等) | 异步生成器,包含原始token流+框架处理消息 |
| 网络性能 | 最佳(HTTP/2流式,TCP优化,连接复用) | 良好(框架开销增加5-15ms/请求) | 优秀(底层优化+框架控制) |
| 用户体验 | 无感知(一次性返回) | 最佳(实时显示"打字机"效果) | 极佳(可自定义显示粒度) |
| 控制粒度 | 低(仅控制请求/响应) | 中(控制框架级消息流) | 最高(同时控制底层token流和框架消息) |
| 复杂度 | 低(API简单) | 中(需处理异步生成器) | 高(需处理多级数据流) |
| 内存使用 | 中等(需缓冲完整响应) | 低(逐块处理) | 高(存储原始流数据) |
| 适用场景 | 1. 后台批量处理 2. 高并发API服务 3. 数据批处理任务 4. 监控日志系统 | 1. 实时聊天界面 2. 教育演示系统 3. 内容创作工具 4. 无障碍应用 | 1. AI行为调试 2. 高级监控系统 3. 研究分析工具 4. 定制化流处理 |
| 示例场景 | 批量生成1000条商品描述 | 在线客服机器人实时回复 | AI模型性能分析平台 |
| 代码复杂度 | 简单 | 中等 | 复杂 |
| 调试难度 | 易 | 中 | 难 |
| 首次Token时间 | 正常(但用户无感知) | 最快感知 | 最快感知+详细监控 |
| 错误处理 | 简单(整体重试) | 中等(可部分恢复) | 复杂(可精确定位错误点) |
| 生产推荐 | ✅ 高吞吐后台服务 | ✅ 用户交互应用 | ⚠️ 专业监控调试工具 |
🤔🤔🤔🤔🤔:model_client_stream 为何会让网络层性能更佳?
response_format:响应格式
就像我们做客户端开发,有时候需要把后端数据整合成我们需要的格式。LLM 开发也一样,之前也学过 Python 是通过 Pydantic BaseModel定义数据模型。
response_format:响应格式
# 定义代理的响应格式,使用 Pydantic BaseModel
class AgentResponse(BaseModel):
thoughts: str
response: Literal["高兴", "悲伤", "一般", "愤怒"]
model_client = OpenAIChatCompletionClient(
model=ALI_TONGYI_MAX1_MODEL,
api_key=os.getenv(ALI_TONGYI_API_KEY_OS_VAR_NAME),
base_url=ALI_TONGYI_URL,
response_format=AgentResponse,
model_info={
"vision": True,
"function_calling": True,
"json_output": True,
"family": "unknown",
"structured_output": True
},
)
agent代码
agent = AssistantAgent(
"assistant",
model_client=model_client,
system_message='''按照以下JSON格式将输入分类为高兴、悲伤或一般:
{
"thoughts": "分析用户情绪的原因",
"response": "高兴|悲伤|一般|愤怒"
}'''
)
测试代码
asyncio.run(Console(agent.run_stream(task="气死我了,想起那个事情就火大")))