09
9 / 50

Context Engineering & Memory

⏱️ 35分钟

Context 不是资料仓库,而是模型本轮决策的工作台。Memory 也不是把聊天永久保存,而是从历史中筛选可复用事实,再在合适的任务里安全取回。系统稳定性取决于“哪些信息进入本轮推理”,不是数据库里存了多少文本。


先分清四个概念

概念生命周期谁能看到典型内容
Local run state单次运行应用代码与工具数据库连接、用户 ID、重试次数
LLM-visible context单次模型调用模型指令、消息、工具定义、检索片段
Working memory当前任务Agent 与模型当前计划、已完成步骤、待决策问题
Long-term memory跨会话持久化系统,按需检索已确认偏好、业务事实、历史决策

OpenAI Agents SDK 也明确区分本地 context 与模型可见 context:依赖对象可以留在代码层,不需要为了让工具访问数据库而把连接信息发送给模型。

业务数据与依赖 ──→ Local context ──→ Tool
                           │
                           └─ 只把任务所需结果送入 LLM context

历史事件 ──→ Memory write gate ──→ Memory store
                                      │
当前任务 ──→ Retrieval + policy ──────┘ → Working set → LLM

Context 的目标不是“塞满”

Anthropic 将 Context Engineering 定义为:从所有可能信息中,持续选择最有助于当前行为的 token。最实用的原则是 最小充分集

一轮调用可以按以下层次组织:

  1. 稳定指令:角色、安全边界、输出契约。
  2. 当前目标:用户要达成的结果和验收条件。
  3. 工作状态:已完成步骤、未决问题和剩余预算。
  4. 按需知识:带来源的检索结果或 Memory。
  5. 工具定义:仅加载本轮可用的工具。
  6. 最近观察:对下一步仍有价值的结果。

不要写死“Context 必须用到 70%”。不同模型、任务和输出长度不同。应先为输出和工具结果预留空间,再通过 eval 找到自己的阈值。


给 Context 做预算

type ContextBudget = {
	modelLimit: number;
	reservedOutput: number;
	stableInstructions: number;
	taskState: number;
	retrievedKnowledge: number;
	toolDefinitions: number;
	recentObservations: number;
};

function assertBudget(b: ContextBudget) {
	const inputBudget = b.modelLimit - b.reservedOutput;
	const planned =
		b.stableInstructions +
		b.taskState +
		b.retrievedKnowledge +
		b.toolDefinitions +
		b.recentObservations;
	if (planned > inputBudget) throw new Error('CONTEXT_BUDGET_EXCEEDED');
}

预算超限时的优先顺序通常是:删除重复内容 → 截断低价值工具结果 → 归纳旧观察 → 减少检索片段 → 延迟加载工具。稳定安全指令不能成为第一个被压缩的对象。


Memory 应该记什么

类型示例是否适合长期保存
Semantic facts用户所在时区、团队项目名称已确认且有业务价值时保存
Episodic events某次部署失败、某次订单处理有追溯或恢复价值时保存
Preferences喜欢简洁回答、默认导出格式用户明确表达后保存
Procedures团队发布检查清单作为版本化知识,不写成个人偏好
Temporary state当前表单输入、一次性验证码不进入长期 Memory
Sensitive data密码、完整支付信息、私密健康资料默认禁止或走专门合规系统

“用户说过”不等于“应该永久记住”。写入前至少判断:是否明确、是否仍可能变化、是否对未来有用、是否允许保存、保存多久。


Memory 写入闸门

type MemoryCandidate = {
	tenantId: string;
	userId: string;
	kind: 'fact' | 'preference' | 'event' | 'procedure';
	value: string;
	sourceRef: string;
	observedAt: string;
	confidence: number;
	expiresAt?: string;
	sensitivity: 'public' | 'internal' | 'personal' | 'restricted';
};

function mayPersist(candidate: MemoryCandidate) {
	return (
		candidate.confidence >= 0.9 &&
		candidate.sensitivity !== 'restricted' &&
		Boolean(candidate.tenantId && candidate.userId && candidate.sourceRef)
	);
}

真实系统还应加入用户同意、地区保留政策、数据删除请求和字段级加密。模型可以提出 Memory candidate,但代码与政策层决定是否写入。


检索不是只做向量相似度

一次安全检索至少包含:

当前任务
  → tenant/user/region 硬过滤
  → 按 kind、时间与权限过滤
  → 语义或关键词召回
  → 去重与冲突检测
  → relevance + recency + confidence 重排
  → token budget 截断
  → 带 sourceRef 注入工作集
const memories = await memoryStore.search({
	tenantId: run.tenantId,
	userId: run.userId,
	query: run.objective,
	kinds: ['fact', 'preference'],
	notExpiredAt: new Date().toISOString(),
	limit: 12
});

const workingSet = rerank(memories)
	.filter(item => item.score >= retrievalThreshold)
	.slice(0, maxMemoryItems)
	.map(({ value, sourceRef, observedAt }) => ({ value, sourceRef, observedAt }));

Tenant 与用户过滤必须在数据库查询阶段完成,不能先跨租户召回,再让模型判断哪些内容可见。


冲突、过期与更正

Memory 不是不可变真理。假设用户先说“我住在 Brisbane”,后来明确说“我已经搬到 Sydney”,系统应保留来源和时间,并把旧事实标为 superseded。

{
	"memoryId": "mem_204",
	"key": "home_city",
	"value": "Sydney",
	"observedAt": "2026-08-25T11:10:00+10:00",
	"sourceRef": "message_991",
	"status": "active",
	"supersedes": "mem_102"
}

