面向生产的高效上下文感知多代理框架架构

发布: (2025年12月17日 GMT+8 17:15)
21 min read

Source: Google Developers Blog

请提供您想要翻译的具体文本内容,我将为您翻译成简体中文。

Overview

AI 代理开发的格局正在快速变化。 我们已经超越了仅原型化单轮聊天机器人。如今,组织正在部署复杂的、自治的代理,以处理 长期任务:自动化工作流、进行深入研究以及维护复杂的代码库。

这种雄心立刻遭遇了瓶颈:上下文

随着代理运行时间的延长,它们需要跟踪的信息量——聊天历史、工具输出、外部文档、中间推理——呈指数级增长。当前的“解决方案”是依赖基础模型中日益增大的上下文窗口。但仅仅给代理提供更多粘贴文本的空间并不能成为唯一的扩展策略。

要构建 可靠、高效且易于调试 的生产级代理,业界正在探索一门新学科:

Context engineering

将上下文视为具有自身架构、生命周期和约束的一等系统。

基于我们在扩展复杂单代理或多代理系统方面的经验,我们在 Google Agent Development Kit (ADK) 中设计并演进了上下文栈,以支持这一学科。ADK 是一个开源的、多代理原生框架,旨在让 主动上下文工程 在真实系统中变得可行。

Source:

可扩展性瓶颈

更大的上下文窗口可以帮助解决与上下文相关的问题,但并不能解决所有问题。实际上,把所有内容都追加到一个巨大的提示中的朴素模式会在三重压力下崩溃:

  • 成本和延迟螺旋: 随着上下文大小的增加,模型成本和首次 token 的响应时间会快速上升。把原始历史记录和冗长的工具负载“硬塞”进窗口会让代理的运行速度和费用变得不可接受。
  • 信号衰减(“在中间丢失”): 被无关日志、过时的工具输出或已废弃的状态淹没的上下文窗口会分散模型注意力,使其固执于过去的模式而不是当前指令。为了确保稳健的决策,需要最大化相关信息的密度。
  • 物理限制: 真实工作负载——包括完整的 RAG 结果、中间产物以及长对话记录——最终会超出即使是最大的固定窗口的容量。

单纯增加 token 数量只能暂时缓解问题,却无法改变曲线的形状。要实现规模化,我们需要改变 上下文的表示和管理方式,而不仅仅是把更多内容塞进一次调用中。

Source:

设计论点:上下文作为编译视图

在上一代代理框架中,上下文被视为可变的字符串缓冲区。ADK 基于一个不同的论点构建:

上下文是对更丰富的有状态系统的编译视图。

在该视图中:

  • 会话、记忆和制品(文件) —— 交互及其数据的完整、结构化状态。
  • 流程和处理器编译器管线 —— 一系列将该状态转换的步骤。
  • 工作上下文 是你在单次调用中发送给 LLM 的 编译视图

一旦采用了这种思维模型,上下文工程就不再是提示技巧,而是系统工程。你被迫提出标准的系统问题:中间表示是什么?我们在哪里进行压缩?如何让转换可观察?

ADK 的架构通过三条设计原则回答这些问题:

  1. 存储与呈现分离 —— 我们区分持久状态(会话)和每次调用的视图(工作上下文)。这使你能够独立演进存储模式和提示格式。
  2. 显式转换 —— 上下文通过具名、排序的处理器构建,而不是临时的字符串拼接。这让“编译”步骤可观察、可测试。
  3. 默认作用域 —— 每次模型调用和子代理只看到所需的最小上下文。代理必须通过工具显式获取更多信息,而不是默认被信息淹没。

ADK 的分层结构、关联机制以及多代理交接语义本质上是对该“编译器”论点及三条原则的应用:

  • 结构 —— 一个分层模型,将信息的存储方式与模型可见内容分离。
  • 关联 —— 代理和人工控制,决定当前重要的内容。
  • 多代理上下文 —— 明确的语义,用于在代理之间交接正确的上下文切片。

接下来的章节将依次阐述这些支柱。

结构:分层模型

大多数早期代理系统隐式假设 单一上下文窗口。ADK 则相反。它将存储与呈现分离,并将上下文组织为不同层级,每个层级承担特定职责:

层级描述
工作上下文本次模型调用的即时提示:系统指令、代理身份、选定的历史记录、工具输出、可选的记忆结果以及对制品的引用。
会话交互的 持久日志:每条用户消息、代理回复、工具调用、工具结果、控制信号和错误,均以结构化的 Event 对象捕获。
记忆长期 可搜索的知识,超越单个会话的寿命:用户偏好、过去的对话等。
制品与会话或用户关联的大型二进制或文本数据(文件、日志、图像),通过名称和版本寻址,而不是粘贴到提示中。

1.1 工作上下文作为重新计算的视图

