# 主题与订阅 运行时有两种消息传递方式:直接消息传递或广播。直接消息传递是一对一的:发送方必须提供接收方的 agent ID。而广播则是一对多的,发送方无需提供接收方的 agent ID。 许多场景适合使用广播。例如,在事件驱动的工作流中,agents 并不总是知道谁会处理它们的消息,而且工作流可以由相互无依赖的 agents 组成。本节重点介绍广播中的核心概念:主题和订阅。 (topic_and_subscription_topic)= ## 主题 主题定义了广播消息的作用范围。本质上,agent运行时通过其广播API实现了一种发布-订阅模型:当发布消息时,必须指定主题。这是对agent ID的一种间接抽象。 主题由两个组成部分构成:主题类型和主题来源。 ```{note} 主题 = (主题类型, 主题来源) ``` 类似于[agent ID](./agent-identity-and-lifecycle.md#agent-id)(同样包含两个组成部分),主题类型通常由应用程序代码定义,用于标记该主题所针对的消息类型。例如,GitHub agent在发布关于新issue的消息时,可能会使用`"GitHub_Issues"`作为主题类型。 主题来源是主题在特定主题类型下的唯一标识符。它通常由应用程序数据定义。例如,GitHub agent可能会使用`"github.com/{repo_name}/issues/{issue_number}"`作为主题来源,以唯一标识该主题。主题来源允许发布者限制消息范围并创建隔离区。 主题ID可以与字符串相互转换。该字符串的格式为: ```{note} 主题类型/主题来源 ``` 如果类型满足UTF8编码且仅包含字母数字(a-z和0-9)或下划线(_),则被视为有效。有效标识符不能以数字开头,也不能包含任何空格。如果来源满足UTF8编码且仅包含ASCII码32(空格)至126(~)之间的字符(含),则被视为有效。 ## 订阅机制 订阅机制实现了主题与agent ID之间的映射关系。 ![订阅示意图](../assets/subscription.svg) 上图展示了主题与订阅之间的关系。agent运行时会维护订阅信息,并据此将消息分发给对应的agents。 若某主题未被订阅,发布到该主题的消息将不会被递送给任何agent。若某主题存在多个订阅,消息将遵循所有订阅规则仅向每个接收agent投递一次。应用程序可通过agent运行时的API动态添加或移除订阅。 ## 基于类型的订阅 基于类型的订阅实现了主题类型与agent类型之间的映射(参见[agent标识](./agent-identity-and-lifecycle.md#agent-id))。这种机制声明了从主题到agent ID的无限映射关系,无需预先知晓具体的主题来源和agent密钥。其原理简明:任何匹配订阅规则中主题类型的主题,都会被映射为具有对应agent类型的agent ID,且agent密钥会被赋值为主题来源的值。Python API中请使用{py:class}`~autogen_core.components.TypeSubscription`。 ```{note} 基于类型的订阅 = 主题类型 --> Agent类型 ``` 通常而言,基于类型的订阅是声明订阅关系的首选方式。它具有可移植性和数据独立性:开发者无需编写依赖特定agent ID的应用程序代码。 ### 适用场景 当具体主题或agent ID与数据相关时,基于类型的订阅可应用于多种场景。这些场景可通过两个维度划分:(1) 单租户或多租户架构;(2) 每个租户对应单个主题或多个主题。此处租户通常指代处理特定用户会话或特定请求的一组agents。 #### 单租户单主题场景 在此场景中,整个应用只有一个租户和一个主题。这是最简单的场景,适用于许多用例,例如命令行工具或单用户应用程序。 要为这种场景实现基于类型的订阅,需要为每种agent类型创建一个基于类型的订阅,并为所有订阅使用相同的主题类型。发布消息时,始终使用相同的主题(即相同的主题类型和主题来源)。 例如,假设有三种agent类型:`"triage_agent"`、`"coder_agent"`和`"reviewer_agent"`,主题类型为`"default"`,则创建以下基于类型的订阅: ```python # 单租户单主题场景下的基于类型订阅 TypeSubscription(topic_type="default", agent_type="triage_agent") TypeSubscription(topic_type="default", agent_type="coder_agent") TypeSubscription(topic_type="default", agent_type="reviewer_agent") ``` 使用上述基于类型的订阅时,所有消息都采用相同的主题来源`"default"`。因此主题始终是`("default", "default")`。发布到此主题的消息将传递给所有上述类型的agents。具体来说,消息将发送到以下agent ID: ```python # 基于主题来源生成的agent ID AgentID("triage_agent", "default") AgentID("coder_agent", "default") AgentID("reviewer_agent", "default") ``` 下图展示了此示例中基于类型订阅的工作机制。 ![基于类型订阅的单租户单主题场景示例](type-subscription-single-tenant-single-topic.svg) 如果对应ID的agent不存在,运行时将自动创建它。 #### 单租户多主题场景 在此场景中,只有一个租户,但您需要控制不同agent处理不同主题。当您希望创建信息隔离区,并让不同agent专门处理不同主题时,这种配置非常有用。 要实现基于类型的订阅,请为每种agent类型创建不同的订阅配置,但使用不同的主题类型。如果希望多个agent类型共享同一主题,可以将相同主题类型映射到多个agent类型。发布消息时,所有消息仍使用相同的主题源值。 沿用前文示例中的agent类型,创建以下基于类型的订阅配置: ```python # 单租户多主题场景的基于类型订阅配置 TypeSubscription(topic_type="triage", agent_type="triage_agent") TypeSubscription(topic_type="coding", agent_type="coder_agent") TypeSubscription(topic_type="coding", agent_type="reviewer_agent") ``` 通过上述订阅配置,发布到主题`("triage", "default")`的所有消息将被递送给类型为`"triage_agent"`的agent,而发布到主题`("coding", "default")`的消息将同时递送给类型为`"coder_agent"`和`"reviewer_agent"`的agents。 下图展示了此示例中基于类型订阅的工作机制: ![基于类型的订阅-单租户多主题场景示例](../assets/type-subscription-single-tenant-multiple-topics.svg) #### 多租户场景 在单租户场景中,主题来源始终相同(例如 `"default"`),它被硬编码在应用程序代码中。而在多租户场景中,主题来源则变为数据依赖型。 ```{note} 判断是否处于多租户场景的一个明显标志是:你需要创建同一agent类型的多个实例。例如,你可能需要不同的agent实例来处理不同用户会话以保持数据隔离,或者你可能希望将繁重的工作负载分配给同一agent类型的多个实例并行处理。 ``` 延续上述示例,如果你希望使用专用agent实例来处理特定的GitHub问题,就需要将主题来源设置为该问题的唯一标识符。 例如,假设为agent类型 `"triage_agent"` 设置了以下基于类型的订阅: ```python TypeSubscription(topic_type="github_issues", agent_type="triage_agent") ``` 当消息发布到主题 `("github_issues", "github.com/microsoft/autogen/issues/1")` 时,运行时会将消息传递给ID为 `("triage_agent", "github.com/microsoft/autogen/issues/1")` 的agent。同理,发布到主题 `("github_issues", "github.com/microsoft/autogen/issues/9")` 的消息会被传递给ID为 `("triage_agent", "github.com/microsoft/autogen/issues/9")` 的agent。 下图展示了此示例中基于类型订阅的工作原理: ![基于类型的订阅多租户场景示例](../assets/type-subscription-multi-tenant.svg) 请注意agent ID是数据依赖型的,如果对应实例不存在,运行时将会创建新的agent实例。 要支持每个租户的多个主题,可以像单租户多主题场景那样使用不同的主题类型。