多智能体系统正在成为复杂 AI 负载的主流架构。与其用一个单体智能体包揽一切,不如把能力分解为专门化的智能体——每个有自己的领域知识、部署流水线和发布节奏。这种方式跟微服务改造后端开发的路线如出一辙。
但拆分带来了新问题:对话上下文怎么跨智能体边界传递? 用户追问时,接收智能体需要理解之前发生了什么。单智能体系统中上下文存在内存里;多智能体系统中,上下文必须在独立部署、不共享基础设施的服务之间传递。
微软 ISE 团队在一次客户合作中正好面临这个问题。他们需要协调智能体(coordinator)通过 A2A(Agent-to-Agent)协议 向无状态的领域智能体(domain agent)共享对话历史和领域上下文。下文覆盖了他们评估的三种方案以及为什么选了内嵌上下文。
A2A 协议与上下文传递
A2A 协议是智能体间通信的开放标准,定义了智能体如何发现彼此、交换消息和管理任务。对上下文传递来说,三个协议概念最关键:
- contextId:服务端生成的字符串,将对话中的相关任务分组。远程智能体在首次交互时分配并返回,客户端在后续轮次中回传。一个
contextId对应多个 task ID(每轮一个)。可以理解为对话线程标识符。 - Message:智能体间的通信单元,包含
parts数组和可选的metadata。 - Parts:消息的构建块。每个 part 恰好包含三个字段之一:
text:对话内容和查询文本file:文件引用或二进制内容的FilePart对象data:结构化、机器可读上下文的DataPart对象
每个 part 可以包含自己的 metadata 对象用于类型识别和版本管理。part 级别的 metadata.type 让接收智能体能判断如何反序列化每个 part。
问题:无状态世界里的上下文
团队的架构遵循微服务最佳实践。领域智能体是独立部署的服务,每个由不同团队拥有、有自己的发布节奏。协调智能体编排这些领域智能体,把用户请求路由到对应的领域智能体。
这个设计提供了部署独立性和清晰的所有权边界,但也产生了上下文传递问题,有几个硬约束:
- 上下文连续性:对话跨越多轮多个智能体,每个智能体需要足够的历史才能产出连贯的响应
- 版本独立性:协调和领域智能体在不同时间表上部署,上下文格式不能在一方更新时断裂
- 不共享基础设施:领域智能体是真正的微服务,要求它们访问协调器的数据库会违反服务边界
- 安全隔离:不是每个智能体都应该看到完整的对话历史,协调器应该控制每个领域智能体收到什么
三种上下文共享方案
A2A 协议支持三种上下文传递模式,区别在于领域智能体从哪里获取上下文以及谁拥有状态。
方案一:共享存储 + contextId 引用
协调器发送一个只包含 contextId 的轻量消息,领域智能体用这个 ID 从共享的 Context Store(双方都能访问的数据库或缓存)中读取对话历史。
优点:载荷最小,所有智能体从同一个存储读取因而有单一可信来源,对话长度不受消息大小限制。但根本问题是违反了不共享基础设施原则——要求领域智能体访问协调器(或共享平台层)拥有的存储,在自治服务之间创建了运行时依赖。每个领域智能体需要共享存储的连接和凭证,增加了运维复杂度和跨组织边界的安全面。
方案二:内嵌上下文于消息载荷
协调器从 Context Store 取回上下文,可选地做摘要压缩,直接嵌入消息载荷。领域智能体带内收到一切需要的信息——不需要任何存储访问。
优点:彻底消除存储依赖,领域智能体完全无状态,协调器精确控制每个智能体看到什么上下文,测试也更简单(mock 消息载荷即可)。代价是载荷更大,因为完整摘要上下文随每次请求走。长对话需要摘要压缩来保持载荷实用。
方案三:每智能体上下文存储(有状态智能体)
每个领域智能体维护自己的上下文存储,用 contextId 跨交互持久化和检索对话状态。A2A 协议的 Life of a Task 文档明确允许智能体”使用 contextId 维护内部状态”。
这使每个智能体自包含——无共享存储、无协调器管理的载荷。但引入了显著复杂度:每个远程智能体变成有状态服务,需要自己的存储基础设施、部署时的状态迁移和防状态分叉的同步逻辑。团队早期就排除了这个方案——跨独立部署服务维护每智能体状态存储的运维开销超过了智能体级自治的收益。
深入:内嵌上下文模式
团队选择了内嵌上下文模式,因为它跟微服务原则对齐。领域智能体应该可独立部署、可独立测试、不受基础设施依赖约束。从共享存储拉上下文会违反这些原则。
保持领域智能体无状态还有四个具体好处:
- 单一可信来源:对话状态只存在协调器中,避免状态分叉。
- 上下文精度:协调器可以在发送前精炼和重写上下文,精准提供每个智能体需要的信息,省略无关细节。
- 简化维护与调试:无状态智能体在相同输入下行为是确定性的,调试只需检查输入消息而非重建内部状态历史。
- 演进灵活性:从无状态起步保留了未来选项。以后可以给无状态领域智能体加自己的状态存储并忽略协调器提供的上下文,而反过来从有状态迁移到无状态需要重架构。
A2A 消息结构实操
因为 contextId 是服务端生成的,对话第一条消息省略它——远程智能体分配一个并返回。后续轮次中协调器回传已分配的 contextId。以下是一个追问轮次的消息示例,包含之前分配的 contextId 以及内嵌的对话历史和领域上下文:
{
"message": {
"messageId": "msg-001",
"contextId": "ctx-123",
"metadata": { "userId": "2" },
"parts": [
{
"text": "tell me more about option 4"
},
{
"data": [
{ "role": "user", "content": "I am looking for a lightweight laptop" },
{ "role": "assistant", "content": "Great choice. Do you have any preferred brands?" },
{ "role": "user", "content": "show me models under 1,000 dollars" },
{ "role": "assistant", "content": "Here are some options that match." }
],
"metadata": {
"type": "ConversationHistory",
"schemaVersion": "1.0"
}
},
{
"data": {
"userProfile": { "location": "Sample City", "preferredLanguage": "en-US" },
"foundItems": { "query": "lightweight laptops", "items": [...], "totalCount": 2 },
"sessionContext": { "sourceId": "WebChat" }
},
"metadata": {
"type": "UserContext",
"schemaVersion": "1.0"
}
}
],
"role": "user"
}
}
消息包含三个 part,各司其职:
- text part:当前用户查询
- ConversationHistory part:
data数组包含摘要后的对话历史,metadata.type标识类型,schemaVersion支持格式独立演进 - UserContext part:领域特定上下文的
data对象,包含用户位置和之前的搜索结果
Part 级元数据与类型判别
part 级别的 metadata 是这个模式可扩展的关键。领域智能体接收消息后检查每个 part 的 metadata.type 来决定如何处理。这使工厂模式的强类型反序列化成为可能——智能体可按类型将每个 part 路由到对应处理器。
因为每个 part 类型携带自己的 schemaVersion,团队可以独立演进上下文格式。协调器可以开始发送 ConversationHistory v2.0,领域智能体仍期望 v1.0——版本控制显式且可管理。遵循语义化版本约定进一步加强了兼容性:接收智能体检查版本来判断不熟悉的 schema 能否安全处理。
用摘要控制载荷大小
把完整对话历史嵌入每条消息对长对话来说很快会不现实。团队采用了10 轮摘要策略:每 10 轮之后,协调器在嵌入前摘要对话历史。这保持了历史载荷有界,同时保留了领域智能体产出连贯响应所需的上下文。
摘要并非没有风险——每步摘要可能丢失重要细节或引入不准确,影响下游响应质量。但转发无界对话历史也有自己的质量风险:上下文增长时模型可能在相关性和连贯性上挣扎。摘要是刻意的权衡,在上下文保真度和载荷大小、性能、安全之间平衡。对该项目来说,10 轮阈值达到了这个平衡,团队可以基于观察到的响应质量调优。
这还提供了天然的安全边界:协调器精确控制每个领域智能体收到什么上下文。对话某部分的敏感信息可以在发给特定智能体时排除,共享存储很难做到这一点。
决策矩阵
没有哪个方案在所有场景下都更好,选择取决于部署拓扑、安全需求和运维约束。
核心架构决策是领域智能体是有状态还是无状态。在有状态方式内又有两个变体:共享存储(所有智能体从一个存储读取)和每智能体存储(各自维护)。以下矩阵对比三种方案:
| 维度 | 共享存储 | 内嵌上下文 | 每智能体状态 |
|---|---|---|---|
| 存储依赖 | 领域智能体需要共享访问 | 领域智能体无状态 | 每个智能体需要自己的存储 |
| 载荷大小 | 最小 | 摘要后可管理 | 最小 |
| 上下文保真度 | 完整历史可用 | 摘要可能丢失细节 | 每个智能体完整历史 |
| 网络延迟 | 额外的存储调用 | 无额外调用 | 额外的每智能体存储调用 |
| 安全隔离 | 跨智能体数据暴露风险 | 协调器控制上下文 | 智能体级隔离 |
| 测试复杂度 | 需要 mock 存储 | 简单 mock 载荷 | 需要每智能体状态设置 |
| 版本独立性 | 存储格式耦合 | Part 级 schema 版本化 | 每智能体格式管理 |
| 状态一致性 | 单一可信来源 | 协调器拥有状态 | 状态分叉风险 |
| 运维开销 | 共享基础设施 | 协调器管理上下文 | 每智能体基础设施 |
选共享存储:当领域智能体是已有存储层访问权限的可信内部服务,对话特别长、摘要不现实,或者需要中心化查询对话历史时。
选内嵌上下文:当领域智能体是独立部署的微服务,跨组织边界使共享存储不现实,安全隔离需要精细控制每个智能体看到什么,或者想让领域智能体保持完全无状态和可移植时。也是风险更低的起点——以后加状态是增量改动,去掉状态需要重架构。
选每智能体状态:当领域智能体需要完全的对话记忆自治,智能体长期运行、维护深度每用户上下文,或者每个智能体的状态真正独立不需要跨智能体一致性时。
对 ISE 团队来说,内嵌上下文是明确的匹配。领域智能体是真正的微服务有独立的部署周期,10 轮摘要策略把载荷保持在实用范围内。
总结
多智能体系统中跨智能体传递上下文是一个会波及整个架构的设计决策。内嵌上下文模式——把摘要后的历史直接嵌入 A2A 消息载荷——让领域智能体保持可独立部署、可测试、安全的无状态特性。
代价是更大的载荷,但有界摘要(10 轮方案)让这保持实用。协调器控制上下文共享的安全收益在智能体跨越团队或组织边界时尤其显著。
内嵌上下文也是更低风险的起步点:以后采用状态是增量的,而去掉状态需要重架构协调器。如果你在基于 A2A 协议构建多智能体系统,建议按以上决策矩阵评估三种方案,让架构约束引导你的选择。