ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Context Offloader 设计解析:将大型工具结果外部化存储,从根源上防止 Agent 上下文溢出

Context Offloader 设计解析:将大型工具结果外部化存储,从根源上防止 Agent 上下文溢出 人工智能大模型AI AgentAgent 框架多智能体工具调用MCP 服务【免费下载链接】harness-sdkBuild an agent harness and control it end-to-end. Open-source SDK for production AI agents in Python TypeScript - any model, any cloud.项目地址https://gitcode.com/GitHub_Trending/sdkpython13/harness-sdk点击查看免费下载导读在 Agent 应用中文件读取、API 调用、数据库查询等工具常常返回数十万字符级的大型结果单次工具结果即可瞬间耗尽模型的上下文窗口。本文基于 harness-sdk 仓库中的设计文档 team/designs/0009-context-offloader.md完整剖析「Context Offloader」这一跨 SDK 设计方案的来龙去脉它以Plugin形式在工具执行阶段AfterToolCallEvent主动拦截超限结果将完整内容持久化到可插拔存储后端并用「截断预览 逐块引用」替换上下文中的原始内容同时内置retrieve_offloaded_content检索工具供 Agent 按需回取。读完本文你将掌握该方案解决的两大痛点、Storage接口与三种内置后端内存 / 本地文件 / S3的选型逻辑、完整的配置参数与代码示例以及它与SlidingWindowConversationManager反应式截断之间的职责边界。问题背景一次超大工具结果即可击穿上下文当工具返回超大结果文件转储、API 响应、数据库查询或日志输出时整个内容会以一条工具结果消息进入对话历史。一条超大的结果就可能在一个步骤内把上下文推向溢出。当前SlidingWindowConversationManager对这一问题采取反应式处理只有在ContextWindowOverflowError发生之后它才把结果替换为一条通用消息const toolResultTooLargeMessage The tool result was too large!这种兜底机制存在两个根本性问题数据永久丢失。完整输出被直接丢弃Agent 从此失去引用或推理该内容的能力。反应式时序浪费。替换动作只发生在模型已经拒绝请求之后——超大的结果先占用了上下文空间、触发了溢出、浪费了一次往返最后才被截断。设计文档由此提出核心主张在工具结果进入对话之前、于执行阶段拦截它。完整输出持久化到可插拔的存储后端对话中只保留一段截断预览和指向存储产物的引用。设计方案以 Plugin 形态挂在AfterToolCallEvent上方案决策很明确工具结果外部化externalization被实现为一个Plugin挂在AfterToolCallEvent钩子上。当工具结果超过可配置的大小阈值时插件把完整输出外部化并按可配置策略替换对话内容。其在整个 Agent 循环中的位置如下从源码实现看这条链路确实落地为两个钩子。Python 实现在 strands-py/src/strands/vended_plugins/context_offloader/plugin.py 中注册了_handle_tool_result处理AfterToolCallEvent与_on_before_model_call处理BeforeModelCallEvent用于周期驱逐过期条目两个钩子TypeScript 实现在 strands-ts/src/vended-plugins/context-offloader/plugin.ts 的initAgent中同样通过agent.addHook(AfterToolCallEvent, ...)与agent.addHook(BeforeModelCallEvent, ...)完成注册。测试 strands-py/tests/strands/vended_plugins/context_offloader/test_plugin.py 也验证了插件自动发现两个钩子test_hooks_auto_discovered。选择 Plugin 而非 Agent 或 Tool 级配置是因为插件模式让外部化保持独立且可选opt-in能与其它插件干净组合——这一点在文末「备选方案」部分会详细对比。Storage 抽象存储是必选参数插件接受一个必填的storage参数它实现Storage接口。设计文档给出的接口契约如下export interface Storage { store(key: string, content: Uint8Array, contentType?: string): Promisestring retrieve(reference: string): Promise[Uint8Array, string] }Python 侧对应的协议定义在 strands-py/src/strands/vended_plugins/context_offloader/storage.pyStorageProtocol含store(key, content, content_type)与retrieve(reference)并特别注明协议刻意不包含驱逐或删除方法长时运行 Agent 应自行创建按会话隔离的存储实例或使用自带生命周期管理的后端如 S3 生命周期策略。storage 是必选参数这是刻意为之用户必须显式选择后端从而避免隐式行为、让持久化模型清晰可见。SDK 内置三种实现实现行为适用场景InMemoryStorage内容存内存零文件系统副作用按块保留内容类型测试、Serverless、纯上下文优化FileStorage写入本地目录用.metadata.json伴生文件追踪内容类型调试、审计、离线回取S3Storage写入 S3 桶通过 S3 对象元数据保留内容类型遵循S3SessionManager模式生产工作负载、共享存储不过需要指出实现上的演进后续版本中 Python 与 TypeScript SDK 都引入了unified Storage统一存储位于strands.storage/strands-agents/sdk/storageInMemoryStorage、LocalFileStorage、S3Storage由统一存储提供而设计文档中的InMemoryStorage/FileStorage/S3Storage被标记为 deprecated 的 offloader 专用版本。TypeScript 实现还展示了统一存储的两种集成细节一是命名空间隔离——统一 Storage 会通过resolveNamespace(storage, offloader)自动把键限定在offloader/前缀下二是内容类型封装——每个块以「2 字节内容类型长度 内容类型 UTF-8 内容字节」的帧格式存储让一个存储键对应一个 offloaded 块、避免内容类型元数据占用额外条目见 strands-ts/src/vended-plugins/context-offloader/plugin.ts 的frameContent/unframeContent。此外支持沙箱的存储如LocalFileStorage及其命名空间视图暴露for_sandbox/forSandbox会在插件初始化时按 Agent 绑定到其沙箱文件型后端的 I/O 自动经由 Agent 沙箱路由Python 侧见_storage_for_agentTS 侧见_storageForAgent。内容类型处理逐块存储、逐块替换工具结果可能包含多种内容块类型。当结果超过maxResultTokens阈值时每个内容块被单独存储到后端、保留其内容类型然后替换为预览与逐块引用。各类型的处理策略类型行为Text以text/plain存储替换为截断预览JSON以application/json存储序列化替换为预览Image以原生格式存储如image/png替换为占位符 引用Document以原生格式存储如application/pdf替换为占位符 引用插件先用模型的countTokens方法估算工具结果的总 token 数tiktoken 可用时优先否则回退到 chars/4 启发式若总量超过maxResultTokens则存储全部块并替换结果。TS 实现里这一点非常清晰plugin.ts 的_storeBlockTextBlock存为text/plain并记录字符数JsonBlock序列化后存为application/json并记录字节数ImageBlock/VideoBlock/DocumentBlock提取原始字节、以image/{format}、video/{format}、application/{format}存储并记录字节数不支持的块类型会被跳过并记日志警告。被替换后的内容大致长这样设计文档示例[Offloaded: 3 blocks, ~5,432 tokens] [Use the preview below to answer if possible.] first N tokens as preview [Stored references:] mem_1_tooluse_abc123_0 (text, 12,345 chars) mem_1_tooluse_abc123_1 (json, 8,901 bytes)实际用户指南site/src/content/docs/user-guide/sdk/plugins/context-offloader.mdx中的落地产物更完整预览段以[Offloaded: N blocks, ~M tokens]开头随后是指导文本提示优先用预览、需要细节时用retrieve_offloaded_content并给出pattern/line_range用法、最后才整体回取接着是前previewTokens个 token 的预览最后是[Stored references:]逐块引用列表。当includeRetrievalTool启用时指导文本会指示 Agent 使用retrieve_offloaded_content按引用获取完整内容。内置检索工具retrieve_offloaded_content插件默认注册一个内置工具retrieve_offloaded_contentincludeRetrievalTool: trueAgent 可以按引用回取 offloaded 内容。检索以原生类型返回文本返回字符串、JSON 返回 JSON 块、图片返回图片块、文档返回文档块——这样既不需要用户单独配置文件读取或 S3 工具也让存储后端对模型保持透明。随着实现演进该工具还支持定向检索避免把完整内容重新注入上下文参数类型说明referencestr/string必填offloaded 结果中的存储引用patternstr/string用于 grep 的正则或关键词line_range{ start, end }1 起始的包含式行区间context_linesint/numberpattern 匹配周围的行数默认 5支持的检索模式包括模式搜索提供pattern按正则/关键词匹配并带context_lines上下文、行区间提供line_range随机访问指定行、组合同时提供两者在区间内搜索、头部仅提供context_lines返回前 N 行、完整回取省略全部可选参数对大型内容不鼓励。结果带行号以支持后续查询大结果集会被截断并给出缩小范围的指引二进制内容不可搜索对其使用 pattern/line_range 会报错。用户指南给出了生动示例——一次 150KB JSON 的工具结果被 offload 后Agent 可以用{ reference: mem_1_tool-123_0, pattern: admin, context_lines: 2 }得到带行号与上下文的匹配片段或用{ reference: mem_1_tool-123_0, line_range: { start: 45, end: 55 } }精准读取指定行段。Python 实现细节见 plugin.py 的retrieve_offloaded_content方法检索成功会刷新该引用的驱逐周期活跃回取的内容可以越过evict_after_cycles存活引用不存在时抛出ValueError: reference not found。两个必须注意的实现细节TS 源码_handleToolResult中有明确注释防循环回取retrieve_offloaded_content自身的返回结果被排除在再 offload 之外防止 offload→回取→再 offload 的死循环Python 侧同理由include_retrieval_tool与工具名判断。委派结果跳过AgentAsTool委派工具的结果是最终委派答案会被转换进AgentResult.lastMessage因此被跳过、不参与截断/offload。该工具可以通过includeRetrievalTool: false关闭关闭后offload 指导文本会改为提示 Agent 使用自身可用工具访问数据。快速上手两语言最小配置与阈值定制设计文档的 Developer Experience 部分给出了 TS 侧三种典型用法。结合 Python 源码等价用法如下。Python —— 仅做上下文缩减内存存储from strands import Agent from strands.vended_plugins.context_offloader import ContextOffloader, InMemoryStorage agent Agent(plugins[ ContextOffloader(storageInMemoryStorage()) ])Python —— 文件存储持久化产物、自定义阈值from strands.vended_plugins.context_offloader import ContextOffloader, FileStorage agent Agent(plugins[ ContextOffloader( storageFileStorage(./artifacts/), max_result_tokens5_000, preview_tokens2_000, ) ])Python —— S3 存储持久化到桶from strands.vended_plugins.context_offloader import ContextOffloader, S3Storage agent Agent(plugins[ ContextOffloader( storageS3Storage(my-agent-artifacts, prefixtool-results/), ) ])TypeScript —— 与设计文档一致的三种形态import { Agent } from strands-agents/sdk import { ContextOffloader, InMemoryStorage, FileStorage, S3Storage } from strands-agents/sdk/vended-plugins/context-offloader // In-memory: context reduction only const agent new Agent({ tools: [dataAnalysis, apiClient, fileProcessor], plugins: [new ContextOffloader({ storage: new InMemoryStorage() })], }) // File storage: persists artifacts to disk, custom thresholds const agent new Agent({ tools: [dataAnalysis, apiClient, fileProcessor], plugins: [ new ContextOffloader({ storage: new FileStorage({ artifactDir: ./my-artifacts }), maxResultTokens: 5_000, previewTokens: 2_000, }), ], }) // S3 storage: persists artifacts to a bucket const agent new Agent({ tools: [dataAnalysis, apiClient, fileProcessor], plugins: [ new ContextOffloader({ storage: new S3Storage({ bucket: my-agent-artifacts, prefix: tool-results/ }), }), ], })未启用插件的 Agent 行为完全不变——继续由SlidingWindowConversationManager反应式兜底插件是纯增量、可选加入的。配置参数全表与校验规则PythonContextOffloader.__init__见 plugin.py与 TypeScriptContextOffloaderConfig见 plugin.ts的参数对应如下Python 参数TypeScript 参数默认值说明storagestorage必填存储后端实例省略时在初始化期解析 Agent 级 storage仍无则回退到内存存储max_result_tokensmaxResultTokens2_500估计 token 数超过该阈值的结果被 offloadpreview_tokenspreviewTokens1_000留在上下文中的预览 token 数include_retrieval_toolincludeRetrievalToolTrue/true是否注册retrieve_offloaded_content工具should_offload—TS 未暴露NonePython 独有回调(tool_name, token_count, **kwargs) - bool仅对超限结果生效返回True才 offload可用于只 offload 特定大输出工具evict_after_cyclesevictAfterCycles20条目在多少个 Agent 循环周期后被驱逐删除设为None/null关闭驱逐两类实现都做了相同的构造期参数校验max_result_tokens必须为正整数preview_tokens必须非负且严格小于max_result_tokensevict_after_cycles必须是正整数或None。违反任一条会在构造时抛出ValueErrorPython 侧错误信息如max_result_tokens must be positive、preview_tokens must be less than max_result_tokens测试 test_plugin.py 对这几条校验有完整覆盖。另外预览切片在 TS 中以previewTokens * 4字符为上限直接截断slicePreviewPython 侧文档注明优先使用 tiktoken 精确切片、回退到 chars/4 启发式。驱逐机制与存储成本设计文档并未涉及驱逐但这是落地实现新增的重要能力见 site/src/content/docs/user-guide/sdk/plugins/context-offloader.mdx 与两语言 changelog 中的 add turn-based eviction to InMemoryStorage 条目offloaded 条目在evict_after_cycles默认 20个 Agent 循环周期后自动删除。驱逐在每次模型调用前BeforeModelCallEvent钩子依据cycle_count触发陈旧键stored_cycle 当前周期 - evict_after_cycles被批量删除。TS 侧为 unified Storage 维护_keyStoredAt映射记录每个键的存储周期Python 侧对InMemoryStorage走其内置_evict(cycle)对 unified Storage 走storage.delete(key)。被驱逐的内容永久丢失因此如果 Agent 会在多轮之后回头访问 offloaded 内容应调大该值或设为None/null禁用驱逐。此外成功回取会刷新驱逐计时活跃使用的条目不会被过早清掉。存储成本也不可忽视S3Storage每次大结果都会产生 PUT/GET 与存储费用LocalFileStorage每次大结果都会落盘InMemoryStorage则随进程生命周期存在、重启即失。在 Harness 中的开箱集成两个高层 Harness 包装都内置了 ContextOffloaderPython Harnessharness-py/src/strands_harness/agent.py的_durable_offloader使用FileStorage(offload_dir)并采用_AUTO_MAX_RESULT_TOKENS 1_500、_AUTO_PREVIEW_TOKENS 750的自动档阈值_has_offloader用于检测用户是否已自行传入 offloader 实例以避免重复注入。TypeScript Harnessharness-ts/src/agent.ts同样定义了AUTO_MAX_RESULT_TOKENS 1_500、AUTO_PREVIEW_TOKENS 750并从strands-agents/sdk/vended-plugins/context-offloader导入ContextOffloader、FileStorage消费者通过plugins传入的ContextOffloader实例会替换Harness 默认添加的那个。还有一个值得一提的协同点programmatic_tool_callerharness-ts/src/tools/programmatic-tool-caller.ts在内部工具调用的invocationState中写入SKIP_CONTEXT_OFFLOAD_KEY strands:skipContextOffload因为内层结果由沙箱中的 Python 代码消费而非模型直接消费用预览替换数据会破坏它——这是 SDK 与 offloader 契约的一个具体例子。备选方案与最终取舍设计文档记录了三个被否决的备选方案理解它们有助于把握方案边界不持久化的纯截断。现状SlidingWindowConversationManager已经会截断大结果但会永久丢弃完整输出。它更简单无文件系统依赖却以数据永久丢失为代价外部化则保留完整输出供调试与 Agent 按需回取。在 Agent 或 Tool 上配置外部化。比插件更易发现但无法与其它插件干净组合插件模式让外部化保持独立、可选加入。策略模式捆绑存储 预览。把存储与预览生成捆进单一externalize()方法自定义格式更灵活但耦合了两个关注点分离的存储协议让检索工具能直接调用retrieve()而无需理解预览格式。边界与权衡它不是什么预览 vs 完整内容Agent 基于预览推理而非完整结果。若答案深埋在超大结果中Agent 可能错过。应针对用例调优preview_tokens在上下文占用与信息丢失之间的平衡默认开启的retrieve_offloaded_content提供了回取兜底若 Agent 已有能直接访问存储后端的工具文件读取、shell 等可用include_retrieval_toolFalse关闭它。不是会话管理的替代品本插件只处理单条大结果跨多轮的整体上下文增长仍需SlidingWindowConversationManager之类的会话管理器负责。存储需显式配置没有隐式默认使用文件或 S3 存储时产物会累积、需要清理预览可能不含模型所需信息导致额外的回取调用。迁移无破坏性变更插件纯增量、可选加入设计文档状态为 AcceptedPython 实现在 PR #2162 对应的代码路径中落地。结语Context Offloader 把「大工具结果」问题从溢出后的反应式补救前移为进入对话前的主动外部化用显式选择的存储后端保留完整数据用紧凑预览保住上下文预算再用内置检索工具保留按需回取能力。它由设计文档 0009-context-offloader.md 定义、由 strands-py/src/strands/vended_plugins/context_offloader/plugin.py 与 strands-ts/src/vended-plugins/context-offloader/plugin.ts 双语言落地并经 Python 插件测试 与 用户指南 完整验证与文档化——是从设计到实现的完整闭环范例也是任何长上下文 Agent 值得优先评估的上下文治理手段。赞分享人工智能大模型AI AgentAgent 框架多智能体工具调用MCP 服务【免费下载链接】harness-sdkBuild an agent harness and control it end-to-end. Open-source SDK for production AI agents in Python TypeScript - any model, any cloud.项目地址https://gitcode.com/GitHub_Trending/sdkpython13/harness-sdk点击查看免费下载相关推荐Akka Streams limit 算子为不可信上游设置元素上限防止内存溢出Akka Streams limit 算子为不可信上游设置元素上限防止内存溢出 导读 limit 是 Akka Streams 提供的限制型算子它把上后端并发编程异步编程加入 Apache Fineract 社区贡献代码和参与开发的完整指南加入 Apache Fineract 社区贡献代码和参与开发的完整指南 Apache Fineract 是一个强大的开源核心银行平台它为全球金融服务提供灵活后端金融科技企业应用DeepSeek Harness 工具结果保留库deepseek-ai/dsh-output-retention 的有界模型上下文设计DeepSeek Harness 工具结果保留库 deepseek ai/dsh output retention 的有界模型上下文设计 本篇技术指南以 D人工智能AI AgentAgent 框架DeepSeek上一篇Pretty TypeScript Errors 缓存策略终极指南Map缓存与LRU淘汰算法实现下一篇Path of Building终极指南如何免费构建流放之路最强角色创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表