☰
Codex本地Agent配置详解:模型、TOML与AGENTS.md优先级
2026/10/1 7:12:04 网站建设 项目流程

在本地跑 Codex 的朋友应该都有同感:装好 CLI 只是开始,真正让它按你的想法干活,绕不开模型配置、TOML 文件和 AGENTS.md 这三件事。我最初只是想给 Codex 换一个成本更低的模型,结果顺手把本地自定义 Agent 的整套配置逻辑摸了一遍,期间还踩了 "cc switch local proxy failed while handling codex endpoint /responses" 这个坑,最后才搞明白 model、provider、wire_api 三者之间是怎么协同的,AGENTS.md 的优先级规则又是怎么定死的。这篇就把配置项之间的关系、第三方模型的接入流程、以及本地指令的优先级一次讲透。适合刚接触 Codex CLI、或者已经能跑通但想深度定制的朋友,也欢迎已经踩过坑的来对照验证。

1. 先搞清楚"本地自定义 Agent"到底自定义了什么

1.1 Codex CLI 和网页版 Agent 的本质区别

Codex CLI 是跑在终端里的编码代理,往大了说,它就是你本地自定义 Agent 的载体。网页版 Agent 的模型、工具、运行环境全在服务端,你只能在界面里发指令,很难干预它的决策细节;而 Codex CLI 的工作循环是在本地发生的——它读取本地文件、执行终端命令、按指令规划步骤,每一步你都能看到。这个差别意味着"自定义"的空间完全不同:你可以换掉它的模型、定义它的行为规范、甚至通过配置文件给它接上不同的模型供应商。换句话说,Codex CLI 的本地属性让它从"一个 AI 工具"变成了"一台可以由你装配的 Agent 工作机"。

不过这里要泼一盆冷水:本地自定义并不等于无限自由。Codex CLI 的输出质量 = 模型能力 + 指令约束 + 环境权限 三者相乘。模型换得再好,如果 AGENTS.md 写得一团糟,它照样会在代码风格、文件组织上放飞自我;反过来,你再怎么精心设计指令,基础模型能力不够,推理照样会跑偏。所以下面聊配置的时候,我会一直强调"模型、指令、权限"要一起看。

1.2 本地配置的三层结构

具体到落地,本地自定义 Agent 主要动三样东西:

  • 用户级全局配置:~/.codex/config.toml,存放模型、API 密钥、默认行为等,所有项目共用。
  • 项目级配置:项目根目录下的codex.toml或.codex/config.toml,专属于当前仓库,可以用--config显式指定。
  • 指令文件:AGENTS.md,一份给 Agent 看的"员工手册"。全局版放在~/.codex/AGENTS.md,项目版放在仓库根目录,子目录里也可以放小范围的补充版。

初次接触很容易把这三层搞混,尤其是项目级配置和指令文件的边界。我自己的理解是:config.toml 管的是"用什么模型、连哪个 API、权限怎么给",AGENTS.md 管的是"面对这个项目时,你的工作方式、代码规范、哪些事坚决不能做"。一个是硬件装配,一个是价值观输出。

1.3 你能自定义的四个变量

如果把这套体系再抽象一层,任何本地 Agent 其实都在你手里这四个变量上滑动:

  1. 模型:用 OpenAI 官方模型,还是 DeepSeek、Qwen、Kimi 这些兼容 OpenAI 协议的第三方模型。
  2. 供应商:模型跑在哪家的 API 上,base_url 指向哪里、拿哪个环境变量做密钥。
  3. 指令:通过 AGENTS.md 注入项目背景、编码规范、行为边界。
  4. 工作流:CLI 的参数、沙盒权限、MCP 工具、环境变量传递。

这四个变量排列组合起来,就能得到差异很大的行为。我见过有人把 Codex 配成"只读代码审查员",也有人配成"能自动跑测试并修复失败用例的流水线工人",还有人在同一台机器上同时维护三套 config,对应不同项目风格。这些都不是魔法,就是把上面四个变量的优先级和值调好了而已。

2. config.toml 里能写什么:model、model_provider 与 wire_api 的三方关系

2.1 配置文件怎么被加载