对于每次调用,ADK 会从底层状态重新构建工作上下文。它从指令和身份开始,拉取选定的会话事件,并可选地附加记忆结果。该视图具有:

  • 短暂性 —— 调用结束后即被丢弃。
  • 可配置性 —— 你可以在不迁移存储的情况下更改格式。
  • 模型无关 —— 适用于任何 LLM 后端。

这种灵活性是编译视图的首个优势:你不再硬编码“提示”,而是将其视为派生表示。

1.2 流程和处理器:上下文处理管线

一旦将存储与呈现分离,就需要机制将二者编译在一起。在 ADK 中,每个基于 LLM 的代理都由一个 LLM Flow 支持,该 Flow 维护有序的处理器列表。

一个(简化的)SingleFlow 可能如下所示:

# Example of a simplified SingleFlow definition
flow = SingleFlow(
    name="example",
    processors=[
        SystemPromptProcessor(),
        IdentityProcessor(),
        SessionHistoryProcessor(),
        ToolResultProcessor(),
        MemoryLookupProcessor(),
        ArtifactReferenceProcessor(),
    ],
    # ... other configuration ...
)

Source:

leFlow(
    processors=[
        "static_instruction",   # immutable system prompt
        "contents",            # builds the working context from the Session
        "retrieval",           # optional external knowledge fetch
        "tool_calls",          # invoke tools if needed
        "response",            # generate the final LLM reply
    ]
)

这些 flow 是 ADK 用来编译上下文的机制。顺序很重要:每个处理器都基于前一步的输出构建。这为自定义过滤、压缩策略、缓存和多代理路由提供了自然的插入点。你不再需要重写庞大的 prompt 模板;只需添加或重新排序处理器即可。

1.3 Session 与事件:结构化、语言无关的历史

ADK 的 Session 表示一次对话或工作流实例的确定状态。具体来说,它充当以下内容的容器:

  • 会话元数据(ID、应用名称)
  • 用于结构化变量的状态草稿本
  • Events – 按时间顺序排列的强类型记录列表

ADK 并不是存储原始的 prompt 字符串,而是将每一次交互——用户消息、代理回复、工具调用、结果、控制信号以及错误——捕获为 Event 记录。这种结构化选择带来三大优势:

优势为什么重要
模型无关性可以在不重写历史的情况下切换底层模型;存储格式与 prompt 格式解耦。
丰富的操作下游组件(压缩、时光调试、记忆摄取)可以直接在丰富的事件流上工作,而无需解析不透明的文本。
可观测性为分析提供自然的切入点,让你检查精确的状态转变和动作。

会话与工作上下文之间的桥梁是 contents 处理器。它负责将 Session 转换为工作上下文的历史部分,执行以下三个关键步骤:

  1. 选择 – 过滤事件流,剔除不相关的事件、部分事件以及框架噪声,防止它们进入模型。
  2. 转换 – 将剩余事件展平为 Content 对象,使用正确的角色(user/assistant/tool)并为所使用的模型 API 添加注解。
  3. 注入 – 将格式化后的历史写入 llm_request.contents,确保下游处理器以及模型本身收到干净、连贯的对话轨迹。

在此架构中,Session 是你的 真实来源;工作上下文仅是一个 可计算的投影,你可以随时间对其进行细化和优化。

1.4 会话层面的上下文压缩与过滤

如果无限制地追加原始事件,延迟和 token 使用量必然会失控。ADK 的 Context Compaction 功能在 Session 层面解决此问题。

当达到可配置阈值(例如调用次数)时,ADK 会触发一个异步过程,:

  1. 使用 LLM 对滑动窗口内的旧事件进行 摘要(窗口大小和重叠大小可配置)。
  2. 将生成的摘要作为带有 "compaction" 动作的新事件写回 Session。
  3. 允许系统 剪枝或降级 已被摘要化的原始事件。

因为压缩直接作用于 Event 流本身,收益会向下游传递:

  • 可扩展性 – 即使是极长的对话,Session 仍保持可管理的物理大小。
  • 清晰视图contents 处理器自动在已经压缩的历史上工作,无需在查询时编写复杂逻辑。
  • 解耦 – 你可以在不修改任何代理代码或模板逻辑的情况下调优压缩提示和策略。

对于严格的基于规则的削减,ADK 还提供了一个兄弟操作——Filtering——通过预构建的插件可以全局删除或裁剪上下文,依据确定性的规则进行处理。

rules before it ever reaches the model.

1.5 Context caching

现代模型支持 context caching(前缀缓存),这使得推理引擎能够在多次调用之间复用注意力计算。ADK 将 Session(存储)与 Working Context(视图)分离,为此优化提供了天然的基础。

该架构实际上将上下文窗口划分为两个区域:

区域常见内容
Stable prefixes(稳定前缀)系统指令、代理身份、长期摘要
Variable suffixes(可变后缀)最新用户回合、新的工具输出、小幅增量更新