冲突无法自动解决时,向用户确认,不要把两个值平均或随机选择。涉及价格、法律状态、职位、考试规则等易变事实,应优先读取权威来源,而不是依赖旧 Memory。


压缩历史时保留什么

好的 compaction 不是“把聊天缩短”,而是生成可恢复状态:

## Objective

Deploy checkout-api after regression tests pass.

## Confirmed facts

-   Current branch: release/checkout-42 [source: git]
-   Payment E2E is failing at 3DS callback [source: run_87]

## Decisions

-   Do not bypass the 3DS test.
-   Use provider sandbox account only.

## Completed

-   Unit tests passed at commit 1a2b3c4.

## Open work

-   Fix callback signature verification.

## External actions

-   No deployment has occurred.

删除闲聊、重复日志和已经失去决策价值的中间输出;保留目标、硬约束、证据、外部副作用和下一步。


防 Prompt Injection 持久化

网页、邮件和工具结果都是不可信数据。攻击文本如果被摘要进长期 Memory,会在未来任务里重复生效。

风险防线
文档要求忽略系统指令把检索内容标为数据,不赋予指令权限
恶意文本被写入 MemoryMemory candidate 只提取事实,不复制命令
跨租户泄漏查询阶段强制 tenant filter
敏感字段进入 trace写入前字段级 redact
旧事实覆盖新事实时间、来源、状态和 supersedes 链

不要让模型自行决定“这段文本安全,所以永久保存”。安全分类与保留必须有确定性代码闸门。


如何评测 Context 与 Memory

Eval检查方法
Retrieval precision注入的 Memory 中有多少真正帮助当前任务
Retrieval recall必要事实是否被取回
Context efficiency每个成功任务消耗多少输入 token
Faithfulness回答是否忠于 sourceRef,而非补写细节
Update correctness新事实是否正确替代旧事实
Isolation其他 tenant/user 的内容是否始终为零
Deletion用户删除后是否无法再次检索
Topic switch换任务后旧工作集是否被清理

测试集要覆盖同名用户、冲突偏好、过期事实、恶意文档、空检索和超长工具输出,而不只是“记住我喜欢咖啡”的演示。


常见错误

错误结果修法
保存整段对话成本高,敏感信息和注入一起持久化只保存结构化 candidate
只用向量 top-k召回相似但无权限或已过期的内容先硬过滤,再召回和重排
没有来源和时间无法判断冲突与新旧强制 sourceRefobservedAt
把 Local context 全送给模型密钥和内部依赖暴露代码层与 LLM-visible context 分离
压缩时只留结论新 session 无法验证保留证据 ID 和外部状态

动手练习:跨会话项目助手

  1. 定义事实、偏好、事件三种 Memory schema。
  2. 设计写入闸门,拒绝密码、一次性验证码和未确认推断。
  3. 加入 tenant、user、过期时间和 sourceRef 过滤。
  4. 模拟用户城市变化,验证新事实 supersede 旧事实。
  5. 在检索文档中加入恶意指令,确认它不会进入长期 Memory。
  6. 记录答案质量、输入 token、召回精度和跨用户泄漏率。

完成标准

  • 我能区分 Local state、LLM context、Working memory 和 Long-term memory。
  • Memory 写入受代码与政策控制,不由模型单独决定。
  • 检索先做 tenant/user 硬过滤。
  • 每条长期事实都有来源、时间、状态和删除路径。
  • Compaction 保留目标、决策、证据和外部副作用。

相关阅读

官方参考

📚 相关资源

常见问题

点击问题,查看本章对应的实践答案。

history 越塞越多 token 爆炸,怎么管理?

三种策略组合用:(1) sliding window 只留最近 N 轮;(2) summarization 把更早的压成 bullets,保留 IDs/time;(3) topical caches 按话题存摘要,话题切换时换入换出。话题切换还要触发 reset:重发核心指令、丢弃陈旧历史。目标 context 占 ≤ 60-70% 模型上限,给输出留 1/3。

system / user / history / tools 该怎么排顺序?

Instruction hierarchy:(1) System 装不可妥协的规则(角色、语言、安全),最高优先级;(2) Task/User 是当轮请求和约束;(3) History 只放必需轮次,更早内容先 summarize;(4) Tools 装 function spec 和预期。RAG 里则是 instructions → constraints → retrieved snippets(带 IDs)→ question。关键信息放头或尾,避开中间。

long-term memory(用户偏好、历史事实)怎么存?

短期 memory 是当前对话 + working set;长期 memory 用 vector store 或 key-value store 存 facts/preferences,按 query + tenant/user 检索。Ephemeral memory 自动过期/轮换避免 PII 堆积。事实存成 bullet list 或 key-value 块,不写散文;每条带 ID 便于引用追溯;数字/日期用统一单位和格式。

memory 系统怎么防 prompt injection?

三道闸:(1) 摘要时把用户提供的 prompt 片段剥离,避免注入持久化进 memory;(2) 写/读前对 secrets 和 PII 脱敏;(3) 数据按 tenant/user/region 打 tag,retrieval 时强过滤。production 还要做 token audit(典型 + 峰值场景下 context 大小)和 regression check(核心 instructions 在 packing 后还在)。

怎么验证 context packing 没把核心指令挤掉?

Minimal checklist:(1) instruction hierarchy 强制执行,核心规则每次都进;(2) history 已 trim/summarize 带 IDs,预算 ≤ 70% 模型上限;(3) retrieval snippets 已去重、带引用、按 tenant 过滤。再做 topic-switch 测试:故意切话题,验证 summary 和 reset 行为。每次 release 跑一遍 regression 不漏。