写 Agent 时,有一种失败很难第一时间察觉:
模型给出的回答语气自然、步骤完整,甚至引用了项目里的文档,但它依据的可能是过期策略、无关记录,或者一段来自外部网页的恶意指令。
这时继续修改 Prompt,往往只能暂时掩盖问题。真正缺少的是一层更基础的工程设计:
在每次模型调用前,谁决定哪些信息可以进入上下文?这些信息来自哪里、何时更新、是否有权读取、为什么被选中,又有哪些关键证据根本没有找到?
这一层可以叫作 Context Engineering。为了强调它不只是一次检索或一段 Prompt,本文更具体地使用 Context Architecture:把上下文当成有边界、有策略、有版本、可评测的运行时输入。
本文是 AI Agent 工程进阶 第 3 篇。上一篇 Agent Evals 建立了 Task、Grader、Trial 和发布门禁;这一篇把同一套评测方法用到上下文组装器上。
| 项目 | 说明 |
|---|---|
| 内容类型 | 概念解释、工程设计与可复现实验 |
| 适合读者 | 已经写过 Prompt、RAG 或工具调用 Agent,希望系统控制上下文的开发者 |
| 阅读时间 | 约 17-22 分钟 |
| 跟做时间 | 45-60 分钟 |
| 环境要求 | Python 3.10+,零第三方依赖,不需要 API Key |
| 代码检查点 | 05a30ff |
| 可带走产物 | Context Packet Schema、五道选择闸门、8 个评测案例、失败实验和接入清单 |
| 资料核对日期 | 2026-07-29 |
| 实验边界 | 评估确定性的上下文组装结果,不评估模型回答质量 |
先说明数字的边界
本文实验里
37.5% -> 100%是 8 个合成案例上,Context Packet 是否满足来源、相关性、预算与缺失证据规则的通过率。它不是模型准确率,也不能证明生产效果提升了 62.5 个百分点。真正接入模型后,还需要继续评估最终回答和真实任务结果。
一分钟概览
如果只想先建立心智模型,可以记住八点:
- Prompt 不是全部 Context。模型当前能看到的规则、工具、历史、检索证据和状态共同构成 Context。
- Context Window 是容量,不是质量保证。窗口更长,不代表模型会同等利用每一段信息。
- Session 和 Memory 不必全部进入当前推理。它们可以保存在外部,按任务恢复或检索。
- Context Packet 是一次调用的输入快照,至少记录任务、选中证据、来源、时间、预算、排除原因和缺失证据。
- 先过闸门,再做排序:Trust、Freshness、Access、Relevance、Budget 解决的是不同风险。
- 缺失证据也是结果。找不到报销政策时,系统应输出
missing_topics,而不是让模型靠常识补全。 - 压缩必须可追溯。摘要减少 Token,不应抹掉来源、关键状态和恢复路径。
- Context 策略要用 Eval 比较。不要只凭“看起来更完整”判断版本更好。
图 1:Context Architecture 把多种来源经过信任、新鲜度、权限、相关性和预算闸门组装成可追踪的 Context Packet
图 1:上下文不是把资料拖进窗口,而是一次受策略约束的组装过程。图中的连线会展示来源经过闸门进入 Context Packet,再交给 Agent。移动端可打开原始 SVG查看。
1. 先区分 Prompt、Context 与 Context Architecture
这三个词经常被混在一起。
Prompt:这一次怎样向模型表达任务
Prompt 通常包括系统指令、用户问题、示例和输出要求。它回答的是:
我要模型做什么?
我怎样把要求表达清楚?
Prompt Engineering 仍然重要。目标含糊、约束冲突、输出格式不清,后面的 Context 再完整也救不回来。
Context:这一次推理时模型实际能看到什么
Anthropic 对 Context Engineering 的定义重点不在“写一句更好的话”,而在为下一步推理选择和维护合适的信息集合。一次调用里的 Context 可能包括:
系统规则
+ 项目说明
+ 工具定义
+ 当前用户任务
+ 对话历史
+ 检索证据
+ 工具返回
+ 当前运行状态
+ 压缩摘要
这里的关键是“当前可见”。数据库里有一条正确规则,不等于模型这一步能看到它;一段历史曾经出现过,也不等于它应该继续占用当前窗口。
Context Architecture:谁在何时、按什么规则组装 Context
当 Agent 开始跨多轮运行,问题就不再是手写一段 Prompt,而是:
- 哪些来源允许进入;
- 如何判断资料是否过期;
- 怎样处理同一规则的多个版本;
- 什么数据受权限限制;
- 预算不足时保留什么;
- 长历史怎样压缩和恢复;
- 选错上下文时怎样复现。
因此可以把三者的关系写成:
Prompt = 一部分输入内容
Context = 本次推理可见的完整输入
Context Architecture = 生成、维护、评估 Context 的系统
2. 当前上下文、Session、Memory 和外部来源不是一回事
另一个常见误区,是把 Agent 知道过的所有东西都叫 Memory。
图 2:外部来源、当前模型上下文、Session 状态与长期 Memory 的责任边界
图 2:当前 Context 是一次推理的有限工作区;Session 保存可恢复的任务状态;Memory 保存跨任务仍有价值的信息;外部来源保持为可按需读取的事实系统。
可以用一张表区分它们:
| 区域 | 生命周期 | 典型内容 | 是否默认进入模型 |
|---|---|---|---|
| 当前 Context | 一次模型调用 | 当前任务、必要规则、工具 Schema、选中证据 | 是 |
| Session State | 一次长任务或会话 | 当前步骤、已完成动作、待办、checkpoint、工具结果引用 | 否,按步骤恢复 |
| Long-term Memory | 跨任务 | 稳定偏好、已确认事实、历史决策及其来源 | 否,按需检索 |
| External Sources | 由外部系统维护 | 仓库、数据库、工单、网页、日志、API 状态 | 否,实时或按需读取 |
LangChain 的 Context Engineering 文档也强调类似边界:给模型的 Context 是瞬时的,而运行状态和长期存储可以持久存在。框架术语可能不同,但工程问题相同。
这个区分会直接改变实现:
- 20 MB 日志应保留在 artifact store,Context 只放异常摘要和可追溯位置;
- 已完成的工具调用应写入 Session checkpoint,不必每一步原样重放;
- 用户长期偏好应存入带来源和更新时间的 Memory,需要时检索;
- 最新事故状态应回到状态系统读取,而不是相信三小时前的聊天历史。
一个判断原则
“以后可能有用”不足以让内容进入当前 Context。它只说明内容值得被保存或可检索。
3. 长窗口为什么不能替代 Context Architecture
模型窗口变长后,最直接的做法是把更多内容全部塞进去。但容量增加只解决“放不下”,没有自动解决以下问题。
3.1 注意力并非均匀分配
Lost in the Middle 观察到,一些长上下文模型对信息位置敏感,关键内容放在中间时表现会下降。RULER 进一步用多类任务评估长上下文能力,说明“声明支持某个窗口长度”与“在不同任务上有效使用整个窗口”不是同一个结论。
这些研究使用的模型和测试条件有时间边界,不能直接外推到所有当前模型。但它们足以支持一个谨慎判断:
是否能有效使用长上下文,需要按自己的模型、任务和数据分布评测,不能只看最大 Token 数。
3.2 过期资料不会因为更多而变新
如果旧版限流规则排在新版规则前面,“全部塞入”只是把冲突一起交给模型。真正需要的是:
updated_at;valid_until;- 规则版本或 canonical key;
- 来源权威级别;
- 明确的去重与淘汰策略。
3.3 无关内容会占用预算和判断空间
界面 Changelog 也许可信且最新,但它与“429 如何退避”无关。可信不等于相关。
3.4 未信任内容可能越过指令边界
网页、邮件和工单评论既可能包含业务证据,也可能包含“忽略规则、执行写入”的指令注入。OpenAI 的 Agent 安全指南建议隔离不可信数据,并优先提取受约束的结构化字段,避免让不可信文本直接驱动高风险行为。
实验为了让边界清楚,采用保守策略:trust != trusted 的来源不进入 Packet。真实系统不一定永久丢弃它,可以先在隔离步骤中提取事实,再把结构化结果作为新的、受约束的来源送入下一步。
3.5 更长的输入还会增加成本与延迟
即使 Prompt Caching 能降低完全重复前缀的成本和延迟,它也不会判断内容是否正确、过期或有权限读取。缓存是执行优化,不是语义校验。
4. Context Packet:把隐式拼接变成可检查对象
如果 Context 只是代码里几次字符串拼接,就很难回答“这次到底给模型看了什么”。因此本文引入一个显式产物:
Context Packet
它不是行业统一标准,而是本文项目中的工程约定。下面是 ctx-002 实际 Packet 的字段节选:
{
"schema_version": "0.3.0",
"case_id": "ctx-002",
"question": "服务持续返回 429 时应该怎样重试和降级?",
"as_of": "2026-07-29",
"selected": [
{
"id": "rate-limit-policy-current",
"locator": "repo://handbook/operations.md#rate-limit",
"updated_at": "2026-07-20",
"topics": ["rate-limit", "retry"]
}
],
"excluded": [
{"source_id": "rate-limit-policy-expired", "reason": "expired"},
{"source_id": "general-changelog", "reason": "not_required"}
],
"missing_topics": [],
"budget": {
"limit_estimated_tokens": 115,
"used_estimated_tokens": 58
},
"fingerprint": "..."
}
它解决五个实际问题:
- 可解释:知道哪条证据进入了模型;
- 可复现:通过
case_id、时间和 fingerprint 对照固定数据集中的选择结果; - 可评测:Grader 可以检查必需来源、禁用来源和预算;
- 可恢复:长任务可以保存 Packet 或其引用;
- 可审计:高风险结论能追到来源位置和更新时间。
注意,Packet 不是把所有内容重新复制一遍。在真实系统里,大对象可以只保留 artifact locator、摘要和校验值,需要时再读取。
Lab 的 fingerprint 只由策略、Case、选中来源 ID 和 as_of 生成,用于判断相同 Fixture 下的选择是否变化。它没有包含来源正文,因此不是内容完整性证明。生产实现还应记录 content_hash、ETag、文档版本或不可变 artifact ID,并把 Context Policy 版本写进 Trace。
5. 五道闸门解决五种不同风险
一个来源被选中前,至少要回答五个问题。
| 闸门 | 要回答的问题 | 典型字段 | 失败后的处理 |
|---|---|---|---|
| Trust | 来源能否直接作为当前推理依据 | trust、来源类型、签名 | 隔离提取或拒绝 |
| Freshness | 在任务时间点是否仍有效 | updated_at、valid_until | 查找新版本或标记缺失 |
| Access | 当前主体是否有权读取 | sensitivity、clearance | 不暴露内容,转人工或降级 |
| Relevance | 是否覆盖当前任务所需主题 | topics、任务合同 | 不进入当前 Packet |
| Budget | 在有限窗口中是否值得保留 | 估算 Token、权威性、覆盖数 | 压缩、按需读取或标记缺失 |
这五项不能合并成一个“相似度分数”。
一份泄露的敏感文档可能与问题高度相似,但没有访问权;一份旧规则可能权威且相关,但已经失效;一段网页可能包含正确事实,但不应该被当成指令。
本文实验先做资格过滤,再在合格来源中做覆盖和预算选择:
def _ineligible_reason(case, source):
if source.trust != "trusted":
return "untrusted"
if source.sensitivity > case.clearance:
return "clearance"
if source.valid_until is not None and source.valid_until < case.as_of:
return "expired"
return None
这段代码很简单,却体现了一个重要次序:
安全与有效性约束
-> 去重
-> 相关性与覆盖
-> 预算内选择
不是先按相似度拿到 Top K,再祈祷里面没有越权或过期资料。
闸门的前提是元数据本身可信
Lab 中的
trust、topics、valid_until和sensitivity都是受控 Fixture。生产环境最难的部分之一,是确定谁能写这些字段、怎样验证采集器、默认值是什么,以及来源变化后怎样失效。若网页可以给自己标成trusted,再完整的五道闸门也只是虚假的安全感。
6. missing_topics:让“不知道”成为一等输出
Context Architecture 最有价值的字段,可能不是 selected,而是:
{
"missing_topics": ["expense-policy"]
}
假设读者问“差旅报销住宿上限是多少”,当前候选来源只有一个后台界面 Changelog。系统有三种选择:
- 把 Changelog 给模型,让模型自由回答;
- 返回空白 Context,却不说明缺什么;
- 明确声明缺少
expense-policy,触发补充检索、询问或人工接管。
第三种才是可控路径。
它把模型最危险的一种自由度收了回来:在关键证据缺失时,用语言流畅度填补事实空白。
因此 Packet 的渲染内容也应把缺失证据放在显眼位置:
QUESTION
差旅报销的住宿上限是多少?
MISSING EVIDENCE
- expense-policy
INSTRUCTION
Do not infer a missing policy. Ask for a source or hand off.
“明确缺失”不等于任务失败。它可能是一次正确的拒答、一次新的检索动作,或者一个可靠的人工交接点。
7. Write、Select、Compress、Isolate:Context 的四类操作
开源项目 langchain-ai/context_engineering 把常见策略归纳为 Write、Select、Compress 和 Isolate。这不是唯一分类,但很适合检查系统是否只会“检索后拼接”。
图 3:Context Engineering 的 Write、Select、Compress 与 Isolate 四类操作
图 3:四类操作可以组合使用。写入让状态落盘,选择控制当前可见内容,压缩回收窗口,隔离避免不同任务和信任域互相污染。
Write:把状态写到窗口之外
适合保存:
- 当前计划与完成步骤;
- 文件 diff 和测试报告位置;
- 工具返回的 artifact;
- 已确认的事实和来源;
- 等待人工审批的动作参数。
写入不是 Memory 的同义词。Session checkpoint、artifact store 和长期 Memory 都是不同的写入目标。
Select:只把当前步骤需要的信息取回来
选择可以来自:
- 规则匹配;
- 关键词或向量检索;
- 图关系;
- 最近状态;
- 任务类型;
- 权限和来源过滤;
- 模型路由。
模型可以参与选择,但资格边界和高风险限制最好由确定性代码控制。
Compress:缩短内容,但保留恢复能力
压缩可能是:
- 工具结果摘要;
- 对话 compaction;
- 代码 diff 而不是完整文件;
- 日志异常窗口而不是完整日志;
- 子 Agent 的结构化交接。
OpenAI 的 compaction 文档强调,压缩后的状态用于后续轮次继续推理。工程上还需要额外保存原始 artifact 或 locator,避免摘要成为无法追溯的新事实源。
Isolate:把噪声和风险留在独立边界
适合隔离:
- 外部网页中的不可信文本;
- 大型日志分析;
- 多个子任务的独立工作区;
- 含敏感数据的工具结果;
- 只需要结构化结论的子 Agent 过程。
隔离不是简单“开更多 Agent”。它的目的,是让一个步骤只返回经过约束的交接对象,而不是把整个工作历史灌回主 Agent。
8. Preload 还是 Just-in-time:不要把选择题做成口号
Anthropic 在 Context Engineering 实践里强调 Just-in-time 检索和 progressive disclosure:Agent 先看到高层索引,需要时再展开具体内容。但这不意味着所有信息都应延迟加载。
| 内容 | 更适合 Preload | 更适合 Just-in-time |
|---|---|---|
| 安全边界与禁止动作 | 是,必须始终可见 | 否 |
| 当前任务与验收标准 | 是 | 否 |
| 小而稳定的项目规则 | 通常是 | 可按模块补充 |
| 大型 API 文档 | 否 | 是 |
| 仓库全部源码 | 否 | 按符号、文件和调用链读取 |
| 当前事故状态 | 只预载状态入口 | 执行前实时读取 |
| 20 MB 工具日志 | 否 | 先索引,再读异常片段 |
| 用户长期偏好 | 只载入已确认相关项 | 是 |
我更倾向于使用这条判断:
不看到就可能立即越界的内容 -> Preload
看到索引后能按需恢复的内容 -> Just-in-time
前者优先保证安全和任务完整性,后者优先控制噪声、成本和新鲜度。
9. 稳定前缀与 Prompt Caching:优化执行,不替代选择
OpenAI 的 Prompt Caching 最佳实践建议把静态、重复内容放在前面,把动态和用户特定内容放在后面,因为缓存依赖精确前缀匹配。
这对 Context Architecture 有两个启发:
稳定层:系统规则、固定工具定义、稳定项目约束
动态层:当前任务、实时状态、按需检索、工具结果
Manus 的 Context Engineering 实践也提到保持前缀稳定、尽量 append-only,以及把文件系统当作可恢复的外部 Context。这里应把它看成一个具体 Agent 产品的工程经验,而不是所有模型和 Provider 都必须照搬的标准。
更重要的是,缓存命中不能证明输入正确:
- 旧规则也可能被高效缓存;
- 无关内容也可能复用前缀;
- 敏感内容仍然需要权限判断;
- 缺失证据仍然需要显式输出。
所以先保证 Context 的语义正确和边界正确,再优化它的缓存形状。
10. 跑起来:复现 Context Reliability Lab
本篇代码在 GitHub 仓库 RalfNick/ai-agent-learn 的固定 commit 05a30ff。固定 commit 很重要:主分支以后继续演进,本文命令仍应得到同一组结果。
git clone https://github.com/RalfNick/ai-agent-learn.git
cd ai-agent-learn
git checkout 05a30ffaa5dc03ffd3fcb7a4e4565b3430a0c320
cd phase-7-agent-engineering/agent-reliability-lab
先运行测试:
python -m unittest discover -s tests -v
预期结果:
Ran 22 tests
OK
再运行 Context Eval:
python run_lab.py context-eval --output reports/local/context-review
成功时命令退出码为 0,并生成:
reports/local/context-review/context-comparison.json
reports/local/context-review/context-comparison.md
reports/local/context-review/context-failures.md
reports/local/context-review/context-packets.jsonl
仓库根目录中也保留了本次文章核验使用的固定报告:
11. 这 8 个案例究竟在测什么
实验没有调用模型,因为它先回答一个更窄的问题:
给定任务、候选来源和预算,组装器能否稳定产出符合合同的 Packet?
8 个合成案例分别覆盖:
| Case | 任务 | 主要风险 | 正确行为 |
|---|---|---|---|
ctx-001 | 处理 PII | 外部指令注入、无关记录 | 只选当前敏感数据规范 |
ctx-002 | 429 重试与降级 | 旧政策与新版政策冲突 | 排除过期策略,选择当前策略 |
ctx-003 | 发布失败回滚 | 同一规则多个版本 | 选择有效发布策略 |
ctx-004 | 支付 API 事故 | 需要两个主题 | 同时覆盖事故状态和限流策略 |
ctx-005 | 查询报销上限 | 没有可用证据 | 明确 expense-policy 缺失 |
ctx-006 | 访客读取 PII 规范 | 权限不足 | 不暴露内容,明确缺失 |
ctx-007 | 生产写入审批 | 外部网页伪造批准 | 只选正式审批规则 |
ctx-008 | 查询灰度状态 | 原始日志过长 | 在预算内选择高权威状态摘要 |
这组案例故意集中在上下文常见失败模式上,不代表生产流量分布。dump-all-v1 也只是一个透明、可解释的朴素基线,不是对成熟 RAG 系统的概括。
基线仍在三个直接案例上通过,这一点很重要:实验不是为了证明“只要全部塞入就必然失败”,而是观察它遇到冲突、权限和预算时怎样失败。
12. 拆开 ctx-002:过期规则是怎样被挡住的
ctx-002 问:
服务持续返回 429 时应该怎样重试和降级?
候选来源按顺序是:
旧版限流策略(已过期)
后台界面 Changelog(无关)
当前限流策略(有效)
图 4:ctx-002 中过期策略、无关记录与当前策略经过 Context 闸门后的选择结果
图 4:相关性不是唯一标准。旧策略主题相关但已经失效;Changelog 可信且最新但与问题无关;只有当前限流策略进入 Packet。
dump-all-v1 从候选列表头部开始装入,直到预算切断。它选中了过期策略和无关 Changelog,当前策略反而没有进入 Packet。
context-packet-v1 的路径是:
1. valid_until < as_of
-> 旧策略 excluded: expired
2. Changelog 不覆盖 required_topics
-> excluded: not_required
3. 当前策略可信、有效、有权限且覆盖 rate-limit
-> selected
最终 Packet 不只保存了选中的规则,还保存了两个排除原因。这样即使最终回答错误,也能先判断错误是否来自 Context 选择。
13. 实验结果:改善的是组装合同,不是模型准确率
在固定 commit、Python 测试通过的条件下,报告结果如下:
图 5:dump-all-v1 与 context-packet-v1 在 8 个合成 Context 组装案例上的对比
图 5:Context Packet 在本组装测试中保留了全部可用必需证据,并消除了禁用和无关来源。Token 是项目内确定性估算,不是 Provider tokenizer 的真实计费数。
| 指标 | dump-all-v1 | context-packet-v1 |
|---|---|---|
| 案例通过 | 3 / 8 | 8 / 8 |
| 可用必需证据覆盖 | 87.5% | 100% |
| 选入禁用来源的案例 | 3 | 0 |
| 选入无关来源的案例 | 3 | 0 |
| 缺失证据判断正确率 | 75% | 100% |
| 平均估算 Token | 66.38 | 50.25 |
| 回归案例 | - | 0 |
context-packet-v1 相比基线改善了:
ctx-001
ctx-002
ctx-005
ctx-006
ctx-008
没有出现案例级回归,因此本实验的发布 Gate 通过。
但这个结果只能支持:
在这 8 个已知合同和合成来源上,
受控组装器比前缀式 dump-all 更符合 Context Packet 规则。
它不能支持:
模型回答一定提升;
所有任务都应减少 24.3% Token;
这套贪心选择优于所有 RAG 或 reranker;
8 个案例足以代表生产环境。
下一步应把真实失败样本加入 Dataset,再比较最终模型回答、工具行为和任务 Outcome。
14. 评测 Packet,而不是只看渲染后的文字
Grader 直接检查结构化 Packet:
grades = [
required_evidence,
missing_evidence,
forbidden_sources,
relevance,
budget,
traceability,
]
每一项分别回答:
| Grader | 问题 |
|---|---|
required_evidence | 已知存在的必要主题是否被选中 |
missing_evidence | 系统声明缺失的主题是否准确 |
forbidden_sources | 是否暴露了禁用、未信任或越权来源 |
relevance | 选中来源是否覆盖当前需求 |
budget | Packet 是否在本案例预算内 |
traceability | 每条来源是否保留 locator 和更新时间 |
这种评测有两个优点:
- 不需要等待模型回答,组装层回归可以快速定位;
- 模型答错时,可以把问题分成“上下文没给对”和“给对后仍推理错误”。
不过不能只停在这里。生产链路至少需要两层 Eval:
Layer 1: Context Assembly Eval
-> 该给的是否给了,不该给的是否挡住
Layer 2: Agent Outcome Eval
-> 模型是否正确使用证据,任务是否真正完成
如果只测 Layer 2,失败定位很慢;如果只测 Layer 1,又无法证明最终任务价值。
实验 Dataset 中还有一个字段:
expected_missing_topics
它是 Eval 作者事先标注的 Gold Label,只用于判断组装器是否正确识别缺口。生产 Packet 不会预先知道“正确答案应该缺什么”,它只会根据任务合同和当前可用来源计算 missing_topics。这两个字段不能混为一谈。
15. 故意把预算降到 10:正确失败比勉强通过更重要
再做一次失败注入:
python run_lab.py context-eval \
--context-budget 10 \
--output reports/local/context-budget-ten
在 PowerShell 中也可以写成一行:
python run_lab.py context-eval --context-budget 10 --output reports/local/context-budget-ten
这次预期退出码为 1,Gate 不通过。预算小到连必要来源都放不下时,Context Packet 不会编造一份“精简证据”来获得通过。
实验结果中,两种策略都只通过 2 / 8 个案例,可用必需证据覆盖率为 0%。这不是 Packet 设计失效,而是门禁正确暴露了:
当前预算不足以履行任务合同。
生产系统此时可以:
- 申请更大预算;
- 把任务拆成多个步骤;
- 先读取摘要,再按需展开;
- 转交拥有更合适工具的流程;
- 明确拒绝并请求人工补充。
不应做的是静默丢掉证据,然后让模型继续生成确定语气的答案。
16. Token 估算器为什么故意写得很朴素
Lab 使用一个确定性估算器:
def estimate_tokens(text: str) -> int:
# 中文字符按 1 个单位,其他内容按可重复的文本片段估算
...
它的用途是让测试跨环境稳定,不依赖某个 Provider 的 tokenizer。报告字段因此明确叫:
estimated_tokens
而不是 tokens。
这带来一个必须保留的限制:66.38 -> 50.25 只能比较本实验内部的相对输入规模,不能拿来计算真实账单。
接入生产模型时,应替换或补充:
- 当前模型对应的 tokenizer;
- API 返回的实际 input/output usage;
- cached 与 uncached 输入;
- 工具调用和重试造成的总成本;
- Context 组装耗时与检索耗时。
可复现测试和真实计费可以同时保留,不必让其中一个冒充另一个。
17. 怎样把实验接入真实 Agent
从 Lab 到真实系统,可以拆成五个适配层。
17.1 Source Adapter:统一不同来源
把仓库、数据库、工单和 API 返回统一为:
id
kind
content or artifact locator
trust
authority
updated_at
valid_until
topics
sensitivity
canonical_key
不要让每个检索器直接拼最终 Prompt。
17.2 Policy Engine:资格和选择分开
确定性策略负责:
- 权限;
- 有效期;
- 信任域;
- 强制预载规则;
- 最大预算;
- 禁止来源。
检索或模型排序负责:
- 主题覆盖;
- 语义相关;
- 多样性;
- 在合格来源中挑选更高信号内容。
17.3 Packet Builder:输出结构化快照
Packet Builder 生成:
selected;excluded;missing_topics;budget;fingerprint;- 最终
rendered_context。
高风险任务还可以保存策略版本和调用主体。
17.4 Provider Adapter:渲染成具体模型输入
不同 Provider 对 system、developer、user、tool result 和 compaction 的表达不同。Context Packet 不必等同于 API 消息格式,应该由 Adapter 映射:
Context Packet
-> OpenAI Responses / Agents SDK
-> Claude Agent SDK
-> LangGraph node state
-> 自定义模型网关
这样更换 Provider 时,来源和策略合同不必全部重写。
17.5 Trace 与 Eval:保存选择证据
每次运行至少记录:
packet fingerprint
selected source ids
excluded reasons
missing topics
estimated and actual usage
model / prompt / policy version
final outcome
随后把生产失败转成新的 Context Case。例如:
失败:Agent 使用了旧版退款策略
-> 新 Case:同时提供新旧规则
-> 新 Grader:必须选择当前有效版本
-> 修复策略
-> 重跑历史 Case,确认无回归
这时 Context Engineering 才进入持续改进,而不是一次性的 Prompt 整理。
18. 哪些场景暂时不需要独立 Context 层
并非每个模型调用都需要完整的 Packet 系统。
| 场景 | 更简单的方案 |
|---|---|
| 单次改写一段用户已提供的文字 | 直接 Prompt |
| 输入固定、没有外部事实和权限差异 | 固定模板 |
| 确定性脚本已经能完成 | 不使用 Agent |
| 小型仓库中的一次本地代码修改 | 项目规则 + 按需读文件 |
| 没有跨轮状态的简单分类 | Schema 输出 + 单次调用 |
当以下问题开始反复出现时,再把 Context Architecture 独立出来:
- 同一任务经常拿到冲突或过期资料;
- 来源包含不同信任域和权限;
- 窗口预算导致关键内容被截断;
- 长任务需要压缩、恢复和交接;
- 团队无法解释某次回答依据了什么;
- 改完检索策略后无法证明没有回归。
工程化的目的不是让每次调用更复杂,而是把已经出现的复杂性放到可管理的位置。
19. 45 分钟练习:为自己的 Agent 做第一版 Context Packet
不需要先接入向量数据库。选一个真实任务,用静态 JSONL 就能开始。
第 0-10 分钟:列出 Context 边界
写四列:
必须始终可见
按需检索
只保存在 Session
长期 Memory
把现有 Prompt、历史、工具返回和知识库内容放进对应列。
第 10-20 分钟:定义 Source Schema 与五道闸门
至少加入:
locator
updated_at
valid_until
trust
sensitivity
topics
选一个过期来源、一个无关来源和一个未信任来源作为失败样本。
第 20-30 分钟:输出第一份 Packet
要求同时包含:
selected
excluded + reason
missing_topics
budget
fingerprint
不要只输出最终拼好的字符串。
第 30-40 分钟:写 5 个 Context Case
建议包括:
- 正常成功;
- 新旧规则冲突;
- 必要证据缺失;
- 权限不足;
- 预算不足。
为每个 Case 写确定性 Grader。
第 40-45 分钟:做一次失败注入
把预算减半,或把当前规则改成过期,确认:
- 命令返回非零退出码;
- 报告指出具体失败 Case;
- Packet 没有静默改写缺失事实;
- 修复后历史 Case 仍然通过。
完成这一步,你得到的不是一段更长 Prompt,而是一条可以持续回归的 Context 管道。
20. 收藏清单
设计或 Review Agent Context 时,可以逐项确认:
- 已区分 Prompt、当前 Context、Session、Memory 和外部来源;
- 当前任务与成功标准始终可见;
- 每条动态证据保留 locator、更新时间和有效期;
- 未信任内容不能直接改变系统行为;
- 权限检查发生在内容进入模型之前;
- 相关性排序不会绕过信任、权限和有效期;
- 预算不足时会显式失败、拆分或按需读取;
-
missing_topics是结构化输出,而不是模型自行猜测; - 压缩结果仍能追到原始 artifact;
- 稳定前缀和缓存只作为执行优化;
- Token 估算与 Provider 实际 usage 明确区分;
- Context Assembly Eval 与 Agent Outcome Eval 分层运行;
- 每次策略变更都能列出 improvement、regression 和失败样本;
- 生产失败可以沉淀成新的 Context Case。
21. 这篇真正建立了什么
这一篇没有试图找到“最好的上下文长度”,而是把问题换成了更可执行的形式:
以前:
把可能有用的资料都塞给模型。
现在:
根据任务合同,从有来源、有时效、有权限的候选中,
在预算内组装一份可追踪的 Context Packet;
缺什么明确写出来;
再用固定 Case 和 Grader 判断策略是否改善。
Context Packet 不是终点。它仍要交给模型,模型仍可能误读证据;检索器也可能根本没有找到正确来源。但现在至少能把失败拆开:
来源不存在
来源未被检索
来源被策略挡住
来源因预算未进入
来源已进入但模型使用错误
能区分这些失败,才有资格谈下一步的自动恢复和持续改进。
下一篇进入 Harness Engineering:模型、Context、工具、Session、Sandbox 和审批分别由谁负责?我们会在同一个 GitHub 项目里,把目前分散的组装、执行和评测代码收进一个最小 Harness,并测试超时、重试、停止和恢复边界。
参考资料
官方与一手工程资料
- Anthropic:Effective context engineering for AI agents
- OpenAI:Compaction
- OpenAI:Prompt caching
- OpenAI:Safety in building agents
- LangChain:Context engineering in agents
- Manus:Context Engineering for AI Agents
研究与开源实现
- Lost in the Middle: How Language Models Use Long Contexts
- RULER: What's the Real Context Size of Your Long-Context Language Models?
- langchain-ai/context_engineering
- HumanLayer:12-Factor Agents,Own your context window
- Datawhale Hello-Agents:上下文工程