Agent来了0x09:AutoGen 基础

0 阅读10分钟

前言

前面了解了 什么是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 之上。也是我们这里要讲的重点。

image.png

AgentChat

AgentChat 封装了智能体创建、消息路由、对话管理、团队协作等复杂逻辑,让开发者无需从零开始处理底层的异步事件、消息订阅等,能像搭积木一样快速组建AI团队。

  • AssistantAgent:思考者/规划者。负责生成方案、编写代码、回答问题。但不直接执行任何具体事项。

  • UserProxyAgent:执行者与人机接口。负责执行 AssistantAgent 生成的代码、调用外部工具,并能在关键节点请求人类输入(Human-in-the-Loop)。

  • GroupChat:会议室。负责管理多个 Agent 之间的对话上下文。

  • GroupChatMAnager:主持人。负责调度发言顺序、控制对话流程,决定下一个由谁发言(可基于轮询、随机或LLM决策)。

以一个最基础的 Agent 模型为🌰,其工作流程为:

image.png

当任务需要多个专家(多个 AssistantAgent)共同参与时,就会启用 GroupChat模式。

image.png

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(),
)

运行结果也可看出他们的不同:

image.png

那么,既然 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中提取、过滤、格式化后得到的一个更简洁、更适合呈现或继续下一轮对话的消息列表。

  • 过滤掉系统提示、工具调用等内部机制消息。

  • 只保留标准的userassistant角色消息。

  • 将工具执行结果等,以自然语言形式整合到对话上下文中。

  • 同样是一个List[Dict]

image.png

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)

可见,答案就像打字机一样是一点一点蹦出来的。

image.png

结合 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 的定义:

  • 同步(或异步)阻塞执行,直到任务完成返回最终结果。他抽象了整个任务执行过程,对外提供一个简单的异步调用接口。

对,他在上层抽象了整个过程,只返回一个结果。

image.png

总结

特性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="气死我了,想起那个事情就火大")))

image.png

源码

github