已发布资料基础官方资料 + 个人实践
·18 分钟AI Agent 工程进阶
文章/AI 与智能体

Context Architecture:为 Agent 组装可验证的上下文

AI Agent 工程进阶第 3 篇:区分 Prompt、当前上下文、Session 与 Memory,设计带来源、新鲜度、权限、预算和缺失证据的 Context Packet,并用可运行实验比较“全部塞入”与受控组装。

文章目录
  1. 一分钟概览
  2. 1. 先区分 Prompt、Context 与 Context Architecture
  3. Prompt:这一次怎样向模型表达任务
  4. Context:这一次推理时模型实际能看到什么
  5. Context Architecture:谁在何时、按什么规则组装 Context
  6. 2. 当前上下文、Session、Memory 和外部来源不是一回事
  7. 3. 长窗口为什么不能替代 Context Architecture
  8. 3.1 注意力并非均匀分配
  9. 3.2 过期资料不会因为更多而变新
  10. 3.3 无关内容会占用预算和判断空间
  11. 3.4 未信任内容可能越过指令边界
  12. 3.5 更长的输入还会增加成本与延迟
  13. 4. Context Packet:把隐式拼接变成可检查对象
  14. 5. 五道闸门解决五种不同风险
  15. 6. missingtopics:让“不知道”成为一等输出
  16. 7. Write、Select、Compress、Isolate:Context 的四类操作
  17. Write:把状态写到窗口之外
  18. Select:只把当前步骤需要的信息取回来
  19. Compress:缩短内容,但保留恢复能力
  20. Isolate:把噪声和风险留在独立边界
  21. 8. Preload 还是 Just-in-time:不要把选择题做成口号
  22. 9. 稳定前缀与 Prompt Caching:优化执行,不替代选择
  23. 10. 跑起来:复现 Context Reliability Lab
  24. 11. 这 8 个案例究竟在测什么
  25. 12. 拆开 ctx-002:过期规则是怎样被挡住的
  26. 13. 实验结果:改善的是组装合同,不是模型准确率
  27. 14. 评测 Packet,而不是只看渲染后的文字
  28. 15. 故意把预算降到 10:正确失败比勉强通过更重要
  29. 16. Token 估算器为什么故意写得很朴素
  30. 17. 怎样把实验接入真实 Agent
  31. 17.1 Source Adapter:统一不同来源
  32. 17.2 Policy Engine:资格和选择分开
  33. 17.3 Packet Builder:输出结构化快照
  34. 17.4 Provider Adapter:渲染成具体模型输入
  35. 17.5 Trace 与 Eval:保存选择证据
  36. 18. 哪些场景暂时不需要独立 Context 层
  37. 19. 45 分钟练习:为自己的 Agent 做第一版 Context Packet
  38. 第 0-10 分钟:列出 Context 边界
  39. 第 10-20 分钟:定义 Source Schema 与五道闸门
  40. 第 20-30 分钟:输出第一份 Packet
  41. 第 30-40 分钟:写 5 个 Context Case
  42. 第 40-45 分钟:做一次失败注入
  43. 20. 收藏清单
  44. 21. 这篇真正建立了什么
  45. 参考资料
  46. 官方与一手工程资料
  47. 研究与开源实现
  48. 本文代码与证据
阅读提要

AI Agent 工程进阶第 3 篇:区分 Prompt、当前上下文、Session 与 Memory,设计带来源、新鲜度、权限、预算和缺失证据的 Context Packet,并用可运行实验比较“全部塞入”与受控组装。

#Agent#Agent Engineering#Context Engineering#Context Architecture#Evals

写 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 个百分点。真正接入模型后,还需要继续评估最终回答和真实任务结果。

一分钟概览

如果只想先建立心智模型,可以记住八点:

  1. Prompt 不是全部 Context。模型当前能看到的规则、工具、历史、检索证据和状态共同构成 Context。
  2. Context Window 是容量,不是质量保证。窗口更长,不代表模型会同等利用每一段信息。
  3. Session 和 Memory 不必全部进入当前推理。它们可以保存在外部,按任务恢复或检索。
  4. Context Packet 是一次调用的输入快照,至少记录任务、选中证据、来源、时间、预算、排除原因和缺失证据。
  5. 先过闸门,再做排序:Trust、Freshness、Access、Relevance、Budget 解决的是不同风险。
  6. 缺失证据也是结果。找不到报销政策时,系统应输出 missing_topics,而不是让模型靠常识补全。
  7. 压缩必须可追溯。摘要减少 Token,不应抹掉来源、关键状态和恢复路径。
  8. Context 策略要用 Eval 比较。不要只凭“看起来更完整”判断版本更好。

