跳转到内容

事件驱动的 Agent 运行时

事件驱动的 Agent 运行时

学习目标

本章要解决什么问题:AutoGen Core API 如何实现 Agent 之间的解耦通信。你将学到:

  • SingleThreadedAgentRuntime 的消息队列和路由机制
  • 三种订阅策略的使用场景
  • send_message(RPC)与 publish_message(pub/sub)的选择依据
  • RoutedAgent 装饰器模型的签名

前置知识

本章涉及事件驱动 Agent 运行的通用原理,建议先阅读:

下文假设你已理解 pub/sub 架构和订阅系统的基本概念,直接聚焦 AutoGen 的具体实现。


项目实践

SingleThreadedAgentRuntime 的消息循环

AutoGen Core 的运行时基于 asyncio 实现单线程事件循环。核心数据结构是三个消息信封:

PublishMessageEnvelope — 用于 publish_message(一对多广播)
SendMessageEnvelope — 用于 send_message(点对点 RPC)
ResponseMessageEnvelope — 用于返回 RPC 响应

消息循环的工作方式:

关键实现细节:

  • 每个 Agent 有独立的消息队列
  • 消息通过 Queue 异步传递,而非直接调用
  • 序列化注册表(SerializationRegistry)在发送端序列化、接收端反序列化

订阅策略实战

AutoGen 提供三种订阅策略,通过装饰器或注册方法绑定到 Agent:

# 方式一:装饰器注册(推荐)
@default_subscription
class MyAgent(RoutedAgent):
...
@type_subscription("chat")
class ChatAgent(RoutedAgent):
...
# 方式二:手动注册订阅
await runtime.register_factory("my-agent", MyAgent)
await runtime.add_subscription(TypeSubscription("chat", "my-agent"))
策略匹配规则适用场景
@default_subscription匹配默认 Topic简单的点对点通信
@type_subscription("type")精确匹配消息类型专用消息处理器
TypePrefixSubscription匹配类型或 Topic 前缀一组相关消息

RoutedAgent 装饰器

RoutedAgent 是 AutoGen Core 中最常用的 Agent 基类,使用三个装饰器区分消息处理类型:

class MyAgent(RoutedAgent):
@message_handler
async def handle_any(self, message: MyMessage, ctx: MessageContext) -> Response:
# 处理事件和 RPC
...
@event
async def handle_event(self, message: MyEvent, ctx: MessageContext) -> None:
# 仅处理 pub/sub 事件,无返回值
...
@rpc
async def handle_rpc(self, message: MyRequest, ctx: MessageContext) -> Response:
# 仅处理 RPC,必须返回值
...

装饰器的核心作用:将方法签名(消息类型 → 返回类型)注册到运行时的路由表中,使得运行时可以根据消息类型自动选择处理方法。

RPC vs Pub/Sub 的选择

# RPC:需要返回值
response = await runtime.send_message(
message=MyRequest(data="..."),
recipient=AgentId("my-agent", "default"),
)
# Pub/Sub:广播通知,无返回值
await runtime.publish_message(
message=MyEvent(status="done"),
topic_id=DefaultTopicId(),
)

选择依据

  • 需要获取 Agent 处理结果 → send_message(RPC)
  • 通知多个 Agent 某事发生 → publish_message(pub/sub)
  • 不关心谁处理了消息 → publish_message

问题与规避

单线程运行时的阻塞陷阱

AutoGen Core 默认使用 SingleThreadedAgentRuntime,所有 Agent 共享同一个 asyncio 事件循环。

陷阱:如果在消息处理方法中执行同步阻塞操作(如 time.sleep()、同步 HTTP 请求),会阻塞整个运行时,其他 Agent 无法处理消息。

规避

  • 所有 I/O 操作必须使用 async 版本
  • 必须调用同步代码时使用 asyncio.to_thread()
  • 对于高吞吐场景,考虑使用 autogen-ext 中的 GRPC 分布式运行时

消息序列化的性能开销

每个消息在发送端序列化、接收端反序列化。对于高频消息(如每秒数千条),序列化开销可能显著。

规避

  • 使用 Protobuf 而非 JSON 序列化(通过 PROTOBUF_DATA_CONTENT_TYPE
  • 减少消息体大小,只传递必要的引用而非完整数据

设计取舍

为什么用 asyncio 队列而非线程池?

优势

  • 无锁设计:单线程避免了锁竞争的复杂度
  • 内存效率:不需要为每个消息创建新线程
  • 调试友好:调用栈连续,易于追踪

代价

  • 无法利用多核 CPU
  • 阻塞操作会冻结整个运行时

与直接函数调用的对比

维度直接调用AutoGen 事件驱动
代码行数多(需要注册、订阅)
解耦度无(硬编码依赖)高(通过 Topic 解耦)
多播不支持天然支持
调试简单需要追踪消息流

AutoGen 选择事件驱动的动机是支持多 Agent 协作场景中的灵活消息路由,而非追求最小的代码量。

参考来源

  • AutoGen Core API Reference — 运行时 API 文档
  • 源码验证:autogen-core/_single_threaded_agent_runtime.py(消息循环实现)
  • 源码验证:autogen-core/_routed_agent.py(装饰器路由)