上下文选择:从历史入库到请求装配
逐段追踪 Prompt.input 的来源,分开接纳规则、输出截断、状态差量和协议规范化。
学完你能做到
- 能沿入库、状态注入、for_prompt 和 build_prompt 追踪一条内容
- 能区分按类型/能力选择与相关性检索
- 能解释原始记录、活跃历史和传输增量为何不同
本课目录 · 6 节
1. “select 上下文”其实有几次选择
先限定问题:这里追踪的是普通请求从本地状态到 Prompt.input 的装配过程,以及后续所使用的保留逻辑。它并不是“从仓库里自动取最相关的代码”这一单一算法。仓库文件只有通过读取工具或显式输入进入历史,才会进入这条路径。
| 阶段 | 选择依据 | 对信息的影响 |
|---|---|---|
| 历史入库 | 协议项类型和 | 不是所有事件都进入历史;部分配置更新需要框架来源标记。 |
| 工具输出入库 | 单项容量策略 | 对活跃历史副本做;原始记录可另行持久化。 |
| 指令与环境更新 | 当前 和已知基线 | 初次完整注入,后续按分区状态产生更新。 |
| 请求前规范化 | 调用关系、输入模态 | 补缺失结果、去孤立结果,移除不支持的媒体。 |
| 后的历史重建 | 分支、类型白名单、预算和时间顺序 | 哪些原文保留由客户端规则决定;摘要或结果是另一份信息。 |
在这些函数中没有看到面向整段活跃历史的 embedding 检索或相关性排序。这个结论只针对所列调用链:插件、外部检索工具或扩展可以先取得资料,再把它作为输入加入。不能由此断言整个产品没有 。
2. 入库:先判断这是不是模型历史
ContextManager 的核心集合是带元数据的 ResponseItemEnvelope,而不是界面上显示的全部消息。record_item_with_metadata 先调用 is_api_message;普通 Message 排除 system 角色,ConfigurationUpdate 只有在 harness_authored_configuration 为真时进入;CompactionTrigger 与 Other 不进入。工具请求、输出、推理项和项等则有明确的接纳分支。
这里排除原始 system 消息,不意味着请求没有基础指令。基础指令有单独的持有与装配路径。反过来,把输入文本的 role 写成 developer,也不能制造 client_authored 等来源元数据。类型和来源共同影响后续保留行为。
fn is_api_message(message: &ResponseItem, metadata: Option<&CodexHarnessMetadata>) -> bool {
match message {
ResponseItem::Message { role, .. } => role.as_str() != "system",
ResponseItem::ConfigurationUpdate { .. } => {
metadata.is_some_and(|metadata| metadata.harness_authored_configuration)
}
ResponseItem::AdditionalTools { .. }入库规则不是单凭角色优先级排序。配置更新还检查来源。
查看原文及相邻代码 ↗完整接纳白名单 ↗codex-rs/core/src/context_manager/history.rs:1031–1054
历史快照使用 Arc 共享数据。clone_history 并不等于每次先深拷贝所有内容;规范化需要修改时,经 make_mut/unwrap_or_clone 得到请求侧副本。这个 设计让重试和请求组装可以使用稳定快照,同时避免每个只读阶段都复制大历史。
快照转请求材料 ↗codex-rs/core/src/context_manager/history.rs:603–617
3. 工具输出:先限制单项,再讨论整个窗口
record_annotated_items 的注释明确区分活跃历史与原始 rollout。实际入库时,函数输出和自定义输出在副本上被:若 带 history_truncation_token_limit,采用该覆盖值;否则在策略上增加序列化余量。工具输出的原始 envelope 没有在此处被同样替换,持久化仍有独立策略。
活跃历史截断不改写原始输出 ↗codex-rs/core/src/context_manager/history.rs:517–528
单项工具输出预算选择 ↗codex-rs/core/src/context_manager/history.rs:556–577
默认余量是 policy × 1.2。文本保留首尾,省略中段; 估算采用 UTF-8 字节数除以 4 向上取整。它不是 tokenizer,因此中文、代码和媒体的真实使用量可能不同,省略标记也不能直接当作精确额度证明。
预算余量与截断入口 ↗codex-rs/utils/output-truncation/src/lib.rs:17–40
字节近似常量 ↗codex-rs/utils/string/src/truncate.rs:1–5
首尾截断及估算实现 ↗codex-rs/utils/string/src/truncate.rs:61–115
4. 指令和环境:完整注入与差量更新
当前环境、权限、指令、项目约定和工具等被组织为 分区。没有 reference_context_item 时,record_context_updates_and_set_reference_context_item 构造完整初始并记录快照;已有基线时,更新世界状态并只发出相应分区的差量。
完整注入或状态更新的分支 ↗codex-rs/core/src/session/mod.rs:4744–4785
每个分区知道怎样比较自身状态,也知道怎样识别历史中仍保留的片段。render_history_diff 即使找到了旧快照,如果对应片段已不在活跃历史里,也会把它视作 Absent,再次渲染。这个检查避免“本地基线认为已经发过,但后的输入其实看不到”的失配。
if let Some(previous) = previous.and_then(|previous| previous.sections.get(id)) {
if section.has_retained_fragment_matcher()
&& !has_retained_fragment(items.clone(), section)
{
PreviousSectionState::Absent
} else {
PreviousSectionState::Known(previous)
}
} else if has_legacy_fragment(items.clone(), section) {
PreviousSectionState::Unknown精确快照和活跃历史是否含有对应片段,需要一起核对。
查看原文及相邻代码 ↗以 分区为例:状态相同不输出;替换时加入 replacement notice;删除时发出 removal notice。新文本不是简单追加给去猜哪些旧指令失效。分区更新还区分前缀、独立项和可合并片段,装配时维持规定的顺序。
约定的相同、替换与删除分支 ↗codex-rs/core/src/context/world_state/agents_md.rs:45–82
上下文放置策略 ↗codex-rs/core/src/context/world_state/mod.rs:68–80
5. for_prompt:结构修复,而不是挑出“重要消息”
正常克隆当前历史,调用 for_prompt(model.input_modalities),然后把结果交给 run_sampling_request。for_prompt 的关键动作是 normalize_history。它不在这里重新对所有消息打相关性分,也不默认只保留最近 N 条。
正常采样请求读取活跃历史 ↗codex-rs/core/src/session/turn.rs:523–540
| 顺序 | 操作 | 必须保留的语义 |
|---|---|---|
| 1 | 给缺失输出的调用补合成结果 | 普通函数/自定义调用使用 aborted 结果;不是补一个“成功”。工具搜索缺失时可补空目录输出。 |
| 2 | 去除没有对应请求的孤立输出 | 具名、无 的外部工具事件有特殊规则,不能一概删除。 |
| 3 | 移除不支持的图像 | 按当前 input_modalities 转换消息和工具输出。 |
| 4 | 移除不支持的音频 | 模态能力变化影响输入形状,和历史相关性是两回事。 |
四步规范化顺序 ↗codex-rs/core/src/context_manager/history.rs:962–980
这是协议 的修复:调用与结果必须对齐,输入必须符合当前能力。合成结果的 ID 从源项稳定派生,源码注释说明这是为重复规范化、重试和恢复时的缓存复用保留稳定性。
合成结果的稳定身份 ↗codex-rs/core/src/context_manager/normalize.rs:145–170
6. 组装 Prompt:工具和基础指令有两种放法
常规模式中,Prompt 把历史 input、model_visible_specs 和 base_instructions 分别装入字段。若当前窗口采用增量工具模式,build_prompt 会把 tools 设为空、base_instructions 设为空,因为这些信息通过历史前缀与更新项提供。
build_prompt 的常规与增量分支 ↗codex-rs/core/src/session/turn.rs:1563–1592
增量模式把工具和基本指令放入状态 ↗codex-rs/core/src/session/world_state.rs:144–160
所以分析请求大小时,应区分“逻辑输入由什么组成”和“这次传输只发了哪些增量”。连接复用或请求缓存是传输层问题,不能用网络包很小推断只看见最后一条消息。哪些历史原文在窗口重建后保留,继续读下一课的三种分支。
沿着源码读
以下定位均锁定本课程版本。先读注释与分支,再深入类型定义。
- 入库、输出截断和请求材料
codex-rs/core/src/context_manager/history.rs:517–617 - 规范化
codex-rs/core/src/context_manager/history.rs:962–980 - 模型历史项白名单
codex-rs/core/src/context_manager/history.rs:1031–1054 - 状态基线与注入
codex-rs/core/src/session/mod.rs:4744–4785 - 最终 Prompt 装配
codex-rs/core/src/session/turn.rs:1563–1592
实现推演
构造四个输入项:无来源标记的 ConfigurationUpdate、超限 FunctionCallOutput、只有调用没有输出的 FunctionCall、含图像的用户消息。给当前设置仅文本模态,逐阶段写出每个项是否进入历史、怎样变化、是否影响持久化原文。
阅读练习无需模型账号。示例命令请先确认目录与环境。
自测与边界检查
换成仅文本能力后,是哪个阶段决定图像不再进入普通请求?
展开自测答案
for_prompt 调用 normalize_history,按 input_modalities 移除不支持的媒体。它不是按相关性选择图像。此前入库与后续对媒体还有各自的容量规则。
为什么记录了一个 快照还不够?
展开自测答案
需要看到快照所代表的实际片段。render_history_diff 会同时检查历史是否仍有该片段;没有就按 Absent 重新渲染。否则或回滚后,框架与可见状态可能失配。