第 8 章:上下文工程与对话管理
第 8 章:上下文工程与对话管理
关于本课
本课程的视频是当时的一个快照。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),
],
),
)
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),
],
),
)
自定义 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 结果,就保持开启(默认就是开启)。
策略对比
| 策略 | 取舍 |
|---|---|
context_manager="auto" |
最好的默认值。Offloading + summarization + 主动压缩 |
context_manager="agentic" |
模型自己管上下文。用 token 换判断力 |
Offload.truncate(...) |
简单、可预测。旧信息彻底丢失 |
Offload.summarize(...) |
保留更多信息。有损压缩,多花一次 LLM 调用 |
Offload.drop(...) |
最激进。内容只进 stash |
| Context injector | 每次调用的临时数据,永不写入历史 |