图 1:Context Architecture 把多种来源经过信任、新鲜度、权限、相关性和预算闸门组装成可追踪的 Context Packet图 1:Context Architecture 把多种来源经过信任、新鲜度、权限、相关性和预算闸门组装成可追踪的 Context Packet

图 1:上下文不是把资料拖进窗口,而是一次受策略约束的组装过程。图中的连线会展示来源经过闸门进入 Context Packet,再交给 Agent。移动端可打开原始 SVG查看。

1. 先区分 Prompt、Context 与 Context Architecture

这三个词经常被混在一起。

Prompt:这一次怎样向模型表达任务

Prompt 通常包括系统指令、用户问题、示例和输出要求。它回答的是:

text
我要模型做什么?
我怎样把要求表达清楚?

Prompt Engineering 仍然重要。目标含糊、约束冲突、输出格式不清,后面的 Context 再完整也救不回来。

Context:这一次推理时模型实际能看到什么

Anthropic 对 Context Engineering 的定义重点不在“写一句更好的话”,而在为下一步推理选择和维护合适的信息集合。一次调用里的 Context 可能包括:

text
系统规则
+ 项目说明
+ 工具定义
+ 当前用户任务
+ 对话历史
+ 检索证据
+ 工具返回
+ 当前运行状态
+ 压缩摘要

这里的关键是“当前可见”。数据库里有一条正确规则,不等于模型这一步能看到它;一段历史曾经出现过,也不等于它应该继续占用当前窗口。

Context Architecture:谁在何时、按什么规则组装 Context

当 Agent 开始跨多轮运行,问题就不再是手写一段 Prompt,而是:

  • 哪些来源允许进入;
  • 如何判断资料是否过期;
  • 怎样处理同一规则的多个版本;
  • 什么数据受权限限制;
  • 预算不足时保留什么;
  • 长历史怎样压缩和恢复;
  • 选错上下文时怎样复现。

因此可以把三者的关系写成:

text
Prompt = 一部分输入内容
Context = 本次推理可见的完整输入
Context Architecture = 生成、维护、评估 Context 的系统

2. 当前上下文、Session、Memory 和外部来源不是一回事

另一个常见误区,是把 Agent 知道过的所有东西都叫 Memory。

图 2:外部来源、当前模型上下文、Session 状态与长期 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 只是代码里几次字符串拼接,就很难回答“这次到底给模型看了什么”。因此本文引入一个显式产物:

text
Context Packet

它不是行业统一标准,而是本文项目中的工程约定。下面是 ctx-002 实际 Packet 的字段节选:

json
{
  "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": "..."
}

它解决五个实际问题:

  1. 可解释:知道哪条证据进入了模型;
  2. 可复现:通过 case_id、时间和 fingerprint 对照固定数据集中的选择结果;
  3. 可评测:Grader 可以检查必需来源、禁用来源和预算;
  4. 可恢复:长任务可以保存 Packet 或其引用;
  5. 可审计:高风险结论能追到来源位置和更新时间。

注意,Packet 不是把所有内容重新复制一遍。在真实系统里,大对象可以只保留 artifact locator、摘要和校验值,需要时再读取。

Lab 的 fingerprint 只由策略、Case、选中来源 ID 和 as_of 生成,用于判断相同 Fixture 下的选择是否变化。它没有包含来源正文,因此不是内容完整性证明。生产实现还应记录 content_hash、ETag、文档版本或不可变 artifact ID,并把 Context Policy 版本写进 Trace。

5. 五道闸门解决五种不同风险

一个来源被选中前,至少要回答五个问题。

闸门要回答的问题典型字段失败后的处理
Trust来源能否直接作为当前推理依据trust、来源类型、签名隔离提取或拒绝
Freshness在任务时间点是否仍有效updated_atvalid_until查找新版本或标记缺失
Access当前主体是否有权读取sensitivityclearance不暴露内容,转人工或降级
Relevance是否覆盖当前任务所需主题topics、任务合同不进入当前 Packet
Budget在有限窗口中是否值得保留估算 Token、权威性、覆盖数压缩、按需读取或标记缺失

