最近在给团队做编码模型的成本评估,前前后后跑了几轮真实仓库的对比测试,发现一个很现实的问题:网上关于 Codex 价格的说法五花八门,有人说便宜到可以随便用,有人说跑一轮重构账单就爆了。但真去看官方定价页,会发现单价本身并不是最关键的变量——上下文长度档位、缓存命中率、是否走批量接口、任务拆得粗不粗,这几项对最终账单的影响远比单价大得多。本文不打算堆一串可能明天就变的数字,而是把 Codex 从安装、配置、接入到成本核算的整条链路完整走一遍,附上可直接复制的代码和一份能落地的成本统计脚本。无论你是刚接触编码代理的新手,还是已经在团队里推动 AI 编码工具落地的后端工程师,都能从里面找到能直接用的部分。
1. Codex 是什么:从模型到编码代理的定位变化
1.1 Codex 的产品形态
很多读者对 Codex 的印象还停留在早期的代码补全模型上,这个理解已经过时了。现在的 Codex 更像是一套“编码代理(Coding Agent)”产品矩阵,通常包含几个部分:一个可以在终端里直接运行的 CLI 工具、一个可以在编辑器里调用的扩展、以及一套可以通过 API 调用的编码模型。它们的共同目标不是“帮你补全一行代码”,而是“给一个任务描述,让它自己去读文件、改代码、跑测试、看报错、再改”。
这个定位变化很重要,因为它直接决定了成本结构。补全类工具是按“每次按键触发一次小请求”计费的,单次请求只有几百 token;而代理类工具是按“一次任务触发几十轮大请求”计费的,每轮都可能带着整个仓库的相关上下文。同样是“用了一小时”,两者的 token 消耗可能差两个数量级。所以在讨论“Codex 便宜不便宜”之前,先要搞清楚你用的是补全模式还是代理模式。
从工程角度看,代理模式带来的最大变化是“人从执行者变成了审查者”。你不再需要自己去找文件、改 import、调缩进,而是写清楚需求、看它给的 diff、决定是否接受。这个转变对流程的要求更高:如果没有测试、没有 lint、没有代码审查,代理生成的代码很容易以“看起来能跑”的形态混进主干。
1.2 Codex 与 DeepSeek 等模型的差异
经常有人问“Codex 和 DeepSeek 到底选哪个”,这个问题其实问得不太准确,因为两者的可比维度不止一个。它们都能做代码生成,但在工具链形态、上下文窗口、是否内置代理循环、是否支持沙箱执行、生态集成度上差别很大。一个提供的是“模型 API”,另一个提供的是“模型 + 客户端 + 代理循环 + 沙箱”的整套方案。
从成本角度看,直接比“每百万 token 多少钱”意义有限,更合理的方式是比“完成同一个任务需要多少钱”。一个模型单价低但需要来回试五次才能改对,另一个单价高但一次就能给出可用的 diff,最终账算下来很可能反过来。这也是为什么本文坚持用“真实任务 + 真实 token 统计”的方式做核算,而不是抄一个单价表对比。
在实际选型时,我一般建议按下面这几个维度打分,而不是只看价格:
| 维度 | 需要确认的问题 | 为什么重要 |
|---|---|---|
| 任务完成率 | 同一批任务,一次通过的比例是多少 | 直接决定重试次数,进而决定总成本 |
| 上下文容量 | 能否装下你项目里最大的那个文件 | 装不下就要人工切分,增加沟通成本 |
| 工具调用 | 是否支持读文件、跑命令、看测试结果 | 决定它是“补全”还是“代理” |
| 计费口径 | 输入输出是否分开计价、缓存是否打折 | 决定你的优化方向 |
| 数据合规 | 代码是否会离开你的环境、保留多久 | 企业落地的硬门槛 |
1.3 价格调整为什么会影响技术选型
价格变化之所以值得单独写一篇文章,是因为它改变的不只是“贵不贵”,而是“什么场景下值得用”。当单价处在高位时,合理的策略是只在最难的问题上调用,简单任务交给人或者本地小模型;当单价下降到某个区间后,把重复性的脏活(写测试、补类型注解、改日志格式、迁移 API 调用)整体外包给代理,反而开始划算。
所以真正需要建立的不是“记住当前单价”,而是一套可以随时重算的模型:你的团队每月有多少小时花在机械性编码上、这些任务平均消耗多少 token、当前单价下折合多少钱、和人力成本比是赚还是亏。这套模型建好之后,单价一变,重新跑一遍脚本就能得出结论,不需要重新做一轮调研。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与版本说明
2.1 运行环境与依赖
本文示例以常见的开发环境为例,具体版本请按你项目的实际情况调整。Codex CLI 是通过 Node.js 分发的,所以第一优先级是确认 Node 版本。版本过低会出现安装成功但启动报语法错误的情况,这类问题排查起来很浪费时间,建议一开始就确认好。
下面是我实际使用中比较稳妥的基线环境:
- 操作系统:macOS、Linux 或 Windows(Windows 建议在 WSL2 中运行,避免路径和权限相关的坑)
- Node.js:使用当前 LTS 版本,通过
node -v确认 - 包管理器:npm 或 pnpm 均可,团队内保持一致即可
- Git:必须可用,代理的很多操作依赖 git 来展示 diff 和回滚
- 终端:支持真彩色的终端体验更好,但非必需
先确认环境:
bash复制# 确认 Node 版本,建议使用 LTS
node -v
npm -v
# 确认 git 可用,代理依赖它生成 diff
git --version
如果 node -v 输出的主版本号过低,建议先用 nvm 或 fnm 切换版本,不要试图用旧版本硬跑。切换方式以你本机的版本管理工具为准,这里不写死具体命令,避免和你的环境冲突。
2.2 安装 Codex CLI
安装命令本身很简单,关键在于装完之后要验证它真的能启动,而不只是“装上了”。
bash复制# 全局安装 Codex CLI
npm install -g @openai/codex
# 验证安装结果
codex --version
# 查看可用子命令与参数
codex --help
codex --help 这一步不要跳过。编码代理类工具的参数迭代很快,审批模式、沙箱模式的开关名称在不同版本之间可能有调整。养成先看 --help 再写脚本的习惯,可以避免大量“照着教程抄但跑不通”的时间浪费。本文后续给出的参数都会标注“以你本机 --help 输出为准”。
2.3 认证方式与密钥管理
认证是新手最容易卡住的一步,也是最容易埋下安全隐患的一步。Codex CLI 通常支持两种路径:一种是交互式登录,把凭据保存在本机配置目录;另一种是通过环境变量注入 API Key,适合 CI 和容器环境。
无论用哪种方式,有几条底线必须守住:
- 密钥绝对不要写进代码仓库,包括测试文件和示例文件。
.env文件必须第一时间加入.gitignore。- CI 环境使用平台提供的密钥管理功能注入环境变量,不要明文写在流水线配置里。
- 团队共用密钥是反模式,出问题无法追溯到人,也无法做配额控制。
bash复制# 方式一:交互式登录,凭据由 CLI 自行管理
codex login
# 方式二:通过环境变量注入,适合 CI / 容器
export OPENAI_API_KEY="你的密钥"
在本地开发时,我习惯把环境变量写进 ~/.zshrc 或 ~/.bashrc 之外的一个独立文件,需要时手动 source,避免所有终端会话都带着密钥。这个习惯看起来麻烦,但在误执行 env 或者把终端输出贴到聊天窗口时,能省掉很多麻烦。
2.4 示例项目结构
为了让后面的示例可以直接跑起来,我们准备一个最小的 Python 项目。选择 Python 是因为它的依赖轻、上手快,便于把注意力集中在 Codex 的用法上,而不是环境配置上。
text复制codex-demo/
├── .gitignore
├── AGENTS.md
├── pyproject.toml
├── src/
│ └── calculator/
│ ├── __init__.py
│ └── core.py
└── tests/
└── test_core.py
这个结构里有两个文件值得特别说明。AGENTS.md 是给代理看的项目说明书,后面会专门讲。pyproject.toml 用来声明依赖和工具配置,代理在跑测试时会读取它。其余就是一个普通的包结构,没有特殊要求。
3. 核心概念与工作原理拆解
3.1 会话、上下文与 token 计费
理解 token 是理解成本的第一步。模型不是按“字数”计费的,而是按 token。英文大约 4 个字符对应 1 个 token,中文大约 1 到 2 个汉字对应 1 个 token,代码因为符号密集,token 密度通常比自然语言更高。一个 500 行的 Python 文件,轻松就能吃掉几千 token。
代理模式的成本之所以高,是因为它会做“多轮上下文累积”。第一轮它读三个文件,第二轮它带着这三个文件的内容再加上新读的两个文件,第三轮又叠加……如果不做裁剪,上下文会像滚雪球一样增长,而每一轮都要为前面所有内容重新付费。这就是为什么“上下文管理”是成本优化的核心,而不是可选项。
下面这个表可以帮助建立直觉:
| 操作 | 大致 token 量级 | 备注 |
|---|---|---|
| 一次简单代码补全 | 数百 | 只带光标附近内容 |
| 一次单文件重构 | 数千到一万 | 带整个文件和相关 import |
| 一次跨文件功能开发 | 数万到十几万 | 多轮累积,含测试输出 |
| 一次全仓库级别改造 | 可能超过窗口 | 需要分片,否则会被截断 |
这张表不是精确数据,而是量级参考。真正精确的数字要靠后面的统计脚本来获得,不要凭感觉估算预算。
3.2 沙箱与审批模式
代理能执行命令,这是它比补全工具强大的地方,也是风险所在。所以主流代理工具都会提供“沙箱模式”和“审批模式”两层控制。沙箱限制它能碰哪些目录、能不能联网;审批决定它执行命令前是否需要你点确认。
常见的组合有这么几种,适用场景不同:
- 完全只读:只允许读文件、不允许写、不允许执行。适合做代码审查和方案咨询。
- 工作区可写、命令需确认:日常开发最常用,安全性和效率平衡得比较好。
- 工作区可写、命令自动执行:适合在容器或一次性环境里跑,不要在有重要数据的机器上开。
- 全自动:只在隔离的 CI 环境里使用,且必须配合只读的外部依赖源。
bash复制# 只读模式咨询方案,不产生任何副作用
codex --sandbox read-only "分析 src/calculator/core.py 的边界条件问题"
# 允许写工作区,但执行命令前需确认
codex --sandbox workspace-write "为 core.py 补充类型注解并跑测试"
具体参数名请以 codex --help 为准。这里想强调的是选择逻辑:先问“这次任务需要写文件吗”,再问“需要执行命令吗”,最后才决定用哪种模式。默认用最严格的那一档,需要放宽时再放宽,这个习惯能挡掉绝大多数误操作。
3.3 提示词缓存与成本
大多数编码模型的定价里都有一个“缓存命中”折扣,命中的输入 token 单价远低于未命中的。这对代理模式影响巨大,因为代理的多轮对话里,前面几轮的内容在后续轮次中是重复的,如果缓存能命中,这部分就按折扣价算。
想让缓存命中率高,关键是让请求的“前缀”保持稳定。具体做法包括:
- 把固定的系统提示、项目约定(
AGENTS.md)放在最前面,且内容不要频繁改动。 - 不要在每个请求开头插入时间戳、随机 ID 这类每次都变的内容。
- 长任务尽量在一次会话里完成,不要拆成多次独立调用。
- 文件读取顺序尽量稳定,避免同样的内容以不同顺序出现。
第 2 条经常被忽略。有些团队为了做链路追踪,会在提示词最前面加一个请求 ID,结果缓存全部失效,账单直接翻倍。追踪信息应该放在元数据里,不要放在提示词正文里。
3.4 AGENTS.md:给代理的项目说明书
AGENTS.md 是这个生态里非常实用的一个约定。它放在仓库根目录,用来告诉代理这个项目的规则:用什么语言、怎么跑测试、代码风格是什么、哪些目录不要碰、提交信息怎么写。有了它,你就不需要在每次对话里重复解释这些背景,既省 token 又提高准确率。
markdown复制<!-- 文件路径:AGENTS.md -->
# 项目约定
## 技术栈
- Python 3.11+,使用 pyproject.toml 管理依赖
- 测试框架:pytest
- 代码风格:ruff + black
## 常用命令
- 安装依赖:pip install -e ".[dev]"
- 运行测试:pytest -q
- 代码检查:ruff check src tests
## 编码规范
- 所有公共函数必须有类型注解和 docstring
- 禁止吞掉异常,捕获后必须记录日志或重新抛出
- 新增函数必须同时新增对应测试
- 不要修改 migrations/ 目录下的历史文件
## 禁止事项
- 不要执行 rm -rf、git push --force 等破坏性命令
- 不要修改 .env、密钥文件和 CI 配置
- 不要引入新的第三方依赖,除非在说明中明确理由
这份文件的写法有个原则:写“约束”比写“介绍”有用。代理不需要知道项目多宏伟,它需要知道“跑测试用哪条命令”“改完代码要做什么检查”。把这两类信息写清楚,任务成功率会明显提升。
4. 完整实战:用 Codex CLI 改造一个小模块
这一节我们把前面的概念串起来,完整走一遍从零到一个可用 diff 的流程。示例任务选的是“给一个计算器模块补全边界处理和测试”,因为这类任务有明确的验收标准(测试通过),便于验证结果。
4.1 初始化项目与依赖
先建目录、写代码、提交一次初始版本。提交这一步很重要,代理生成的改动依赖 git 来展示和回滚,没有初始提交会很难受。
bash复制mkdir -p codex-demo/src/calculator codex-demo/tests
cd codex-demo
git init
python复制# 文件路径:src/calculator/core.py
def divide(a, b):
return a / b
def average(numbers):
total = 0
for n in numbers:
total += n
return total / len(numbers)
toml复制# 文件路径:pyproject.toml
[project]
name = "calculator"
version = "0.1.0"
requires-python = ">=3.11"
[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff>=0.5"]
[tool.pytest.ini_options]
testpaths = ["tests"]
gitignore复制# 文件路径:.gitignore
__pycache__/
*.pyc
.env
.pytest_cache/
.ruff_cache/
这几段代码里明显埋了问题:divide 没有处理除零,average 没有处理空列表,而且两个函数都没有类型注解和 docstring。这正是我们想让代理去发现并修复的。
bash复制git add .
git commit -m "chore: init calculator demo"
4.2 交互式运行第一个任务
进入交互模式,把需求说清楚。这里有个技巧:需求里要包含“验收方式”,代理会自己跑测试来验证,而不是改完就交差。
bash复制codex --sandbox workspace-write
进入交互界面后输入:
text复制请阅读 src/calculator/core.py,完成以下改造:
1. 为 divide 和 average 添加类型注解和 docstring
2. divide 在 b 为 0 时抛出 ValueError,并给出清晰错误信息
3. average 在输入为空序列时抛出 ValueError
4. 在 tests/test_core.py 中为上述行为补充测试,覆盖正常和异常路径
5. 完成后运行 pytest -q 验证,如果失败请自行修复直到通过
注意第 5 条,这是让代理闭环的关键。很多人在这一步只说“改一下代码”,结果代理改完就停了,你还得自己跑测试、自己贴报错、再让它改,来回几轮 token 全浪费在沟通上。
4.3 非交互模式与脚本化
交互模式适合探索,非交互模式适合放进脚本和 CI。codex exec 就是干这个的,它执行完就退出,输出可以直接接管道。
bash复制# 非交互执行单次任务,输出到标准输出
codex exec "检查 tests 目录下是否有未覆盖的异常分支,列出清单即可,不要修改文件"
# 把结果重定向到文件,便于后续处理
codex exec "为 src/calculator/core.py 生成一份变更摘要,说明每个函数改了什么" > summary.md
非交互模式有几个使用要点:一是任务描述要足够明确,因为没有追问的机会;二是建议配合只读沙箱,避免在无人值守的情况下产生意外改动;三是输出格式要约定好,比如“只输出 Markdown 表格”,否则后续解析会很痛苦。
在 CI 里使用时要格外谨慎。建议的顺序是:先在本地跑通,再放进流水线的“检查”环节(只读),确认稳定后再考虑放进“自动修复”环节。直接把写权限交给 CI 里的代理,风险很高。
4.4 运行结果与验收
代理执行完成后,你会看到它给出的文件改动。这时候不要直接全部接受,按下面的顺序检查:
- 用
git diff看改动范围,确认没有碰不该碰的文件。 - 自己跑一遍
pytest -q,不要只信代理说“测试通过”。 - 检查异常处理的语义是否符合你的预期,比如抛
ValueError还是自定义异常。 - 检查测试是否真的在测东西,有没有出现
assert True这种占位。
bash复制# 查看改动
git diff
# 独立验证
pytest -q
ruff check src tests
这一步是整个流程里最重要的。代理生成的代码有个特点:看起来非常合理,注释齐全、命名规范,但可能在边界条件上存在细微偏差。比如除零检查写成 if b == 0 只覆盖了整数零,没覆盖 0.0——虽然 Python 里两者相等不受影响,但换成别的语言就是 bug。人工审查的价值就在这里。
5. 用 API 方式调用编码模型
CLI 适合交互,但要做批量处理、集成到自有系统、或者做精细化成本统计,就得走 API。这一节给出 Python 和 Node.js 两个版本的调用示例,以及一个可复用的成本统计脚本。
5.1 Python 调用示例
下面这段代码演示了完整的调用流程,包括重试、超时和用量记录。模型标识请替换为你账号实际可用的编码模型,不要照抄示例里的占位值。
python复制# 文件路径:scripts/run_task.py
import json
import os
import time
from pathlib import Path
from openai import OpenAI
client = OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
timeout=120.0,
max_retries=3,
)
MODEL = os.environ.get("CODEX_MODEL", "your-codex-model-id")
def run_task(prompt: str) -> dict:
"""执行一次编码任务,返回文本结果和用量信息。"""
start = time.time()
resp = client.responses.create(
model=MODEL,
input=prompt,
instructions=(
"你是一个严谨的编码助手。只输出补丁和必要的说明,"
"不要输出与任务无关的寒暄。所有代码必须附带类型注解。"
),
)
elapsed = time.time() - start
usage = getattr(resp, "usage", None)
record = {
"elapsed_sec": round(elapsed, 2),
"input_tokens": getattr(usage, "input_tokens", 0) if usage else 0,
"output_tokens": getattr(usage, "output_tokens", 0) if usage else 0,
"text": resp.output_text,
}
# 把用量追加写入本地账本,便于后续统计
ledger = Path("usage.jsonl")
with ledger.open("a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
return record
if __name__ == "__main__":
result = run_task(
"阅读 src/calculator/core.py,指出所有可能的除零和空序列场景,"
"按文件:函数:行号的格式列出,不要修改代码。"
)
print(result["text"])
print(f"\n用量:输入 {result['input_tokens']} / 输出 {result['output_tokens']}")
这段代码有三个值得注意的细节。第一,instructions 里把输出约束写死了,减少无关输出就是省钱。第二,用量记录写进了 JSONL 文件,这是后面成本核算的数据来源。第三,getattr 做了兜底,因为不同 SDK 版本对 usage 字段的暴露方式可能不同,硬取属性容易在升级后报错。
5.2 Node.js 调用示例
团队里如果有前端同学要复用这套逻辑,Node 版本更顺手。逻辑和 Python 版保持一致,方便对照。
javascript复制// 文件路径:scripts/run-task.mjs
import { OpenAI } from "openai";
import { appendFile } from "node:fs/promises";
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
timeout: 120_000,
maxRetries: 3,
});
const MODEL = process.env.CODEX_MODEL ?? "your-codex-model-id";
async function runTask(prompt) {
const startedAt = Date.now();
const resp = await client.responses.create({
model: MODEL,
input: prompt,
instructions: "你是严谨的编码助手,只输出补丁与必要说明,不要寒暄。",
});
const record = {
elapsedSec: Number(((Date.now() - startedAt) / 1000).toFixed(2)),
inputTokens: resp.usage?.input_tokens ?? 0,
outputTokens: resp.usage?.output_tokens ?? 0,
text: resp.output_text ?? "",
};
await appendFile("usage.jsonl", JSON.stringify(record) + "\n", "utf8");
return record;
}
const result = await runTask(
"整理 src/calculator/core.py 的公开接口清单,输出 Markdown 表格。"
);
console.log(result.text);
console.log(`用量:输入 ${result.inputTokens} / 输出 ${result.outputTokens}`);
运行方式和 Python 版一致,通过环境变量注入密钥和模型标识。两个版本都写 usage.jsonl,格式字段名略有差异,实际使用时建议统一,避免后续统计脚本要写两套解析逻辑。
5.3 成本核算脚本
这是本文最实用的一段。它的思路很简单:把账本里的 token 数按单价表算钱,按任务类型分组,输出明细和汇总。单价表放在独立的 JSON 文件里,官方价格一变,只改这个文件,不用动代码。
json复制{
"comment": "单价单位为每百万 token,请按你后台看到的实际单价填写",
"your-codex-model-id": {
"input": 0,
"cached_input": 0,
"output": 0
}
}
python复制# 文件路径:scripts/cost_report.py
import json
from collections import defaultdict
from pathlib import Path
PRICE_FILE = Path("pricing.json")
LEDGER_FILE = Path("usage.jsonl")
def load_prices() -> dict:
return json.loads(PRICE_FILE.read_text(encoding="utf-8"))
def cost_of(record: dict, price: dict) -> float:
"""按每百万 token 单价计算单次调用成本。"""
cached = record.get("cached_input_tokens", 0)
normal_in = max(record.get("input_tokens", 0) - cached, 0)
return (
normal_in / 1_000_000 * price["input"]
+ cached / 1_000_000 * price["cached_input"]
+ record.get("output_tokens", 0) / 1_000_000 * price["output"]
)
def main() -> None:
if not LEDGER_FILE.exists():
print("没有找到 usage.jsonl,请先运行任务脚本。")
return
prices = load_prices()
default_price = {
"input": 0.0,
"cached_input": 0.0,
"output": 0.0,
}
total_cost = 0.0
total_in = 0
total_out = 0
by_day = defaultdict(float)
for line in LEDGER_FILE.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
record = json.loads(line)
model = record.get("model", "your-codex-model-id")
price = prices.get(model, default_price)
c = cost_of(record, price)
total_cost += c
total_in += record.get("input_tokens", 0)
total_out += record.get("output_tokens", 0)
day = record.get("date", "unknown")
by_day[day] += c
print(f"调用总次数:{sum(1 for _ in LEDGER_FILE.read_text(encoding='utf-8').splitlines() if _.strip())}")
print(f"输入 token 合计:{total_in:,}")
print(f"输出 token 合计:{total_out:,}")
print(f"估算总成本:{total_cost:.4f}")
print("\n按日期汇总:")
for day in sorted(by_day):
print(f" {day}: {by_day[day]:.4f}")
if __name__ == "__main__":
main()
这个脚本故意写得很朴素,因为它的价值不在于代码多精巧,而在于“有一个统一的口径”。团队里最容易出现的争议是“到底花了多少钱”,有了这个脚本,每个人看到的都是同一份账本算出来的数字,讨论就能聚焦在“哪个任务最费钱”上,而不是扯皮。
5.4 让账本更完整
上面的脚本还缺两个字段:model 和 date。补上它们很简单,在任务脚本写账本的时候一起写进去就行。
python复制# 在 run_task 的 record 字典里补充
from datetime import date
record["model"] = MODEL
record["date"] = date.today().isoformat()
补完之后,账本就能按模型、按天两个维度切片。实际运营中我发现,“按天切片”比“按任务切片”更有用,因为你能立刻看出某一天成本异常飙升,然后回去查那天跑了什么任务。常见的原因是某次任务忘了加限制,代理在一个大目录里反复扫文件。
6. 成本优化:把账单降下来的实操手段
价格调整只是把“单价”这个变量改了,真正决定账单的还是用量结构。这一节给几个我验证过确实有效的手段,按投入产出比排序。
6.1 先量化,再优化
没有度量就没有优化。在动手改任何流程之前,先跑两周的账本,搞清楚三件事:哪个任务类型最费钱、平均每个任务几轮、缓存命中率大概多少。这三个数字决定了你应该优化哪里。
如果发现“输入 token 远大于输出”,说明上下文太重,优化方向是裁剪文件读取范围。如果发现“输出 token 占比高”,说明让模型输出太多无关内容,优化方向是收紧输出格式约束。如果发现“重试次数多”,说明任务描述不够清晰,优化方向是改提示词和 AGENTS.md。
6.2 上下文裁剪的具体做法
最直接有效的一招是“限定范围”。与其说“优化这个项目”,不如说“只看 src/calculator/core.py 和对应的测试文件”。范围明确了,代理就不会去扫整个目录。
第二招是“先定位再动手”。对于跨文件的任务,先去让代理列清单(只读模式),确认清单没问题,再让它按清单逐个改。这样每一轮的上下文都只包含当前文件,而不是整个仓库。
第三招是“清理噪音”。测试输出、构建日志、依赖锁文件这些东西 token 密度极高但信息量很低,在提示词里明确说“跳过 node_modules、dist、*.lock 文件”,能省下相当可观的输入量。
text复制# 放在 AGENTS.md 里,让所有任务都生效
## 上下文规则
- 不要读取 node_modules/、dist/、.venv/、*.lock 文件
- 修改代码时只读取目标文件及其直接依赖,不要全仓库扫描
- 遇到超过 500 行的文件,先输出函数清单,不要一次性全文读取
6.3 缓存友好的提示词结构
前面提过缓存命中的重要性,这里给一个具体的结构建议。把提示词分成三段,从上到下依次是:固定不变的规则、很少变化的项目背景、每次都不同的具体任务。前两段天然适合缓存。
text复制[段一:固定规则,永不改动]
你是编码助手。只输出补丁和必要说明。所有代码带类型注解。
禁止修改 migrations/、.env、CI 配置。
[段二:项目背景,随版本更新]
技术栈:Python 3.11,pytest,ruff。
测试命令:pytest -q。
代码规范:函数必须有 docstring。
[段三:本次任务,每次不同]
任务:为 divide 补充除零处理并加测试。
反过来,如果把“本次任务”放在最前面,后面跟着固定规则,那每次请求的前缀都不一样,缓存基本不可能命中。这个顺序差异看起来很小,账单上的差别可能是一倍。
6.4 任务分级与模型路由
不是所有任务都值得用最强的模型。机械性任务——改日志格式、补类型注解、批量重命名、把旧 API 调用换成新 API——用能力中等的模型完全够用。真正需要强模型的是那些涉及架构判断、复杂 bug 定位、跨模块重构的任务。
按这个思路做一个简单的路由,代码量不大但效果明显:
python复制# 文件路径:scripts/router.py
TASK_ROUTING = {
"rename": "cheap-model-id",
"type_annotation": "cheap-model-id",
"log_format": "cheap-model-id",
"bug_hunt": "strong-model-id",
"refactor": "strong-model-id",
"architecture": "strong-model-id",
}
def pick_model(task_type: str) -> str:
"""根据任务类型选择模型,未知类型回退到强模型。"""
return TASK_ROUTING.get(task_type, "strong-model-id")
路由表要落在配置里,不要写死在业务代码里,方便随时调整。另外建议加一条兜底规则:拿不准的任务类型一律走强模型。省钱的收益远小于“改错了要重来一遍”的损失。
7. 常见问题与排查思路
实际使用中遇到的问题,八成集中在这几类。按下面的表先定位方向,再往下看详细说明。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
安装后 codex 命令找不到 |
npm 全局 bin 目录不在 PATH | 查 npm config get prefix,把 bin 加进 PATH |
| 启动报语法错误 | Node 版本过低 | 切换到 LTS 版本后重装 |
| 认证失败或 401 | 密钥未注入或已失效 | 检查环境变量、重新登录 |
| 代理反复读同一个文件 | 上下文被截断,丢失了记忆 | 缩小任务范围,分批处理 |
| 账单突然飙升 | 大目录扫描、缓存未命中 | 用账本定位到具体任务,加范围限制 |
| 生成的代码跑不起来 | 缺少依赖或环境不一致 | 先确认本地能跑通再让代理介入 |
| 改动范围失控 | 沙箱模式过宽 | 收紧到 workspace-write 并逐步放开 |
| 测试通过但线上出错 | 测试本身写得不对 | 人工审查测试断言,警惕占位断言 |
7.1 安装与命令找不到
这是新手最高频的卡点。现象是 npm install -g 显示成功,但敲 codex 提示 command not found。根本原因是 npm 的全局 bin 目录不在系统 PATH 里,常见于用 nvm 管理 Node 的环境,因为 nvm 会为每个 Node 版本维护独立的 bin 目录。
bash复制# 查找全局安装位置
npm config get prefix
# 确认可执行文件是否存在
ls "$(npm config get prefix)/bin" | grep codex
如果文件存在但命令找不到,把那个 bin 目录加进 PATH 即可。注意用 nvm 时切换 Node 版本后 PATH 会变,如果仍然找不到,先确认当前 node -v 是不是你安装时用的那个版本。
7.2 上下文丢失导致重复劳动
现象是代理明明已经读过某个文件,过两轮又去读一遍,或者改了 A 文件却忘了 B 文件里还有同样的函数。这类问题的根因通常是任务范围太大,中间某次工具调用的输出把上下文撑爆了,早期的内容被挤出去。
解决办法是按依赖关系拆任务,每个任务只处理一个模块,完成后提交一次,再开新会话处理下一个模块。把一个“改造整个项目”的大任务拆成十个“改造单个文件”的小任务,成功率会显著提升,总 token 消耗反而下降。
7.3 成本异常增长
账单突然涨,第一反应不应该是“单价变了”,而是“用量结构变了”。打开账本,按天看曲线,找到突增的那一天,然后回查那天的任务记录。常见的三种情况:某次任务忘了限制目录,代理扫了几个 G 的生成物;某种任务的重试次数从 1 次变成了 5 次,说明提示词被改坏了;缓存命中率从高位掉了下来,说明有人在提示词前面加了动态内容。
定位到之后,把对应的约束补进 AGENTS.md,让它在所有任务里生效,而不是靠人记住。约束写在文档里,比写在人的脑子里可靠得多。
7.4 生成代码的质量波动
同一类任务,有时候一次就对,有时候改五轮还不对。除了模型本身的随机性,最常见的原因是“任务描述里缺少验收标准”。如果只写“优化这段代码”,代理不知道什么叫“优化好了”,只能猜;如果写“消除所有除零和空序列场景,并让 pytest -q 通过”,它就有了明确的停止条件。
另一个原因是项目约定没写清楚。不同的项目对异常处理、日志格式、命名风格的要求差别很大,代理不可能自己猜出来。把这些写进 AGENTS.md,等于给它一份验收清单,波动会明显降低。
8. 最佳实践与工程建议
8.1 安全边界:默认最小权限
几条必须守住的规则,没有例外:
第一,沙箱模式默认从最严格开始。只有在明确需要写文件时才放开写权限,只在需要执行命令时才放开执行权限。习惯了“反正它很聪明不会乱来”的团队,早晚要经历一次误删。
第二,密钥永远不进仓库。.gitignore 里必须有 .env,CI 里用密钥管理功能注入,不要写进流水线 YAML。如果发现历史提交里混进了密钥,立刻轮换,不要只删文件了事——历史记录里还在。
第三,涉及数据库、生产配置、部署脚本的任务,一律在测试环境验证,且要求人工确认每一步。让代理直接对生产环境执行 DDL,这个风险不值得冒。
第四,对 rm -rf、git push --force、drop table 这类高危命令做显式拦截,写在 AGENTS.md 和沙箱配置里双重保险。
8.2 代码审查与测试策略
代理生成的代码必须走和人工代码一样的审查流程,不要因为“它看起来很规范”就跳过。审查时重点看三处:
看边界条件。这是代理最容易出错的地方,因为它倾向于实现“正常路径”,对异常输入的覆盖不够。除零、空集合、超长字符串、负数、并发修改,这些都是高频漏点。
看测试本身。代理有时会写出“能通过但没测到东西”的测试,比如只断言不抛异常,或者用 assert True 占位。审查时要确认每条断言都在验证一个具体行为。
看依赖变更。如果 diff 里出现了 pyproject.toml 或 package.json 的改动,务必确认新增依赖是必要的、版本是合理的、没有引入已知有问题的包。
8.3 团队协作与规范沉淀
把用过的好提示词沉淀下来,形成团队资产。一个典型的做法是在仓库里建 prompts/ 目录,按任务类型存放,每个文件配一句说明。
text复制prompts/
├── add-test.md # 为指定模块补充测试
├── migrate-api.md # 批量替换旧 API 调用
├── fix-lint.md # 按 lint 规则批量修复
└── review-diff.md # 审查当前分支的 diff
有了这些文件,新人不需要从零摸索怎么写提示词,直接引用即可。同时这也让成本变得可预测——同类任务用同一份提示词,token 消耗的波动会小很多。
另外建议定期复盘。每月看一次账本,问三个问题:哪些任务最费钱、这些任务值不值得、下个月能不能把其中一部分换成更便宜的模型或更清晰的提示词。这个复盘花不了多少时间,但能避免成本悄悄失控。
9. 继续深入的方向
如果你已经把上面的流程跑通了,接下来可以往几个方向深入。
第一个方向是本地化的质量门禁。把 lint、类型检查、单元测试串成一条命令,让代理在提交前必须通过,而不是靠人记得去跑。这条链路建好之后,代理产出的可用率会明显提升。
第二个方向是任务拆分能力的训练。同样一个需求,拆成三步做和一步做,成本和质量差别很大。可以刻意练习“先让代理出方案(只读),人工确认后再让它执行(可写)”这个两段式流程,养成习惯之后会省下大量返工。
第三个方向是建立自己的评测集。从你的真实仓库里挑 20 个有代表性的任务,每个任务记录“一次通过率”和“平均 token 消耗”。有了这个基线,任何模型或价格变化,你都能在半小时内算出对自己的实际影响,而不是去看别人的测评文章。
第四个方向是成本可视化。把第 5 节的账本脚本接上简单的图表,做成每日自动跑的报表,异常波动自动告警。这件事投入不大,但能让成本问题在变成事故之前被发现。
价格会变,模型会换,但“用真实数据衡量投入产出”这套方法不会过时。把度量做扎实了,后面无论出现什么新工具,你都能快速判断它值不值得接进来。