Codex CLI 默认读~/.codex/config.toml。如果你在项目根目录放了codex.toml,CLI 会把它和用户级配置做合并,项目级覆盖用户级;命令行参数优先级最高,能直接盖过文件里的同名字段,比如codex --model gpt-5会覆盖配置文件里的 model 字段。这个优先级顺序我实测是稳定生效的,排查"我改了配置怎么不生效"的问题,第一件事永远是确认有没有更高层级的配置在覆盖你。

小技巧:不确定当前生效的是什么,跑codex --show-config或者看 CLI 启动时的调试输出,它会打印最终合并结果,比自己翻文件猜靠谱得多。

2.2 最简配置:一个 model 字段就够了

如果你用官方模型,不走任何第三方,config.toml 里甚至可以只有一行:

model = "gpt-5"

登录方式也简单:先codex login走 OpenAI 账号,或者用环境变量OPENAI_API_KEY。但相信我,只要是折腾本地自定义 Agent 的人,用不了两天就会碰到"我想换更便宜的模型"这个需求,于是 model 字段背后的坑就来了。

2.3 model_provider 打开的自定义空间

换第三方模型的时候,光写model = "deepseek-chat"是不够的。Codex 需要一个"供应商描述"来知道这个模型该往哪里发请求、用哪个密钥、走什么协议。在 config.toml 里的写法是:

model = "deepseek-chat" model_provider = "deepseek" [model_providers.deepseek] name = "deepseek" base_url = "https://api.deepseek.com/v1" env_key = "DEEPSEEK_API_KEY" wire_api = "chat"

这里有几个字段要逐个说清楚:

  • name:供应商名字,自己起,只要能对应上就行。
  • base_url:API 的根地址。Codex 会在后面拼接具体的端点路径。
  • env_key:告诉 Codex 从哪个环境变量读取密钥,而不是硬编码在配置文件里。这是好习惯,因为 config.toml 一旦误提交到 Git 仓库,密钥就全裸奔了。
  • wire_api:这是最容易踩坑的字段,也是我接下来要单独讲的。

2.4 wire_api:responses 还是 chat,决定你能不能连通

OpenAI 自己的模型走的是 Responses 协议,端点通常是/responses;而绝大多数第三方模型(包括 DeepSeek、很多开源模型的托管 API)只兼容 OpenAI 的 Chat Completions 协议,端点是/v1/chat/completions。

Codex 默认假设模型供应商支持 Responses 协议。如果你没设置wire_api,它会按/responses路径发请求,第三方服务根本不认识这个路径,直接报错。解决办法就是显式声明协议:

wire_api = "chat"

设置之后,Codex 会改用 Chat Completions 协议和第三方 API 通信。

我把这两者的区别整理成了表格,方便对照:

项目Responses 协议Chat Completions 协议
典型端点/responses/v1/chat/completions
原生支持OpenAI 官方模型大多数兼容 OpenAI 接口的第三方
配置字段wire_api = "responses"wire_api = "chat"
常见场景默认配置、官方模型DeepSeek、Qwen 等第三方接入

简而言之,model 决定"选谁",model_provider 决定"去哪连",wire_api 决定"用什么语言说话"。三者配合错任何一个,结果都是连不通或者返回格式解析失败。

3. 接 DeepSeek 的完整链路:配置、报错,再到 cc-switch 管理

3.1 为什么我选 DeepSeek 作为本地 Agent 的默认模型

选择第三方模型的目的不外乎两点:成本、可用性。DeepSeek 的 token 单价低,中文和代码能力在开源模型里属于第一梯队,上下文窗口也够日常项目用。对我这种要长时间开着 Codex 跑任务的人来说,成本优势非常明显。另外,把模型从官方账号切到第三方 API,登录态的管理也更直接,不存在账号登录过期导致的工作流中断。

要特别说明的是:第三方模型不是免费的午餐。DeepSeek 虽然便宜,但它在某些复杂推理任务上和 OpenAI 旗舰模型还有差距。我的建议是,粗活累活(代码格式整理、按模板生成文件、测试用例补写)交给第三方,高难度重构再切回官方模型。这就是为什么后面要聊 cc-switch 这种配置切换工具。

3.2 从零到能对话的完整配置

接 DeepSeek 的步骤其实很短,但每一步都有讲究。这是我的完整操作:

  1. 从 DeepSeek 开放平台拿到 API key。
  2. 写入环境变量:export DEEPSEEK_API_KEY=你的key(建议写进 shell 的配置文件,比如~/.zshrc)。
  3. 在~/.codex/config.toml里配置供应商和默认模型:
model = "deepseek-chat" model_provider = "deepseek" [model_providers.deepseek] name = "deepseek" base_url = "https://api.deepseek.com/v1" env_key = "DEEPSEEK_API_KEY" wire_api = "chat"
  1. 随便在终端跑一个任务:cd /tmp && codex "用 Python 写一个读取 CSV 的小工具"。

正常情况下,看到 Codex 开始拆解任务、逐文件操作,就算通了。但如果你和我一样,是在已有其他配置的基础上改的,大概率会撞上下面这个报错。

3.3 "cc switch local proxy failed while handling codex endpoint /responses" 完整排查

这是我在这篇文章里最想写的坑。现象是:切换配置之后,Codex 每次发请求都在处理/responses端点时失败,报错里还带了一句 "cc switch local proxy failed"。

先说报错里的 "local proxy" 指什么:它一般指你配置里指定的本地端点转发服务,也就是 base_url 指向的那个进程。很多人在自建 API 网关或者调试流量时会引入一个本地转发层,Codex 的请求先到这个本地服务,再由它转给真正的模型 API。这个转发层一旦没起来、端口配错、或者它自己不认/responses路径,你就会看到这类报错。

我当时的排查链路是这样的,建议你也按顺序来:

  1. 确认 base_url 指向的服务在线。如果 base_url 写的是http://127.0.0.1:端口,先看那个进程有没有在跑。我那次就是切配置时把端口改掉了,转发服务听着老端口,Codex 自然连不上。
  2. 直接 curl 验证端点。curl http://127.0.0.1:端口/responses看看返回什么。这能把"网络不通"和"协议不匹配"快速分开。
  3. 检查 wire_api 是否匹配供应商。Codex 默认走responses路径,如果你的转发层背后是只支持 chat 的第三方 API,就得在 config.toml 里写wire_api = "chat"。否则即使服务在线,它也不认识/responses这个请求。
  4. 看转发层日志。本地转发服务通常会打印收到的请求路径和转发目标,一眼就能看出 Codex 实际请求的是哪个端点、转发目标是不是配错了。

我这次问题最终落在 wire_api 上。配置里残留的旧供应商声明把协议撑成了默认的 responses,而本地转发层和后端都只支持 chat,请求自然死在中途。把wire_api = "chat"显式写好之后,报错立刻消失。另外也建议给转发层加一条路由,把/responses请求转换成 chat 协议的请求,这样两边都舒服。

一个小提醒:如果你根本没有自建任何转发服务,却在 config.toml 里看到了127.0.0.1开头的 base_url,那大概率是从某个教程或切换工具里继承来的残留配置,直接改成官方 API 地址即可。

3.4 用 cc-switch 管理多套模型配置

本地自定义 Agent 一旦跑起来,你会发现自己可能有两三套配置:一套连官方、一套连 DeepSeek、可能还有一套连内部模型。手动改 config.toml 很容易改乱,这时候 cc-switch 这类工具就有用了。

cc-switch 是一个管理 Codex / Claude Code 配置切换的小工具,它做的事情本质上是:把你要的 provider、model、base_url、env_key 组合存成一套"配置预设",需要时一键切换,帮你重写对应的配置文件。用完它之后,你会感觉"换模型"从编辑文件变成点按钮。

使用时有两点注意。第一,cc-switch 切换时会重写~/.codex/config.toml,如果你在里面写了自定义的复杂配置(比如多个 provider 或沙盒设置),切换前最好先备份,或者把自定义部分也补录进 cc-switch 的预设里。第二,切换完务必检查最终生成的配置里有没有多余的 base_url 或没用的环境变量残留,避免出现上一节那种端口、路径不匹配的诡异报错。

4. AGENTS.md 的优先级:项目规矩和全局规矩打架时听谁的

4.1 AGENTS.md 是给 Agent 看的"员工手册"

AGENTS.md 的作用不是给人类看的文档,而是给 Agent 读的指令文件。你希望 Codex 在项目里怎么工作、遵守什么规范、什么操作绝对禁止、常用命令是什么,都可以写进去。它和 README.md 的区别就在这里:README 描述项目"是什么",AGENTS.md 描述 Agent 在项目里"怎么干活"。

