第 8 章 StrandsContext Engineering对话管理

第 8 章:上下文工程与对话管理

第 8 章:上下文工程与对话管理

在 YouTube 观看

关于本课

本课程的视频是当时的一个快照。Strands 一直在积极开发,所以本页展示的代码反映的是最新的写法,但视频里讲的概念依然成立。有疑问时,以代码为准。

具体来说,视频演示的是 SummarizingConversationManager 和 ContextOffloader plugin。它们已经被下面这套统一的 ContextManager 和 Offload 策略取代了。思路没变(把旧历史做摘要、把大的 tool 结果卸载出去、提前压缩),只是 API 从两套系统合成了一套。

本课代码:samples/08-context-management

什么是上下文工程

上下文工程是一门决定哪些信息进入模型的上下文窗口、什么时候进入、以什么形式进入的学问。prompt engineering 关注的是指令怎么写,而上下文工程关心的是让模型在对的时间拿到对的数据,同时不把窗口撑爆,也不浪费 token。

它分成四类:

类别 做什么 Strands 原语
Select(选择) 决定什么进入上下文,注入相关信息,过滤噪声 ContextInjector plugin
Compress(压缩) 压缩已经在上下文里的内容,把历史做摘要,截断结果 ContextManager 策略(truncate、summarize、drop)
Isolate(隔离) 分离关注点,让 sub-agent 拥有自己的上下文窗口 多 Agent 模式、context_manager="agentic"
Externalize(外置) 把大数据移出上下文,存到外部,只留一个紧凑引用 ContextManager stash + 检索 tool

一行搞定的默认值:context_manager="auto"

大多数场景下,从这里开始就行:

from strands import Agent

agent = Agent(context_manager="auto")

这一个参数就启用了:

  • Offloading:把大的 tool 结果卸载到 stash,原位换成截断后的预览
  • Summarization:把旧消息压成结构化摘要(不是直接丢掉)
  • Proactive compression:上下文用到 85% 时提前触发压缩,赶在溢出之前动手

在真实代码调查任务的 benchmark 里,成本降了 55%,准确率从 68% 涨到 98%。token 减半,结果更好。

如果 Agent 需要在长对话里保护某些特定上下文,用 context_manager="agentic"。模型自己拿到工具,可以自行摘要、截断或固定(pin)消息,用一些 token 换它的判断力。

Context Injector

ContextInjector plugin 在每次调用模型之前,把临时文本折进模型输入。这段文本永远不会写进对话历史,它只增强这一次调用。适合放那种 Agent 应该始终知道、但不属于持久历史的信息:当前时间、环境事实、检索查到的内容。

from datetime import datetime, timezone
from strands import Agent
from strands.vended_plugins.context_injector import ContextInjector

agent = Agent(
    plugins=[
        ContextInjector(
            lambda context: f"{datetime.now(timezone.utc).isoformat()}"
        ),
    ],
)

注入什么时候触发,可以控制:

  • trigger="userTurn"(默认):只在新的用户消息上触发
  • trigger="everyTurn":每次模型调用前都触发,包括任务中途的 tool 结果轮次
  • 自定义判断函数:trigger=lambda context: context.state.get("recall_enabled") is True

ContextManager 与 Offload 策略

ContextManager 是 context_manager="auto" 背后的引擎。当你需要自己控制上下文怎么管,就用 Offload 策略搭一条自己的策略流水线。

实验性

ContextManager 和 Offload 目前从 strands.experimental.context_manager 导出。它们功能完整,后续版本会移到主命名空间。

Offload 策略有三种:

策略 上下文里留下什么
Offload.truncate(...) 原始内容的头/尾预览
Offload.summarize(...) 由 LLM 生成的原始内容摘要
Offload.drop(...) 什么都不留(原文只进 stash)

每个策略都要传一个 target,用来控制它作用在什么内容上:

from strands.experimental.context_manager import Offload

Offload.summarize("*")                   # everything: all message types
Offload.truncate("tool_results")         # only successful tool results
Offload.drop("tool_result_errors")       # only errored tool results
Offload.summarize("assistant_text")      # only assistant text blocks
Offload.truncate(["bash", "read_file"])  # only results from specific tools
Offload.truncate(["!read_file"])         # everything except read_file

.when(...) 则用来设触发条件:

  • threshold=N:单个 block 超过 N 个 token 就触发(按 block 模式)
  • utilization=0.85:上下文窗口用到 85% 时触发(按消息模式)
  • preserve_recent=N:跳过最近 N 条命中的消息

Truncate 策略(丢掉最旧的)

最简单的做法。上下文满了就移除旧消息:

from strands import Agent
from strands.experimental.context_manager import ContextManager, Offload

agent = Agent(
    tools=[...],
    context_manager=ContextManager(
        strategies=[
            Offload.truncate("tool_results").when(threshold=2500),
            Offload.truncate("*").when(utilization=0.9, preserve_recent=10),
        ],
    ),
)

📂 sliding_window.py

Summarize 策略

不丢旧消息,而是把它们压成摘要。信息保留得更多,但摘要是有损的:

from strands.experimental.context_manager import ContextManager, Offload

agent = Agent(
    tools=[...],
    context_manager=ContextManager(
        strategies=[
            Offload.truncate("tool_results").when(threshold=2500),
            Offload.summarize("*").when(utilization=0.85, preserve_recent=10),
        ],
    ),
)

📂 summarizing.py

自定义 summarizer(用更便宜的模型)

给历史做摘要,用不着你最强的模型。通过 config dict 传一个更便宜的模型,再加一段针对业务领域的 prompt:

from strands.models import BedrockModel
from strands.experimental.context_manager import ContextManager, Offload

summarizer_model = BedrockModel(model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0")

agent = Agent(
    tools=[...],
    context_manager=ContextManager(
        strategies=[
            Offload.truncate("tool_results").when(threshold=2500),
            Offload.summarize(
                "*",
                {
                    "model": summarizer_model,
                    "system_prompt": "Summarize the customer service conversation...",
                },
            ).when(utilization=0.85, preserve_recent=8),
        ],
        stash=False,
    ),
)

stash=False 会完全关掉 stash:卸载掉的内容不保存在任何地方,Agent 也拿不到检索工具。如果你只是纯做摘要、不需要把东西捞回来,这样就行。如果你希望 Agent 能找回被卸载的 tool 结果,就保持开启(默认就是开启)。

📂 custom_summarizer.py

策略对比

策略 取舍
context_manager="auto" 最好的默认值。Offloading + summarization + 主动压缩
context_manager="agentic" 模型自己管上下文。用 token 换判断力
Offload.truncate(...) 简单、可预测。旧信息彻底丢失
Offload.summarize(...) 保留更多信息。有损压缩,多花一次 LLM 调用
Offload.drop(...) 最激进。内容只进 stash
Context injector 每次调用的临时数据,永不写入历史

参考资源