这五项不能合并成一个“相似度分数”。

一份泄露的敏感文档可能与问题高度相似,但没有访问权;一份旧规则可能权威且相关,但已经失效;一段网页可能包含正确事实,但不应该被当成指令。

本文实验先做资格过滤,再在合格来源中做覆盖和预算选择:

python
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

这段代码很简单,却体现了一个重要次序:

text
安全与有效性约束
  -> 去重
  -> 相关性与覆盖
  -> 预算内选择

不是先按相似度拿到 Top K,再祈祷里面没有越权或过期资料。

闸门的前提是元数据本身可信

Lab 中的 trusttopicsvalid_untilsensitivity 都是受控 Fixture。生产环境最难的部分之一,是确定谁能写这些字段、怎样验证采集器、默认值是什么,以及来源变化后怎样失效。若网页可以给自己标成 trusted,再完整的五道闸门也只是虚假的安全感。

6. missing_topics:让“不知道”成为一等输出

Context Architecture 最有价值的字段,可能不是 selected,而是:

json
{
  "missing_topics": ["expense-policy"]
}

假设读者问“差旅报销住宿上限是多少”,当前候选来源只有一个后台界面 Changelog。系统有三种选择:

  1. 把 Changelog 给模型,让模型自由回答;
  2. 返回空白 Context,却不说明缺什么;
  3. 明确声明缺少 expense-policy,触发补充检索、询问或人工接管。

第三种才是可控路径。

它把模型最危险的一种自由度收了回来:在关键证据缺失时,用语言流畅度填补事实空白。

因此 Packet 的渲染内容也应把缺失证据放在显眼位置:

text
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: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 工具日志先索引,再读异常片段
用户长期偏好只载入已确认相关项

我更倾向于使用这条判断:

text
不看到就可能立即越界的内容 -> Preload
看到索引后能按需恢复的内容 -> Just-in-time

前者优先保证安全和任务完整性,后者优先控制噪声、成本和新鲜度。

9. 稳定前缀与 Prompt Caching:优化执行,不替代选择

OpenAI 的 Prompt Caching 最佳实践建议把静态、重复内容放在前面,把动态和用户特定内容放在后面,因为缓存依赖精确前缀匹配。

这对 Context Architecture 有两个启发:

text
稳定层:系统规则、固定工具定义、稳定项目约束
动态层:当前任务、实时状态、按需检索、工具结果

Manus 的 Context Engineering 实践也提到保持前缀稳定、尽量 append-only,以及把文件系统当作可恢复的外部 Context。这里应把它看成一个具体 Agent 产品的工程经验,而不是所有模型和 Provider 都必须照搬的标准。

更重要的是,缓存命中不能证明输入正确:

  • 旧规则也可能被高效缓存;
  • 无关内容也可能复用前缀;
  • 敏感内容仍然需要权限判断;
  • 缺失证据仍然需要显式输出。

所以先保证 Context 的语义正确和边界正确,再优化它的缓存形状。

10. 跑起来:复现 Context Reliability Lab

本篇代码在 GitHub 仓库 RalfNick/ai-agent-learn 的固定 commit 05a30ff。固定 commit 很重要:主分支以后继续演进,本文命令仍应得到同一组结果。

bash
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

先运行测试:

bash
python -m unittest discover -s tests -v

预期结果:

text
Ran 22 tests
OK

再运行 Context Eval:

bash
python run_lab.py context-eval --output reports/local/context-review

成功时命令退出码为 0,并生成:

text
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-002429 重试与降级旧政策与新版政策冲突排除过期策略,选择当前策略
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 问:

text
服务持续返回 429 时应该怎样重试和降级?

候选来源按顺序是:

text
旧版限流策略(已过期)
后台界面 Changelog(无关)
当前限流策略(有效)

图 4:ctx-002 中过期策略、无关记录与当前策略经过 Context 闸门后的选择结果图 4:ctx-002 中过期策略、无关记录与当前策略经过 Context 闸门后的选择结果

图 4:相关性不是唯一标准。旧策略主题相关但已经失效;Changelog 可信且最新但与问题无关;只有当前限流策略进入 Packet。

dump-all-v1 从候选列表头部开始装入,直到预算切断。它选中了过期策略和无关 Changelog,当前策略反而没有进入 Packet。