Codex 启动时会自动加载 AGENTS.md,不需要你在提示词里引用它。官方约定的路径有全局和项目两种:

  • 全局:~/.codex/AGENTS.md,作用于所有项目。
  • 项目:仓库根目录的AGENTS.md,以及各级子目录里的AGENTS.md。

4.2 加载顺序和优先级规则

关于 AGENTS.md,最容易被误解的就是优先级问题。我最早以为"文件越多越好,所有规则都会生效",实践下来完全不是这样。Codex 的加载规则大致是:

  1. 从当前工作目录开始,逐级向上找 AGENTS.md,然后再加上用户全局的 AGENTS.md。
  2. 多条规则之间是"合并"关系,并不冲突的约束会全部生效。
  3. 同一条约束出现冲突时,越靠近当前工作目录、描述越具体的文件优先。也就是说,项目根目录的规则优先生效于~/.codex/AGENTS.md;子目录的规则又优先生效于项目根目录。

这个"就近优先"的原则非常好用。举个例子:全局 AGENTS.md 里写死"Python 代码统一用空格缩进",但某个项目根目录的 AGENTS.md 规定"该项目统一用 Tab 缩进",那么 Codex 在这个项目里就会按 Tab 干活,因为它读到的最近指令把更通用的指令覆盖掉了。

但要注意,覆盖不是无条件的。涉及安全红线的规则(比如"不得直接执行删除关键目录的命令"),即便只出现在某一个层级的 AGENTS.md 里,Codex 也会很谨慎地遵守。这类约束不会被"更大范围但更弱的规范"冲掉。我的理解是:安全提醒属于模型内置的强约束,文件里的普通风格偏好在弱约束层面互相覆盖。

4.3 冲突实例:全局说左,项目说右

再补一个实战里经常看到的冲突场景。假设全局 AGENTS.md 写了:

IMPORTANT - 所有提交信息必须使用英文

而某仓库的 AGENTS.md 写了:

- commit message 统一使用中文描述

结果会怎样?实测下来,项目级会赢,Codex 提交时写中文。因为项目文件离任务更近,对整个任务的上下文有更强的约束力。如果你想让某条规则无论如何都生效,我的经验是:在文字层面做足功夫,比如用大写IMPORTANT开头、写明"这是不可覆盖的强制规则",并且同时在项目级文件里再写一遍。多层级重复声明,比单层级的权重声明更可靠。

4.4 一份能让你少走弯路的 AGENTS.md 模板

说了一堆规则,不如给一份我能直接复制的模板。我给常用项目写 AGENTS.md 时,结构基本固定:

# AGENTS.md ## 项目背景 这是一个基于 Python 3.12 的 CLI 工具仓库,使用 uv 管理依赖。 ## 编码规范 - Python 代码使用 4 空格缩进,行宽 100。 - 类型标注必须完整,函数必须有 docstring。 - 禁止引入未经验证的新依赖。 ## 常用命令 - 运行测试:uv run pytest tests/ - 代码格式化:uv run ruff format . - 类型检查:uv run mypy src/ ## 重要约束 IMPORTANT - 不允许修改 tests/ 下的文件来迎合实现。 - 任何重构必须先运行测试,再给出结论。 - 删除文件前必须列出来交给用户确认。

这份模板里最重要的是最后一段"重要约束"。别小看它,模型对"先运行测试再下结论""删除前确认"这类强约束的遵守程度,决定了 Agent 到底是帮你干活还是给你添乱。模板本身不复杂,但每次花五分钟把这段写好,后面能省下大量收拾残局的时间。

5. 组装一个完整的本地实战 Agent:从配置文件到运行复盘

5.1 目标:做一个"代码审查 Agent"

理论讲了不少,做一个完整的实战项目才有感觉。我最近给一个旧仓库定制了一个"代码审查 Agent",需求是:审查未提交的 diff,按照仓库既有风格提意见,最终输出一份 markdown 报告。这个过程正好把前面讲到的模型、TOML、AGENTS.md 全部用上。

分工是这样的:

  • 模型和供应商:审查任务不需要特别深的推理,成本要低,选 DeepSeek。
  • AGENTS.md:定义审查清单、禁止事项、报告输出格式。
  • config.toml:把模型、供应商、协议串起来。
  • 命令行:用codex "开始审查"这类固定话术触发。

5.2 完整配置与启动方式

~/.codex/config.toml长这样:

model = "deepseek-chat" model_provider = "deepseek" [model_providers.deepseek] name = "deepseek" base_url = "https://api.deepseek.com/v1" env_key = "DEEPSEEK_API_KEY" wire_api = "chat"

项目根目录的AGENTS.md浓缩版:

# AGENTS.md ## 审查任务说明 审查每次请求时的 git diff,重点关注: - 变更是否符合项目现有架构分层。 - 是否缺少单元测试,或测试是否只覆盖正例。 - 是否引入不必要的全局状态。 ## 上报格式 - 问题清单按 严重 / 建议 两级分类。 - 每条意见必须给出文件路径与行号。 - 结尾给出总体结论:可以合并 / 需要修改后合并。 ## 重要约束 IMPORTANT - 不要直接修改代码,只输出审查报告。 - 如果 diff 为空,直接说明无事可做,不要猜测。

启动方式很简单:

export DEEPSEEK_API_KEY=xxx cd 项目目录 codex "审查当前工作区的改动,输出 markdown 报告"

5.3 运行复盘:我实际撞到的几个问题

配置跑通不等于万事大吉。我把实测过程中最有代表性的问题列出来,这些都是你们大概率也会碰到的。

"codex auth token is unavailable"

这个报错几乎可以肯定是认证信息没被读到。排查顺序:先确认环境变量真的存在(echo $DEEPSEEK_API_KEY),再看 config.toml 里的env_key名字是否和它完全一致,最后确认没有别的配置把环境变量覆盖掉。我自己有一次是大小写写错了,Codex 读不到,折腾了十分钟。

"agent execution terminated due to error"

这个通常是 Agent 在执行某个终端命令时出错并终止了任务,比如 pytest 找不到模块、目录权限不足。解决办法是看它执行到哪一步失败了,然后给它更明确的工作目录或恢复指令。不要急着换模型,多半不是模型问题,而是任务上下文没说清。

"显示更新 agent 沙盒"

Codex 的沙盒机制如果提示更新,把 CLI 升级到最新版就行。沙盒的作用是隔离 Agent 执行的命令,避免它对系统造成不可控的影响,建议日常使用保持沙盒开启。在 config.toml 里可以设置权限边界,一般用"只读"或"工作区可写"两档就够了,除非你明确需要 Agent 安装依赖、修改系统配置,否则不要轻易给完全访问权限。

并发和限流问题

本地同时开多个 Agent 会话时,第三方 API 通常不限制并发,但要注意两点:一是 token 消耗会快速上涨,二是部分 API 有 RPM/TPM 限制,容易在峰值时触发 429。我的做法是写一个非常小的队列脚本,一次只让两个会话在跑,高优先级的任务插队。对多数人的日常使用,人工控制"一次只开一两个会话"反而是最有效的。

5.4 稳定运行后的参数调优

跑稳定之后,我开始做减法。第一个发现是 AGENTS.md 别写太长。模型确实会读,但动辄几十条的规范会稀释重点,它更倾向于遵守开头和结尾的规则。我最后把审查项目的 AGENTS.md 压缩到二十多行,效果反而更好。

第二个发现是让 Agent 先做计划再动手。在任务里加一句"先列出执行步骤,分步进行",比直接让它一股脑做完要稳定得多。这个习惯对推理能力中等偏上的模型都有效,DeepSeek 也不例外。

第三个发现是本地自定义 Agent 的稳定性瓶颈往往在 API 端点的稳定性,而不是模型本身。响应超时、连接重置这类问题,重试一两次通常就好了。代码审查这种任务重试成本很低,放心大胆重跑。

折腾完这一整套,我的感想是:Codex 本地自定义 Agent 的门槛不在模型选择,而在配置体系的完整性。TOML 决定它能不能连上正确的模型,AGENTS.md 决定它会不会用正确的方式工作,优先级规则决定两者冲突时谁说了算。这三件事理顺了,自定义 Agent 其实就是把"好用的模型 + 清晰的指令 + 合适的权限"组合起来的体力活。

我现在的习惯是:每接入一个新模型,先写一小段基线问题集去试,再用 cc-switch 固化一套配置;每进入一个新项目,先把项目 AGENTS.md 写好再让 Agent 介入。如果你也打算深度定制,建议从一个小而具体的场景(比如"帮我按规范审查代码")入手,先跑通,再慢慢加规则。这套流程我已经用了几个项目,稳定性和可控性都达到了能日常使用的水平。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询