缓存时间:
2026/08/05 13:32
# Codex 实际发送给模型的内容
来源:https://www.0xkato.xyz/what-codex-actually-sends-to-the-model
- 首页 (https://www.0xkato.xyz/)
- 博客 (https://www.0xkato.xyz/blog)
- 研究 (https://www.0xkato.xyz/projects)
- 关于 (https://www.0xkato.xyz/about)
- 作品集 (https://github.com/0xkato/Portfolio/tree/main)
Codex 实际发送给模型的内容
2026 年 8 月 4 日,星期二 - 10 分钟阅读
我录制了 Codex 为一个 16 字符提示词生成的请求,然后测量了它在加载指令、暴露工具、读取文件、运行命令、接收图像以及压缩历史记录时的变化。
当我输入 "Reply with pong." 时,提示词只有 16 个字符。
Codex 发送的请求却是 42,980 字节。
在本地用 o200k\_base 编码为 JSON 后,大约相当于 9,435 个 token。其中包装提示词约占 25 个 token,即 0.3%。其余部分都来自 Codex 本身:指令、工具定义、权限、技能元数据、环境上下文和请求框架。
这些 token 数量是本地估算值,并非 API 用量或计费数据。捕获到的请求体本身是精确的。
文件读取和命令输出会在之后被加入。当累积的历史记录变得过大时,Codex 可以将其通过另一个模型请求发送,用一个摘要替换它,然后继续执行。
## 我如何录制请求
Codex 支持自定义模型提供商。我将其指向一个本地 HTTP 服务器,该服务器保存每个请求,对敏感标头进行脱敏,并返回固定的假响应。该实验没有调用任何外部模型。
``
Codex 客户端 → 本地录制器 → 确定性假响应
↑
请求在此处被保存
``
录制器展示的是客户端发送了什么。它无法展示真实提供商在收到请求后可能做出的更改、任何输入是否会被缓存,或它将如何计费。
我测量了两项内容:
- 原始请求字节数:HTTP JSON 正文在脱敏前的大小。
- 近似文本 token 数:脱敏后的 JSON 在本地用 o200k\_base 编码的结果。
测试使用的是 Codex CLI 0.145.0 及 gpt-5.6-sol 模型。
## 第一个请求
我在一个空的临时 Git 仓库中启动了 Codex,禁用了项目指令,并将 CODEX\_HOME 指向一个空目录。
请求仍然描述了五个内置系统技能,因此这是一个隔离主目录的基线,而不是最小化可能的请求。
有三项内容占据了 9,435 个 token 中的 7,696 个:
包含内容 | 序列化大小 | 本地估算值
--- | --- | ---
additional\_tools 开发者条目 | 四个顶级工具条目 | 16,741 字符 | 3,942 tokens
开发者消息 | 主要 Codex 指令 | 17,730 文本字符 | 3,729 tokens
用户消息 | Reply with pong. | 16 文本字符 | 25 tokens
这四个工具条目分别是 exec、wait、request\_user\_input 和一个协作命名空间。它们代表的动作不止四个。collaboration 包含六个子工具,而 exec 描述了命令执行、打补丁、图像检查、计划更新以及其他嵌套工具。
在这次运行中,Codex 将工具条目和基础指令放在了 input 数组中,而不是使用顶层的 instructions 和 tools 字段。
## 项目指令
Codex 从仓库根目录到它启动的目录构建其项目指令链。它在每一级查找 AGENTS\.md,将更靠近的指令放在后面。
我创建了一个根级和一个子级的 AGENTS\.md,每个包含 100 个唯一标记。
- 从仓库根目录启动会发送全部 100 个根标记,且不发送子标记。
- 从子目录启动会发送全部 200 个标记。
- 从根目录启动后运行 ls child 不会添加子级指令。
启动目录决定了自动指令链。显式读取子文件仍然可以将其内容作为普通工具历史记录添加进来。
在大小测试中,我使用了合成的高熵标记而不是自然语言。每个标记是一个类似 PAIRED\_AGENTS\_1000\_0001 的字符串,o200k\_base 会将其自行拆分为 11 个 token。普通散文词汇则不会。下表显示指令会被完整传输——这并不代表你自己的 AGENTS\.md 会花费这么多。
更大的文件会直接改变第一个请求:
原始请求字节数 | 本地估算值 | 与基线的差异
--- | --- | ---
隔离主目录基线 | 42,980 | 9,435 | —
250 个合成 AGENTS 标记 | 48,927 | 11,965 | +5,947 字节;+2,530 tokens
1,000 个合成 AGENTS 标记 | 67,177 | 20,465 | +24,197 字节;+11,030 tokens
在一个包含 250 个标记的两请求追踪中,全部 250 个标记出现在两个请求中。
## 技能分两个阶段加载
仓库中 \.agents/skills/ 下的技能最初只贡献其名称、描述和路径。它们的 SKILL\.md 正文在被读取之前不会出现。
每个合成技能有一个 12 词的描述和一个包含唯一标记的 200 词正文。
- 一个技能为第一个请求增加了 481 字节和约 125 个 token。
- 十个技能增加了 4,810 字节和约 1,250 个 token。
- 没有任何正文标记出现在两个请求的第一个中。
我测试了一个包含三个工具的本地 MCP 服务器,以及两个共计七个工具的本地服务器。
在仅初始运行的测试中,两个请求均为 46,582 字节和约 10,410 个本地 token。二者都不包含自定义工具名称或描述。相反,exec 接口获得了通用的 MCP 发现指南。禁用已配置的服务器后,请求恢复到了隔离主目录基线。
在我让 exec 打印匹配的延迟工具条目后,描述出现了:
保留的描述标记 | 增加的字节数 | 增加的本地估算值
--- | --- | ---
一个服务器,三个工具(每个 40 个标记) | 120 | 5,894 | 1,522 tokens
两个服务器,七个工具(三个 40 标记,四个 60 标记) | 360 | 15,970 | 4,210 tokens
一个服务器白名单仅一个工具(40 个标记) | 40 | 2,288 | 578 tokens
这些差异将发现后的请求与同一追踪中的第一个请求进行比较,后者在另一次运行中为 46,589 字节和 10,410 个本地 token。
我还创建了一个包含 5,000 个标记的工具描述。截断后,其保留的首尾样本仍包含 1,046 个标记。下一个请求增长了 40,607 字节和约 9,631 个本地 token。
按配置观察到的请求大小
按配置观察到的请求大小
MCP 发现将延迟的工具描述移入历史记录
MCP 发现将延迟的工具描述移入历史记录
## 一个编码任务,逐个请求查看
我创建了一个带有配置错误的小型 Python 测试项目:一个显式的 false 值被用户默认值替换。任务是:
> 修复导致显式 false 值被用户默认值替换的配置优先级错误。添加一个回归测试并运行相关的测试套件。
这个追踪衡量的是请求增长,而非模型推理。动作是预先确定的。
动作完成在其之前 | 原始字节数 | 本地估算值
--- | --- | ---
0 | 初始任务 | 44,189 | 9,815
1 | 搜索 | 45,289 | 10,114
2 | 文件读取 | 46,278 | 10,368
3 | 初始测试 | 46,959 | 10,529
4 | 添加回归测试 | 47,635 | 10,701
5 | 回归测试失败 | 48,832 | 10,968
6 | 第二次检查 | 49,953 | 11,254
7 | 应用修复 | 50,471 | 11,386
8 | 测试套件通过 | 51,170 | 11,554
9 | 验证差异 | 52,389 | 11,889
最终请求比第一个请求大 8,200 字节和约 2,074 个 token。搜索结果、文件内容、测试、失败信息、补丁和最终差异都保留给了后续轮次。
一次错误修复期间的上下文增长
一次错误修复期间的上下文增长
## 文件在读取后才会进入请求
我创建了五个带有唯一标记的无害文件:一个普通源文件、一个被忽略的文件、一个假的 \.env、一个被忽略的 20,000 行日志,以及一个在 AGENTS\.md 中被提及名称的文件。
它们的内容都没有出现在第一个请求中。
运行 rg --files -uu 添加了它们的文件名,而不是内容。在显式读取之后,普通文件、被忽略的文件、假的 \.env 和被忽略的日志都出现在后续请求中。
原始字节数 | 本地估算值
--- | ---
文件操作之前 | 43,500 | 9,617
文件名列表之后 | 45,040 | 10,092
读取普通文件之后 | 45,445 | 10,196
读取被忽略文件之后 | 45,846 | 10,303
读取假 \.env 之后 | 46,250 | 10,418
读取大型被忽略日志之后 | 87,561 | 20,350
读取被提及文件之后 | 87,983 | 20,461
大型日志在下一个请求之前被截断为首尾样本。
Codex 不会自动上传仓库。但 \.gitignore 并不会阻止显式读取,也不会阻止由此产生的工具输出进入后续请求。
## 终端输出保留在历史记录中
十行和一百行的命令结果被逐字保留到后续请求中。一个 10,000 行的结果被截断为首尾样本,但请求仍然从 43,337 字节和约 9,584 个 token 增长到了 88,480 字节和 25,835 个 token。
重复的输出不会被去重。ANSI 格式化的文本和失败的 Python 堆栈跟踪也会保留在历史记录中。
## 图像以数据 URL 形式传输
一张合成的 32×32 PNG 以 442 字符的 data:image/png;base64,... URL 出现在请求中。
随后我附加了一张更大的合成渐变 PNG。Codex 在传输前对其进行了调整大小并重新编码。捕获到的 71,666 字符数据 URL 解码后是一张 1600×1600 的 PNG。一个包含两张图像的请求同时包含两个数据 URL。
原始请求体大小分别为:小图像 43,955 字节,变换后的大图像 115,265 字节,两张图像一起 115,886 字节。对 base64 进行本地文本 token 化并不是图像 token 计算,因此我没有用它来估算成本。
## 压缩会将历史记录通过另一个请求发送
针对 OpenAI 或 Azure,Codex 通过专用端点进行压缩。针对自定义提供商(如我的录制器),它会自行构建摘要请求。这就是我捕获的路径,它有两个阶段。首先,Codex 将累积的历史记录连同摘要提示词一起发送。然后,它围绕保留的用户消息和返回的摘要重建对话。代码位于压缩请求 (https://github.com/openai/codex/blob/bb5054fe47abe73ecbbd454751066a28c89f4bb9/codex-rs/core/src/compact.rs#L112-L140) 和 历史替换 (https://github.com/openai/codex/blob/bb5054fe47abe73ecbbd454751066a28c89f4bb9/codex-rs/core/src/compact.rs#L241-L385) 路径中。
我通过合成用量强制触发了它:报告 13,000 个输入 token、20,000 个 token 的上下文窗口和 12,000 个 token 的压缩阈值。
这产生了三个请求:
- 初始请求:42,030 字节和约 9,378 个 token。包含普通工具和用户请求。
- 压缩请求:68,375 字节和约 21,408 个 token。包含累积的历史记录、一个大型保留的工具结果和一个摘要提示词。普通工具列表为空。
- 恢复后的请求:42,646 字节和约 9,500 个 token。普通工具和原始用户消息回来了。原始工具调用和输出被替换为合成摘要。
压缩移除了什么、保留了什么
压缩移除了什么、保留了什么
恢复后的请求比压缩请求小 25,729 字节和约 11,908 个 token。摘要故意写得很简短。
这展示了历史记录是如何被替换的,而非真实模型会如何进行摘要。压缩之后,某个细节可能只能通过生成的摘要存续,而不再是原始的消息或工具输出。
## 什么穿过了机器边界
有些信息在任何工具使用之前就已存在:Codex 指令、可见的工具接口、被发现的 AGENTS\.md 链、技能元数据、环境上下文和用户提示词。
其他信息只在某次动作之后才出现:读取后的文件内容、命令后的终端输出、发现后的 MCP 描述,以及附加后的图像。
未读取的仓库文件、被忽略的文件和假的 \.env 不会自动出现。提示词是种子。请求是工作状态。
什么可以离开机器
什么可以离开机器
## 这个实验没有测量什么
- 跨所有 Codex 版本、模型、操作系统或产品形态的通用最小请求。
- 自主模型推理。多轮工作流使用了固定的假响应,因此真实会话每轮携带的内容比这些追踪显示的多。
- 模型生成的压缩摘要的质量。
- 提供商侧的转换、缓存命中、计费或模型内部机制。
配套的工件包包含录制器、脱敏的请求体、分析文件、测试和图源。
非常欢迎反馈。如果你对其中任何内容感兴趣,请在 X (https://x.com/0xkato) 上联系我。我喜欢结交新朋友。