context-packet-v1 的路径是:

text
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:dump-all-v1 与 context-packet-v1 在 8 个合成 Context 组装案例上的对比

图 5:Context Packet 在本组装测试中保留了全部可用必需证据,并消除了禁用和无关来源。Token 是项目内确定性估算,不是 Provider tokenizer 的真实计费数。

指标dump-all-v1context-packet-v1
案例通过3 / 88 / 8
可用必需证据覆盖87.5%100%
选入禁用来源的案例30
选入无关来源的案例30
缺失证据判断正确率75%100%
平均估算 Token66.3850.25
回归案例-0

context-packet-v1 相比基线改善了:

text
ctx-001
ctx-002
ctx-005
ctx-006
ctx-008

没有出现案例级回归,因此本实验的发布 Gate 通过。

但这个结果只能支持:

text
在这 8 个已知合同和合成来源上,
受控组装器比前缀式 dump-all 更符合 Context Packet 规则。

它不能支持:

text
模型回答一定提升;
所有任务都应减少 24.3% Token;
这套贪心选择优于所有 RAG 或 reranker;
8 个案例足以代表生产环境。

下一步应把真实失败样本加入 Dataset,再比较最终模型回答、工具行为和任务 Outcome。

14. 评测 Packet,而不是只看渲染后的文字

Grader 直接检查结构化 Packet:

python
grades = [
    required_evidence,
    missing_evidence,
    forbidden_sources,
    relevance,
    budget,
    traceability,
]

每一项分别回答:

Grader问题
required_evidence已知存在的必要主题是否被选中
missing_evidence系统声明缺失的主题是否准确
forbidden_sources是否暴露了禁用、未信任或越权来源
relevance选中来源是否覆盖当前需求
budgetPacket 是否在本案例预算内
traceability每条来源是否保留 locator 和更新时间

这种评测有两个优点:

  1. 不需要等待模型回答,组装层回归可以快速定位;
  2. 模型答错时,可以把问题分成“上下文没给对”和“给对后仍推理错误”。

不过不能只停在这里。生产链路至少需要两层 Eval:

text
Layer 1: Context Assembly Eval
  -> 该给的是否给了,不该给的是否挡住

Layer 2: Agent Outcome Eval
  -> 模型是否正确使用证据,任务是否真正完成

如果只测 Layer 2,失败定位很慢;如果只测 Layer 1,又无法证明最终任务价值。

实验 Dataset 中还有一个字段:

text
expected_missing_topics

它是 Eval 作者事先标注的 Gold Label,只用于判断组装器是否正确识别缺口。生产 Packet 不会预先知道“正确答案应该缺什么”,它只会根据任务合同和当前可用来源计算 missing_topics。这两个字段不能混为一谈。

15. 故意把预算降到 10:正确失败比勉强通过更重要

再做一次失败注入:

bash
python run_lab.py context-eval \
  --context-budget 10 \
  --output reports/local/context-budget-ten

在 PowerShell 中也可以写成一行:

powershell
python run_lab.py context-eval --context-budget 10 --output reports/local/context-budget-ten

这次预期退出码为 1,Gate 不通过。预算小到连必要来源都放不下时,Context Packet 不会编造一份“精简证据”来获得通过。

实验结果中,两种策略都只通过 2 / 8 个案例,可用必需证据覆盖率为 0%。这不是 Packet 设计失效,而是门禁正确暴露了:

text
当前预算不足以履行任务合同。

生产系统此时可以:

  • 申请更大预算;
  • 把任务拆成多个步骤;
  • 先读取摘要,再按需展开;
  • 转交拥有更合适工具的流程;
  • 明确拒绝并请求人工补充。

不应做的是静默丢掉证据,然后让模型继续生成确定语气的答案。

16. Token 估算器为什么故意写得很朴素

Lab 使用一个确定性估算器:

python
def estimate_tokens(text: str) -> int:
    # 中文字符按 1 个单位,其他内容按可重复的文本片段估算
    ...

它的用途是让测试跨环境稳定,不依赖某个 Provider 的 tokenizer。报告字段因此明确叫:

text
estimated_tokens

而不是 tokens

这带来一个必须保留的限制:66.38 -> 50.25 只能比较本实验内部的相对输入规模,不能拿来计算真实账单。

