
本文翻译自 Google Developer 博客的 Architecting efficient context-aware multi-agent framework for production[1]。
AI 智能体(Agent)的开发领域正经历着风起云涌的变化。我们早就跨过了还在做单轮聊天机器人原型的阶段。如今,各家机构正在部署的是那种复杂的、自主的智能体,它们能处理长链路任务(long-horizon tasks):比如自动化工作流、开展深度研究,甚至维护庞大的代码库。
但这一愿景很快就撞上了一堵墙:上下文(Context)。
随着智能体运行时间的拉长,它们需要“记住”的信息量——聊天记录、工具返回的数据、外部文档、中间的推理过程——会呈爆炸式增长。目前的“通解”通常是依赖基础模型(Foundation Models)越来越大的上下文窗口(Context Window)。但是,单纯指望给智能体更大的空间来粘贴文本,绝不可能是长久的扩展之道。
为了构建可靠、高效且易于调试的生产级智能体,业界正在探索一门新的学科:
上下文工程(Context Engineering) —— 不再把上下文仅仅当作一段文本,而是将其视为系统中的“一等公民”,拥有独立的架构、生命周期和约束条件。
基于我们在扩展复杂的单智能体或多智能体系统方面的经验,我们在 Google Agent Development Kit (ADK)[2] 中设计并迭代了上下文栈(Context Stack)来支持这一学科。ADK 是一个开源的、原生支持多智能体的框架,旨在让主动的上下文工程在实际系统中落地。
更大的上下文窗口虽能缓解问题,却治标不治本。在实践中,那种幼稚的模式——把所有东西都追加到一个巨大的提示词(Prompt)里——会在以下三重压力下崩塌:
单纯靠“砸 Token”只能争取时间,却改变不了问题的本质。要实现规模化,我们需要改变上下文的表示和管理方式,而不仅仅是纠结于一次调用能塞进多少内容。
在上一代智能体框架中,上下文被视作一个可变的字符串缓冲区。ADK 则建立在一个截然不同的理念之上:上下文是一个基于更丰富状态系统的“编译视图”(Compiled View)。
在这个视角下:
一旦你接受了这个心智模型,上下文工程就不再是“提示词微调的奇技淫巧(prompt gymnastics)”,而变成了正经的系统工程。你将被迫思考标准的系统问题:_中间表示(IR)是什么?在哪里进行压缩?如何让转换过程可被观测?_
ADK 的架构通过三个设计原则回答了这些问题:
ADK 的分层结构、相关性机制及其多智能体交接语义——本质上就是这一“编译器”理念及上述三个原则的应用:
接下来的部分将逐一解析这些支柱。
大多数早期的智能体系统隐含地假设了一个单一的上下文窗口。ADK 则反其道而行之。它将存储与展示分离,并将上下文组织成职责分明的层级:
对于每次调用,ADK 都会从底层状态重新构建工作上下文。它从指令和身份开始,拉取选定的会话事件,并可选地附加记忆结果。这个视图是瞬态的(调用完即焚)、可配置的(改格式不需要动存储数据),并且是模型无关的。
这种灵活性是编译器视图带来的第一个红利:你不再硬编码“那个提示词”,而是开始将其视为一种可以不断迭代的衍生表示。
一旦分离了存储与展示,你就需要一套机制将前者“编译”为后者。在 ADK 中,每个基于 LLM 的智能体背后都有一个 LLM 流(Flow),这个流维护着一份有序的处理器列表。
一个(简化的)SingleFlow 可能长这样:
self.request_processors += [
basic.request_processor,
auth_preprocessor.request_processor,
request_confirmation.request_processor,
instructions.request_processor,
identity.request_processor,
contents.request_processor,
context_cache_processor.request_processor,
planning.request_processor,
code_execution.request_processor,
output_schema_processor.request_processor,
]
self.response_processors += [
planning.response_processor,
code_execution.response_processor,
]
这些流就是 ADK 编译上下文的机制。顺序至关重要:每个处理器都在前一步骤的输出之上进行构建。这为你提供了天然的切入点,用于插入自定义过滤、压缩策略、缓存逻辑和多智能体路由。你不再需要重写巨大的“提示词模板”;你只需添加或重新排序处理器。
一个 ADK 会话(Session)代表了对话或工作流实例的确定性状态。具体来说,它是一个容器,装着会话元数据(ID、应用名称)、用于存放结构化变量的状态暂存器,以及——最重要的——按时间顺序排列的事件(Events)列表。
ADK 不存储原始的提示词字符串,而是将每一次交互——用户发的消息、智能体回的话、工具的调用与结果、控制信号和报错——都记录为强类型的 Event 对象。这种结构化选择带来了三个明显的优势:
连接“会话”和“工作上下文”的桥梁是 contents 处理器。它负责将会话转换为工作上下文中的历史记录部分,主要干了三件大事:
Content 对象,并根据当前使用的特定模型 API,打上正确的角色标签(用户/助手/工具)和注解。llm_request.contents,确保下游处理器——以及模型本身——接收到的是一条干净、连贯的对话线索。在这个架构中,会话是你的事实基准(Ground Truth);工作上下文仅仅是一个计算出来的投影,你可以随着时间推移对其进行优化和打磨。
如果无休止地追加原始事件,延迟和 Token 消耗量迟早会失控。ADK 的上下文压缩功能旨在从会话层解决这个问题。
当达到设定的阈值(比如调用次数)时,ADK 会触发一个异步进程。它利用 LLM 在一个滑动窗口上对旧事件进行总结,并将摘要作为一个带有“compaction(压缩)”动作的新事件写回会话。关键在于,这让系统可以删减或降低那些已被总结的原始事件的优先级。
由于压缩直接作用于事件流本身,其收益会向下游传导:
contents 处理器会自动处理已经被压缩过的历史记录,查询时无需编写复杂的逻辑。这为长上下文建立了一个可扩展的生命周期。对于那些严格基于规则的缩减,ADK 提供了类似的功能——过滤(Filtering)——通过预置插件,在上下文到达模型之前,根据既定规则全局丢弃或修剪内容。
现代模型支持上下文缓存(Context Caching,即前缀缓存),允许推理引擎在多次调用间复用注意力机制的计算结果。ADK 的“会话”(存储)与“工作上下文”(视图)分离的设计,为这种优化提供了天然的土壤。
该架构有效地将上下文窗口划分为两个区域:
因为 ADK 的流和处理器是显式的,你可以将“对缓存友好”作为一个硬性的设计指标。你可以通过对流水线排序,确保存放在上下文窗口前端的片段是经常复用的,同时将高度动态的内容推向末端。为了强制执行这种严谨性,我们引入了 static instruction(静态指令),这是一种保证系统提示词不可变的原语,确保缓存前缀在多次调用中始终有效。
这是上下文工程作为全栈系统工作的一个典型例子:你不仅在决定模型看什么,还在优化硬件底层重新计算张量的频率。
结构一旦搭好,核心挑战就变成了相关性:_既然有了分层的上下文架构,那么哪些具体信息应该放入模型当前活跃的窗口里?_
ADK 通过人类领域知识与智能体决策之间的协作来回答这个问题。光靠硬编码规则既省钱但太死板;光靠智能体去浏览所有内容则灵活但太贵且不稳定。
最佳的工作上下文是两者协商的结果。人类工程师定义架构——数据存在哪里、如何总结以及应用什么过滤器。然后,智能体提供“智力”,动态决定何时主动“伸手”去获取特定的记忆块或制品,以满足当前用户的请求。
早期的智能体实现经常掉进“上下文倾倒(context dumping)”的坑里:把巨大的数据载荷——一个 5MB 的 CSV、一个超大的 JSON 响应或完整的 PDF 实录——直接塞进聊天记录。这就像给会话背上了一笔永久的高利贷;随后的每一轮对话都要拖着这个包袱,既掩埋了关键指令,又拉高了成本。
ADK 通过将大型数据视为制品(Artifacts) 来解决这个问题:由 ArtifactService 管理的命名、版本化的二进制或文本对象。
概念上,ADK 对大数据应用了句柄模式(Handle Pattern)。大数据存在制品库里,而不是提示词里。默认情况下,智能体通过请求处理器只看到一个轻量级的引用(名字和摘要)。当——且仅当——智能体需要原始数据来回答问题时,它会使用 LoadArtifactsTool。这个动作才会将内容临时加载到工作上下文中。
关键点在于,ADK 支持临时扩展(Ephemeral Expansion)。一旦模型调用或任务完成,默认情况下制品会从工作上下文中卸载。这一招通过按需加载,将“每个提示词里 5MB 的噪音”变成了一种精准调用的资源。数据可以很大,但上下文窗口始终保持清爽。
制品处理的是离散的大文件,而 ADK 的记忆(Memory)层则管理超越单个会话的长效语义知识——用户偏好、过去的决策和领域事实。
我们围绕两个原则设计了 MemoryService:记忆必须是可搜索的(而不是永久钉在上下文中),且检索应由智能体主导。
MemoryService 将数据(通常来自已结束的会话)摄取到向量或关键词语料库中。然后,智能体通过两种模式访问这些知识:
load_memory_tool 来搜索语料库。preload_memory_tool 预先注入可能相关的片段。这种方法用“基于记忆”的工作流取代了“上下文填充(Context Stuffing)”的反模式。智能体只回忆当前步骤所需的那一点片段,而不是背负着它们这辈子所有对话的沉重包袱。
单智能体系统面临上下文膨胀的困扰;多智能体系统则会加剧这一问题。如果根智能体(Root Agent)将其完整的历史记录传给子智能体,而子智能体也照做,就会引发上下文爆炸。Token 数量激增,子智能体也会被无关的“祖传”对话历史搞得晕头转向。
每当一个智能体调用另一个智能体时,ADK 允许你显式限定(Explicitly Scope) 被调用者能看到的内容——也许只是最新的那条用户查询和一个制品——同时屏蔽掉绝大部分祖先历史。
宏观上看,ADK 将多智能体交互归纳为两种架构模式。
第一种是 智能体即工具(Agents as Tools)。在这里,根智能体将专用智能体严格视为一个函数:给它一个专有的提示词,拿回结果,然后继续。被调用者只看到特定的指令和必要的制品——没有历史包袱。
第二种是 智能体转移(Agent Transfer,或层级结构)。在这里,控制权完全移交给子智能体以继续对话。子智能体继承会话的一个视图,并可以主导工作流,调用自己的工具或进一步向下转移控制权。
交接(Handoff)行为由诸如 include_contents(包含内容)之类的开关控制,这些开关决定了有多少上下文能从根智能体流向子智能体。在默认模式下,ADK 传递调用者工作上下文的全部内容——当子智能体确实需要完整历史来理解语境时,这很有用。在 none 模式下,子智能体看不到之前的历史;它只接收你为它构建的新提示词(例如,最新的用户轮次加上几个工具调用和响应)。让专用智能体只拿它们所需的最小上下文,而不是默认继承一份巨大的聊天记录。
因为子智能体的上下文也是通过处理器构建的,这些交接规则可以无缝插入到与单智能体调用相同的流管道中。你不需要单独搞一套多智能体机制;你只是调整了现有的上下文编译器被允许看到的上游状态的范围。
基础模型通常在固定的角色模式下运行:system(系统)、user(用户)和 assistant(助手)。它们天生不理解“助手 A”与“助手 B”的区别。
当 ADK 转移控制权时,它通常必须重构(Reframe) 现有的对话,以便新智能体看到连贯的工作上下文。如果新智能体只是看到来自前一个智能体的一连串“Assistant”消息,它会产生幻觉,以为那些动作是它自己 做的。
为了防止这种情况,ADK 在交接期间执行主动翻译:
[背景信息]: 智能体 B 说...),而不是作为新智能体自己的输出出现。实际上,ADK 从子智能体的视角构建了一个全新的工作上下文,同时保留了会话中的事实历史。这确保了正确性,允许每个智能体都能扮演“Assistant”角色,而不会将整个系统的历史错误地归揽到自己头上。
随着我们推动智能体去解决更长周期的问题,“上下文管理”不能再仅仅意味着“字符串操作”。它必须被视为与存储和计算同等重要的架构级关注点。
ADK 的上下文架构——分层存储、编译视图、流水线处理和严格的作用域划分——正是我们对这一挑战的回答。它将构建智能体所需的严谨系统工程封装起来,助力开发者从有趣的 Demo 原型,迈向可扩展、高可靠的生产级系统。
参考资料
[1]
Architecting efficient context-aware multi-agent framework for production: https://developers.googleblog.com/en/architecting-efficient-context-aware-multi-agent-framework-for-production/
[2]
Google Agent Development Kit (ADK): https://github.com/google/adk-python