由于 ADK 的流程和处理器是显式的,你可以将缓存友好性视为硬性设计约束。通过将经常复用的片段保持在上下文窗口的前部(即稳定前缀),并将高度动态的内容推向后部(即可变后缀),可以最大化缓存命中率。

为强制执行此规范,ADK 引入了 static_instruction,这是一种原语,可保证系统提示的不可变性,从而确保缓存前缀在多次调用中保持有效。

相关性:当前重要事项的代理管理

一旦结构建立,核心挑战转向相关性:在分层上下文架构下,当前模型的活动窗口中应该包含哪些具体信息?

ADK 通过 人类领域知识代理决策 的协作来回答这一问题。

  • 硬编码规则 成本低但僵硬。
  • 纯代理驱动的浏览 灵活但成本高且不稳定。

最佳的工作上下文是两者之间的协商。人类工程师定义架构——数据存放位置、如何汇总以及使用何种过滤器。随后代理提供智能,动态决定何时……。

2.1 工件:外部化大状态

早期的代理实现常落入 上下文倾倒 陷阱:将大负载——如 5 MB CSV、庞大的 JSON API 响应或完整的 PDF 转录——直接放入聊天历史。这会在会话中产生永久性负担;每一次后续交互都会拖拽该负载,埋没关键指令并增加费用。

ADK 通过将大数据视为 工件(Artifacts) 来解决此问题:这些是由 ArtifactService 管理的具名、带版本的二进制或文本对象。

在概念上,ADK 对大数据采用 句柄模式。大数据存放在工件存储中,而不是提示中。默认情况下,代理仅通过请求处理器看到轻量级引用(名称和摘要)。当且仅当代理需要原始数据来回答问题时,它会使用 LoadArtifactsTool。此操作会暂时将内容加载到工作上下文中。

关键是,ADK 支持 短暂扩展。模型调用或任务完成后,工件默认会从工作上下文中卸载。这将“每个提示中 5 MB 噪声”转变为精确的按需资源。数据可以非常庞大,但上下文窗口仍保持精简。

2.2 记忆:长期知识,按需检索

工件处理离散的大对象,ADK 的 记忆(Memory) 层管理跨会话的长期语义知识——用户偏好、过去的决策和领域事实。

MemoryService 基于两个原则构建:

  1. 可搜索性——记忆必须可搜索(而非永久固定)。
  2. 代理导向的检索——由代理决定何时获取。

该服务将数据(通常来自已完成的 Sessions)摄入向量或关键词语料库。随后代理通过两种模式访问这些知识:

  • 被动召回——代理识别出知识缺口(例如“用户的饮食限制是什么?”),并显式调用 load_memory_tool 在语料库中搜索。
  • 主动召回——预处理器基于最新用户输入执行相似度搜索,在模型甚至被调用之前通过 preload_memory_tool 注入可能相关的片段。

这取代了“上下文填塞”反模式,采用了“基于记忆”的工作流。代理仅召回当前步骤所需的片段,而不是携带其曾经进行的每一次对话的全部重量。

Source:

多代理上下文:谁看到什么,何时

单代理系统难以应对上下文膨胀;多代理系统会进一步放大这一问题。如果根代理将完整历史传递给子代理,而子代理又如此操作,就会触发 上下文爆炸。令牌数量急剧上升,子代理会被无关的对话历史弄糊涂。

每当一个代理调用另一个代理时,ADK 允许你 显式限定 被调用方能看到的内容——比如仅仅是最新的用户查询和一个制品——同时抑制大部分祖先历史。

3.1 两种多代理交互模式

  1. 代理作为工具 – 根代理将专用代理严格视为一个函数:用聚焦的提示调用它,获取结果后继续。被调用方只看到特定指令和必要的制品——没有历史。
  2. 代理转移(层级) – 完全将控制权交给子代理继续对话。子代理继承对 Session 的视图,并可以驱动工作流,调用自己的工具或进一步向下转移控制。

3.2 受限的转交用于代理转移

转交行为由诸如 include_contents 之类的开关控制,这些开关决定根代理向子代理流动多少上下文。

  • 默认模式 – ADK 传递调用者工作上下文的全部内容(当子代理确实需要完整历史时有用)。
  • 无模式 – 子代理看不到任何先前历史;它只收到你为它构造的新提示(例如最新的用户回合加上几次工具调用及其响应)。

专用代理只获得它们所需的最小上下文,而不是默认继承庞大的对话记录。

由于子代理的上下文同样是通过处理器构建的,这些转交规则会像其他上下文构建步骤一样插入同一流水线。

Back to Blog

相关文章

阅读更多 »

AI:真正的10倍生产力技巧

悖论:我们是真正高效,还是仅仅在表演?你真的因为 AI 而提升了 10 倍的生产力,还是只是因为算法而忙碌了 10 倍?这是一…