接入生产模型时,应替换或补充:

  • 当前模型对应的 tokenizer;
  • API 返回的实际 input/output usage;
  • cached 与 uncached 输入;
  • 工具调用和重试造成的总成本;
  • Context 组装耗时与检索耗时。

可复现测试和真实计费可以同时保留,不必让其中一个冒充另一个。

17. 怎样把实验接入真实 Agent

从 Lab 到真实系统,可以拆成五个适配层。

17.1 Source Adapter:统一不同来源

把仓库、数据库、工单和 API 返回统一为:

text
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 映射:

text
Context Packet
  -> OpenAI Responses / Agents SDK
  -> Claude Agent SDK
  -> LangGraph node state
  -> 自定义模型网关

这样更换 Provider 时,来源和策略合同不必全部重写。

17.5 Trace 与 Eval:保存选择证据

每次运行至少记录:

text
packet fingerprint
selected source ids
excluded reasons
missing topics
estimated and actual usage
model / prompt / policy version
final outcome

随后把生产失败转成新的 Context Case。例如:

text
失败:Agent 使用了旧版退款策略
  -> 新 Case:同时提供新旧规则
  -> 新 Grader:必须选择当前有效版本
  -> 修复策略
  -> 重跑历史 Case,确认无回归

这时 Context Engineering 才进入持续改进,而不是一次性的 Prompt 整理。

18. 哪些场景暂时不需要独立 Context 层

并非每个模型调用都需要完整的 Packet 系统。

场景更简单的方案
单次改写一段用户已提供的文字直接 Prompt
输入固定、没有外部事实和权限差异固定模板
确定性脚本已经能完成不使用 Agent
小型仓库中的一次本地代码修改项目规则 + 按需读文件
没有跨轮状态的简单分类Schema 输出 + 单次调用

当以下问题开始反复出现时,再把 Context Architecture 独立出来:

  • 同一任务经常拿到冲突或过期资料;
  • 来源包含不同信任域和权限;
  • 窗口预算导致关键内容被截断;
  • 长任务需要压缩、恢复和交接;
  • 团队无法解释某次回答依据了什么;
  • 改完检索策略后无法证明没有回归。

工程化的目的不是让每次调用更复杂,而是把已经出现的复杂性放到可管理的位置。

19. 45 分钟练习:为自己的 Agent 做第一版 Context Packet

不需要先接入向量数据库。选一个真实任务,用静态 JSONL 就能开始。

第 0-10 分钟:列出 Context 边界

写四列:

text
必须始终可见
按需检索
只保存在 Session
长期 Memory

把现有 Prompt、历史、工具返回和知识库内容放进对应列。

第 10-20 分钟:定义 Source Schema 与五道闸门

至少加入:

text
locator
updated_at
valid_until
trust
sensitivity
topics

选一个过期来源、一个无关来源和一个未信任来源作为失败样本。

第 20-30 分钟:输出第一份 Packet

要求同时包含:

text
selected
excluded + reason
missing_topics
budget
fingerprint

不要只输出最终拼好的字符串。

第 30-40 分钟:写 5 个 Context Case

建议包括:

  1. 正常成功;
  2. 新旧规则冲突;
  3. 必要证据缺失;
  4. 权限不足;
  5. 预算不足。

为每个 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. 这篇真正建立了什么

这一篇没有试图找到“最好的上下文长度”,而是把问题换成了更可执行的形式:

text
以前:
把可能有用的资料都塞给模型。

现在:
根据任务合同,从有来源、有时效、有权限的候选中,
在预算内组装一份可追踪的 Context Packet;
缺什么明确写出来;
再用固定 Case 和 Grader 判断策略是否改善。

Context Packet 不是终点。它仍要交给模型,模型仍可能误读证据;检索器也可能根本没有找到正确来源。但现在至少能把失败拆开:

text
来源不存在
来源未被检索
来源被策略挡住
来源因预算未进入
来源已进入但模型使用错误

能区分这些失败,才有资格谈下一步的自动恢复和持续改进。

下一篇进入 Harness Engineering:模型、Context、工具、Session、Sandbox 和审批分别由谁负责?我们会在同一个 GitHub 项目里,把目前分散的组装、执行和评测代码收进一个最小 Harness,并测试超时、重试、停止和恢复边界。

参考资料

官方与一手工程资料

研究与开源实现

本文代码与证据