构建高效的上下文感知多代理框架用于生产
Source: Google Developers Blog
缩放瓶颈
大上下文窗口有助于解决与上下文相关的问题,但并不能解决所有上下文相关的问题。实际上,朴素的模式——把所有内容都追加到一个巨大的提示中——在三重压力下会崩溃:
- 成本和延迟螺旋: 随着上下文大小的增加,模型成本和首次 token 的响应时间会快速增长。把原始历史记录和冗长的工具负载“推入”窗口会使代理的速度和费用变得不可接受。
- 信号衰减(“中间丢失”): 被无关日志、过时的工具输出或已废弃的状态淹没的上下文窗口会分散模型注意力,使其固定在过去的模式上,而不是当前指令。为了确保稳健的决策,需要最大化相关信息的密度。
- 物理限制: 真实工作负载——包括完整的 RAG 结果、中间产物和长对话痕迹——最终会溢出即使是最大的固定窗口。
单纯增加 token 数量只能争取时间,却不会改变曲线的形状。要实现规模化,我们需要改变上下文的表示和管理方式,而不仅仅是把更多内容塞进一次调用。
设计论点:上下文作为编译视图
在前一代代理框架中,上下文被视为可变的字符串缓冲区。ADK 基于不同的论点构建:
上下文是对更丰富的有状态系统的编译视图。
在该视图中:
- 会话、记忆和制品(文件) 是 源——交互及其数据的完整结构化状态。
- 流和处理器 是 编译管线——一系列将该状态转换的过程。
- 工作上下文 是 编译视图,即在本次调用中发送给 LLM 的内容。
一旦接受了这种思维模型,上下文工程就不再是提示体操,而是系统工程。你必须提出标准的系统问题:中间表示是什么?我们在哪里进行压缩?如何让转换可观察?
ADK 的架构通过三条设计原则回答这些问题:
- 存储与呈现分离: 我们区分持久状态(会话)和每次调用的视图(工作上下文),从而可以独立演进存储模式和提示格式。
- 显式转换: 上下文通过命名且有序的处理器构建,而不是临时的字符串拼接。这使得“编译”步骤可观察、可测试。
- 默认范围限制: 每次模型调用和子代理只看到所需的最小上下文。代理必须通过工具显式获取更多信息,而不是默认被大量信息淹没。
ADK 的分层结构、相关性机制以及多代理交接语义本质上是对这一“编译器”论点及三大原则的应用:
- 结构 – 一个分层模型,将信息的存储方式与模型可见的内容分离。
- 相关性 – 代理和人工控制决定当前重要的内容。
- 多代理上下文 – 明确的语义用于在代理之间交接正确的上下文片段。
接下来的章节将依次阐述这些支柱。
1. 结构:分层模型
大多数早期代理系统隐式假设单一上下文窗口。ADK 则走相反的路线。它将存储与呈现分离,并将上下文组织为不同层级,每层承担特定职责:
- 工作上下文 – 本次模型调用的即时提示:系统指令、代理身份、选定的历史、工具输出、可选的记忆结果以及对制品的引用。
- 会话 – 交互的持久日志:每条用户消息、代理回复、工具调用、工具结果、控制信号和错误,都以结构化的
Event对象记录。 - 记忆 – 超越单个会话的可搜索知识:用户偏好、过去对话等。
- 制品 – 与会话或用户关联的大型二进制或文本数据(文件、日志、图像),通过名称和版本寻址,而不是粘贴进提示。
1.1 工作上下文作为重新计算的视图
对于每一次调用,ADK 会从底层状态重新构建工作上下文。它从指令和身份开始,拉取选定的会话事件,并可选地附加记忆结果。该视图是短暂的(调用结束后即被丢弃)、可配置的(可以在不迁移存储的情况下更改格式)且模型无关的。
这种灵活性正是编译视图的首个优势:你不再硬编码“提示”,而是把它视为一种可迭代的派生表示。
1.2 流和处理器:上下文处理管线
一旦实现了存储与呈现的分离,就需要机制将二者“编译”在一起。在 ADK 中,每个基于 LLM 的代理都由一个LLM Flow 支持,该 Flow 维护有序的处理器列表。
一个(简化的)SingleFlow 可能如下所示:
# Example of a simplified SingleFlow definition
# (actual implementation omitted for brevity)
这些 Flow 是 ADK 用来编译上下文的机器。顺序很重要:每个处理器都在前一步的输出上进行构建。这为自定义过滤、压缩策略、缓存以及多代理路由提供了自然的插入点。你不再重写庞大的“提示模板”,只需添加或重新排序处理器即可。
1.3 会话与事件:结构化、语言无关的历史
ADK 会话代表一次对话或工作流实例的最终状态。具体而言,它充当会话元数据(ID、应用名称)的容器、结构化变量的暂存区,以及最重要的——按时间顺序排列的事件列表。
ADK 并不存储原始提示字符串,而是将每一次交互——用户消息、代理回复、工具调用、结果、控制信号和错误——捕获为强类型的 Event 记录。这种结构化选择带来三大优势:
- 模型无关性: 可以在不重写历史的情况下切换底层模型,因为存储格式与提示格式解耦。
- 丰富的操作: 下游组件(如压缩、时光调试、记忆摄取)可以直接在丰富的事件流上工作,而无需解析不透明的文本。
- 可观察性: 为分析提供了自然的切入点,能够检查精确的状态转变和动作。
会话与工作上下文之间的桥梁是 contents 处理器。它通过执行以下三个关键步骤,将会话转换为工作上下文的历史部分:
- 选择: 过滤事件流,剔除不相关的条目……(后续内容省略)