上个月我把 Codex Agent 当主力用,Skills 生态刚起来那会儿什么都往里塞,高峰期 .codex/config.yaml 里注册了 34 个 Skill。然后月底一看账单——$187,比上个月翻了将近一倍。排查了两天才发现,有些 Skill 的 description 写得太模糊,导致 Agent 几乎每轮对话都会触发它们做一次"试探性调用",白白烧 token。这篇把我踩过的坑、最终留下的 7 个 Skill、以及 manifest.json 里最容易写错的字段全部摊开讲。

这篇适合谁

  • 已经在用 Codex Agent(或 Codex CLI)写代码,想搞清楚 Skills 配置格式的开发者
  • 装了一堆 Skill 但发现账单异常,想知道哪些配置会导致额外 token 消耗的人
  • 从 Cursor / Cline 转过来,对 Codex 的 config 体系还不熟的同学
  • 想用 o3 / o4-mini 跑 Agent 但怕成本失控的独立开发者

整体流程

  1. 安装并初始化 Codex CLI
  2. 理解 Skill 配置文件的四个核心字段
  3. .codex/config.yaml 中注册 Skills
  4. 验证 schema 是否合法(别等运行时才报错)
  5. 识别哪些配置会触发"隐性 token 消耗"
  6. 根据场景精简到真正有用的 Skill 集合
graph TD
 A[编写 Skill JSON] --> B[注册到 config.yaml]
 B --> C[验证 schema 合法性]
 C --> D{schema 合法?}
 D -->|是| E[Agent 运行时按 description 决策调用]
 D -->|否| F[报错 / 静默降级]
 E --> G[检查 token 消耗是否合理]

先说结论

Skill 名 留/删 原因
web_search ✅ 留 高频刚需,description 写精确后误触率 <5%
file_tree ✅ 留 零参数,几乎不消耗额外 token
run_tests ✅ 留 后端必备,handler 本地执行不走 API
git_diff ✅ 留 code review 场景核心
db_query ✅ 留 但要加 enum 限制表名,否则 Agent 会瞎猜
lint_fix ✅ 留 配合 eslint/ruff,handler 本地跑
deploy_preview ✅ 留 前端项目部署预览一键触发
translate_text ❌ 删 description 太泛,Agent 把所有含外语的上下文都往里塞
summarize_url ❌ 删 每次调用先抓网页再总结,单次 2000+ token
code_explain ❌ 删 和 Agent 本身能力重叠,纯浪费

前置步骤:安装 Codex CLI

如果你从 Cursor / Cline 转过来,Codex CLI 需要单独安装。官方安装方式:

npm install -g @openai/codex

安装完成后初始化项目配置:

cd your-project
codex init

这会在项目根目录生成 .codex/ 文件夹和初始 config.yaml。后续所有 Skill 配置都基于这个目录结构。具体安装要求和版本信息以 OpenAI 官方文档 为准。

第一步:理解 Skill 配置的四个核心字段

一个最小可用的 Skill 长这样:

{
  "name": "web_search",
  "description": "Search the web when user asks about events after 2024.",
  "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]},
  "handler": "./handlers/web_search.py"
}

四个字段缺一个都会炸。name 不填直接报错:

ValidationError: Skill 'name' field is required and must be a non-empty string

这个还好,至少报出来了。真正坑的是下面两个。

第二步:parameters 字段——符合 JSON Schema 规范,写错静默降级

parameters 必须是符合 JSON Schema 规范的对象(具体支持的特性以官方文档为准,并非所有 Draft 版本特性都被完整支持)。很多人(包括我)一开始写成这样:

"parameters": {"query": "string"}

跑起来不报错,但 Agent 调用时参数校验直接跳过,传什么进来都不拦。等你发现 query 收到了一个 number 类型,已经是运行时了。以下是示意性报错(实际报错格式以运行时输出为准):

Error: Skill schema mismatch - expected type 'string' but received 'number' for parameter 'query'

正确写法必须有顶层 "type": "object"properties

"parameters": {
  "type": "object",
  "properties": {"query": {"type": "string"}},
  "required": ["query"]
}

我现在写完 Skill 配置都会跑一遍本地校验:

import jsonschema
schema = {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}
jsonschema.validate({"query": "test"}, schema)
print("Schema valid ✓")

两秒钟的事,省得后面排查半天。

第三步:handler 路径——相对路径的基准目录不是你想的那个

handler 字段填相对路径时,基准是项目根目录(和 .codex/ 文件夹同级),不是 config 文件所在目录。我第一次配的时候写成 "handler": "../handlers/search.py",以为相对于 .codex/config.yaml,结果报错(示意):

Error: Cannot find skill handler at path './handlers/search.py': ENOENT: no such file or directory

改成 "handler": "./handlers/search.py" 就好了。这个报错信息还算友好,但如果你一次注册 10 个 Skill,它只报第一个失败的,剩下的静默跳过——你以为都注册成功了,其实只有一半在工作。

改完 config 后建议跑一次完整的加载验证,确认所有 Skill 都正确注册。具体调试命令以你所用版本的 CLI 文档为准。

第四步:description 写太泛 = 账单翻倍(这是我最大的坑)

这才是重点。Codex Agent 决定"要不要调用某个 Skill"完全靠 description 字段——它会把所有已注册 Skill 的 name + description 拼进 system prompt,让模型自己决策。

问题来了:每轮对话,所有 Skill 的 description 都会被塞进上下文。

粗略估算一下:34 个 Skill,每个 description 平均 80 字符,纯文本部分约 2720 字符,按 4 字符/token 估算约 680 token。加上 name 字段、JSON 结构开销、以及 system prompt 的其他固定内容,实际输入 token 消耗会更高,但具体数字取决于你的 description 实际长度和模型的 tokenizer,建议用 OpenAI Tokenizer 自行测量。

更狠的是,如果 description 写得模糊(比如 "Translate text between languages"),Agent 会在任何出现非英语内容的上下文里触发调用——即使用户根本没让它翻译。每次触发又是一轮 function call 的 token 往返。

官方建议 description 控制在 50-150 字符,1-3 句话,明确写出触发条件。

反面教材:

"description": "Summarize content from URLs"

正面教材:

"description": "Fetch and summarize a URL. Only use when user explicitly provides a URL and asks for a summary."

加了 "Only use when" 这个限定后,我的 summarize_url 误触率从每天 40+ 次降到了 3 次。但最后我还是删了它,因为每次调用要先抓网页内容再走一轮 LLM 总结,单次消耗 2000-3000 token。

第五步:在 config.yaml 中批量注册

.codex/config.yaml 放在项目根目录下的 .codex/ 文件夹,和 .git 同级:

skills:
  - ./skills/web_search.json
  - ./skills/file_tree.json
  - ./skills/run_tests.json
  - ./skills/git_diff.json
  - ./skills/db_query.json

如果 config.yaml 本身格式写错(比如缩进不对、冒号后缺空格),会直接解析失败:

ConfigError: config.yaml parse error at line 12: unexpected token

看到 parse error 就去检查 YAML 格式,常见问题是缩进用了 tab 而不是空格,或者列表项前缺少 -

我最终留下的 7 个 Skill 配置要点

简单列一下每个的 description 写法核心:

web_search:限定"用户问 2024 年之后的事件时才触发"

file_tree:零参数 Skill,parameters 写空对象 {"type": "object", "properties": {}},几乎不消耗额外 token

run_tests:handler 指向本地 pytest/jest,不走 API,成本为零

git_diff:限定"用户提到 diff/review/变更时触发"

db_query:parameters 里用 enum 限制可查询的表名,防止 Agent 瞎猜表结构

"properties": {
  "table": {"type": "string", "enum": ["users", "orders", "products"]},
  "query": {"type": "string"}
}

lint_fix:handler 本地跑 ruff/eslint,不消耗 API token

deploy_preview:限定"用户明确说部署/预览时才触发"

不同场景怎么选

后端为主的项目:留 run_tests + git_diff + db_query,其他按需。测试和 lint 类 Skill 的 handler 跑本地命令,成本几乎为零。

前端项目:留 file_tree + lint_fix + deploy_preview。前端文件多且碎,file_tree 能帮 Agent 快速定位组件位置。

个人开发者控成本:Skill 总数控制在 5 个以内。每多一个 Skill 的 description 就多一点固定的输入 token 开销。预算紧张的话,config 里把模型切到 o4-mini(输入约 $1.10/M token,以官方最新定价为准),牺牲一点推理能力换成本。

团队协作:把 Skill 配置文件放进 git 仓库统一管理。如果你们使用 OpenRouter 等 API 聚合网关,通常可以按 API Key 维度查看各成员的 token 消耗明细,月底对账时能直接定位到是哪个 Skill 在烧钱。具体接入方式参考所用网关的官方文档,一般只需在初始化时修改 base_url

client = openai.OpenAI(
  api_key="your-key",
  base_url="https://your-gateway.example.com/v1"  # 替换为实际可用的网关地址
)

踩坑记录 / 常见问题 FAQ

Q: Codex Skills 和 OpenAI Function Calling 有什么区别?

Skills 是更高层的抽象,内置了执行环境和代理调度逻辑。Function Calling 是底层 API 机制,Skills 底层基于它实现,但多了 handler 执行、schema 校验、调度决策这些层。可以理解为 Skills = Function Calling + 执行引擎 + 触发决策。

Q: .codex/config.yaml 放在哪个目录?

项目根目录下的 .codex/ 文件夹,和 .git 同级。不是 home 目录,不是 ~/.config/,就是项目根。

Q: Skill 的 description 写多长合适?

官方建议 50-150 字符,1-3 句话。关键是写清楚触发条件("Only use when..."),不然 Agent 会在不该调用的时候调用,白烧 token。

Q: 怎么调试 Skill handler 执行失败?

查看 CLI 的详细日志输出(具体参数以你所用版本文档为准)。Python handler 支持 async def,Node.js handler 可以返回 Promise。handler 里有 stderr 输出也会被捕获到日志里。

Q: 注册了很多 Skill 但感觉 Agent 从来不调用某个,怎么回事?

大概率是 description 和当前对话上下文匹配度太低,模型判断不需要触发。也可能是 schema 格式有问题导致静默降级——通过详细日志确认一下是否真的加载成功了。

Q: 能不能限制某个 Skill 的调用频率?

目前原生不支持 rate limit 配置。我的做法是在 handler 里自己加计数器,超过阈值直接返回空结果。不优雅但管用。

小结

折腾了大半个月,结论就是:Skill 不是越多越好,5-7 个精准配置的比 30 个模糊的强太多。核心三点——description 写明触发条件、parameters 用严格的 JSON Schema 格式、handler 能跑本地的就别走 API。成本敏感的话,先把 Skill 总数砍到个位数,账单下降会很明显。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