1. 从零认识 openrig:它到底解决什么问题
第一次看到 openrig 这个名字,很多人会以为是某个硬件项目,毕竟 "rig" 这个词在英文里常指设备支架或者矿机机架。但如果你最近在折腾 Claude Code、Codex 这类命令行 AI 编程工具,大概率已经在某个 issue 或者讨论帖里见过它。openrig 本质上是一个面向 AI 编程助手的配置编排层,它把散落在各个工具里的模型接入、代理转发、会话管理、终端复用这些琐碎事情,收敛成一套可版本化、可复用的 YAML 配置。
我最初接触它是因为一个很具体的痛点:手上同时跑着 Claude Code 和 Codex 两个 CLI,一个要接本地 LM Studio 的模型,一个要接 DeepSeek 的 API,每次换项目就得手动改环境变量、改配置文件,改完还经常忘记哪个项目用的是哪套。更麻烦的是,这两个工具对配置文件的路径、字段命名、优先级规则都不一样,Claude Code 认~/.claude/settings.json,Codex 认~/.codex/config.toml,一旦涉及本地代理转发,还得再叠一层端口映射。openrig 的出现就是为了把这一堆东西抽象出来,用一份 YAML 描述"我要什么模型、走什么通道、在哪个项目里生效",剩下的交给它去分发。
它适合谁?如果你只是偶尔用一下 Claude Code 写个脚本,那确实没必要上这套东西。但如果你符合下面任意一条,openrig 值得花半小时研究:同时使用两个以上 AI 编程 CLI;需要在本地模型和云端模型之间频繁切换;团队里多人共用一套模型接入规范;想把 AI 助手的配置纳入 Git 管理。这几种场景下,手工维护配置的边际成本会迅速超过学习成本。
需要说明的是,openrig 目前并不是一个官方标准,社区里存在多个同名或近似的实现,有的偏向 tmux 会话编排,有的偏向代理路由。下面我讲的这套思路,是基于我实际用下来最顺手的一种组合方式,核心是把 YAML 作为唯一事实来源,tmux 作为运行时载体,代理层作为模型接入的统一出口。你可以根据自己的工具链做裁剪。
2. 核心设计思路:为什么是 YAML 加 tmux 加代理层
2.1 为什么选 YAML 而不是 JSON 或 TOML
配置文件格式的选择看似小事,实际影响很大。Claude Code 用 JSON,Codex 用 TOML,这两种格式各有拥趸,但当你需要写注释、需要多行字符串、需要嵌套结构的时候,YAML 的优势就出来了。比如你要描述一个模型接入配置,里面包含 base_url、api_key 引用、模型名、超时时间、重试策略,用 YAML 写出来是这样的:
models: local-qwen: provider: openai-compatible base_url: http://127.0.0.1:1234/v1 api_key_env: LOCAL_KEY model: qwen2.5-coder-32b timeout: 120 retries: 2 deepseek-chat: provider: openai-compatible base_url: https://api.deepseek.com/v1 api_key_env: DEEPSEEK_KEY model: deepseek-chat timeout: 60同样的内容用 JSON 写,不能加注释,多行处理别扭;用 TOML 写,嵌套层级一深就变得冗长。YAML 的缩进敏感确实容易踩坑,但配合编辑器的 YAML 插件和 schema 校验,这个问题基本可以忽略。更重要的是,YAML 天然适合做"配置的配置",也就是元配置,你可以用一份 YAML 生成多份下游工具的配置文件,这在多工具协同场景下非常关键。
2.2 tmux 在这里扮演什么角色
很多人会问,配置管理就配置管理,为什么要扯上 tmux?答案在于 AI 编程 CLI 的运行形态。Claude Code 和 Codex 都是长驻进程,一次会话可能持续几十分钟甚至几个小时,中间你要切换窗口去看代码、跑测试、查日志。如果每个 CLI 都开一个独立终端窗口,很快桌面就乱了。tmux 的价值在于它把"会话"和"窗口"解耦,你可以用一个 tmux session 承载多个 pane,每个 pane 跑一个 AI 助手或者一个监控命令。
openrig 和 tmux 结合的方式,通常是通过tmuxp或者自定义脚本,根据 YAML 里定义的布局自动拉起会话。比如你定义了一个叫ai-dev的 session,里面左边 pane 跑 Claude Code,右边 pane 跑 Codex,下面再开一个 pane 跑日志监控。一条命令就能把整个工作环境恢复出来,这对于每天要重复同样操作的人来说,节省的时间非常可观。
2.3 代理层为什么不可省略
代理层是整套方案里最容易被低估的部分。表面上看,Claude Code 和 Codex 都支持直接配置 base_url,似乎不需要额外代理。但实际用起来,代理层解决了三个硬问题。
第一是协议差异。Claude Code 走的是 Anthropic 的 messages 格式,Codex 走的是 OpenAI 的 responses 格式,而本地模型或者第三方 API 往往只兼容其中一种。代理层做协议转换,让上游工具无感知。第二是密钥管理。你肯定不想把 API key 明文写在每个工具的配置里,代理层可以统一从环境变量或者密钥管理服务读取,下游工具只认本地地址。第三是流量观测。代理层可以记录每个请求的耗时、token 消耗、错误码,这些数据对于排查问题和成本控制非常有用。
社区里常见的做法是用 LiteLLM 或者 one-api 这类项目做代理,它们都支持 YAML 配置,和 openrig 的思路天然契合。你只需要在 openrig 的 YAML 里声明"这个模型走本地代理的哪个端口",剩下的交给代理层处理。
3. 实操落地:从安装到跑通第一条链路
3.1 环境准备与依赖安装
在开始之前,先把基础环境理清楚。我假设你用的是 macOS 或者 Linux,Windows 用户建议走 WSL2,因为 tmux 在原生 Windows 上体验很差。需要安装的东西不多:tmux、Python 3.10 以上、以及你选择的代理工具。
# macOS brew install tmux python@3.11 # Ubuntu/Debian sudo apt update sudo apt install -y tmux python3.11 python3.11-venv # 安装代理层,这里以 LiteLLM 为例 pip install 'litellm[proxy]'Claude Code 和 Codex 的安装各自有官方文档,这里不展开。需要提醒的是,Claude Code 对 Node 版本有要求,建议用 nvm 管理 Node 版本,避免和系统自带的冲突。Codex 如果是桌面版,注意安装包来源,尽量走官方渠道。
安装完成后,先验证基础命令可用:
tmux -V python3.11 --version litellm --version claude --version codex --version如果某个命令报找不到,先解决 PATH 问题,不要急着往下走。我见过太多人卡在这一步,最后发现是 shell 配置文件没 source。
3.2 编写 openrig 主配置文件
openrig 的核心就是一份 YAML,我习惯叫它openrig.yaml,放在项目根目录或者~/.config/openrig/下。这份文件分几个大块:模型定义、代理配置、工具映射、会话布局。
version: 1 models: local-qwen: provider: openai-compatible base_url: http://127.0.0.1:4000/v1 api_key_env: LITELLM_MASTER_KEY model: qwen2.5-coder-32b context_window: 32768 deepseek-chat: provider: openai-compatible base_url: http://127.0.0.1:4000/v1 api_key_env: LITELLM_MASTER_KEY model: deepseek-chat context_window: 65536 proxy: engine: litellm listen: 127.0.0.1:4000 config_path: ./litellm_config.yaml log_level: info tools: claude-code: config_path: ~/.claude/settings.json default_model: deepseek-chat env: ANTHROPIC_BASE_URL: http://127.0.0.1:4000 ANTHROPIC_API_KEY: ${LITELLM_MASTER_KEY} codex: config_path: ~/.codex/config.toml default_model: local-qwen env: OPENAI_BASE_URL: http://127.0.0.1:4000/v1 OPENAI_API_KEY: ${LITELLM_MASTER_KEY} sessions: ai-dev: layout: main-vertical panes: - name: claude command: claude model: deepseek-chat - name: codex command: codex model: local-qwen - name: logs command: tail -f ./logs/proxy.log这份配置里,models定义逻辑模型,proxy定义代理层怎么起,tools定义下游工具怎么接,sessions定义 tmux 布局。四块之间通过模型名和端口关联,改一处就能全局生效。
3.3 代理层的 YAML 配置与启动
LiteLLM 的配置单独一份文件,因为它的字段和 openrig 的关注点不同。这份文件描述"上游真实模型是什么、密钥从哪来、限流怎么设"。
model_list: - model_name: qwen2.5-coder-32b litellm_params: model: openai/qwen2.5-coder-32b api_base: http://127.0.0.1:1234/v1 api_key: os.environ/LMSTUDIO_KEY - model_name: deepseek-chat litellm_params: model: deepseek/deepseek-chat api_key: os.environ/DEEPSEEK_KEY general_settings: master_key: os.environ/LITELLM_MASTER_KEY database_url: null litellm_settings: drop_params: true set_verbose: false启动代理:
export LITELLM_MASTER_KEY="sk-local-1234" export DEEPSEEK_KEY="你的真实密钥" export LMSTUDIO_KEY="lm-studio" litellm --config ./litellm_config.yaml --port 4000启动后先用 curl 验证一下:
curl http://127.0.0.1:4000/v1/models \ -H "Authorization: Bearer sk-local-1234"返回模型列表就说明代理层通了。这一步不通,后面全是白搭,所以务必先验证。
3.4 把 Claude Code 和 Codex 接到代理上
Claude Code 的配置在~/.claude/settings.json,关键字段是env里的ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY。注意 Claude Code 对 base_url 的处理有个细节:它会在后面拼/v1/messages,所以你的代理必须支持这个路径。LiteLLM 默认支持,但如果你用的是别的代理,要确认一下。
Codex 的配置在~/.codex/config.toml,格式是 TOML:
model = "qwen2.5-coder-32b" model_provider = "local" [model_providers.local] name = "Local Proxy" base_url = "http://127.0.0.1:4000/v1" env_key = "OPENAI_API_KEY"这里有个坑:Codex 的env_key指的是环境变量名,不是密钥本身。你要确保OPENAI_API_KEY这个环境变量在启动 Codex 的 shell 里存在。我建议把这些 export 写进一个env.sh,每次开新终端先 source 一下,或者用 direnv 自动加载。
3.5 用 tmux 一键拉起工作环境
手动开三个终端太累,写个脚本根据 openrig.yaml 自动拉起 tmux 会话:
#!/usr/bin/env bash set -euo pipefail SESSION="ai-dev" if tmux has-session -t "$SESSION" 2>/dev/null; then tmux attach -t "$SESSION" exit 0 fi tmux new-session -d -s "$SESSION" -n claude tmux send-keys -t "$SESSION:claude" "source ./env.sh && claude" C-m tmux new-window -t "$SESSION" -n codex tmux send-keys -t "$SESSION:codex" "source ./env.sh && codex" C-m tmux new-window -t "$SESSION" -n logs tmux send-keys -t "$SESSION:logs" "tail -f ./logs/proxy.log" C-m tmux select-window -t "$SESSION:claude" tmux attach -t "$SESSION"这个脚本的逻辑很简单:先检查会话是否存在,存在就直接 attach,不存在就按预设布局创建。send-keys后面的C-m相当于回车,别漏了,漏了命令不会执行。
4. 常见问题与排查技巧实录
4.1 代理转发失败的典型表现与定位
社区里搜 "cc switch local proxy failed while handling codex endpoint /responses" 这类报错的人不少,本质上是代理层不认识 Codex 发过来的请求格式。Codex 用的是 OpenAI 的 responses API,而很多代理默认只处理 chat completions。解决办法有两个:一是换用支持 responses 的代理版本,二是在代理层做路径重写,把/responses映射到/chat/completions并做格式转换。
排查顺序建议这样:先看代理日志有没有收到请求,再看请求体格式,最后看上游返回。如果代理日志里压根没有记录,说明请求没到代理,检查 base_url 和端口;如果有请求但报 404,检查路径;如果报 400,检查请求体字段。
4.2 密钥与环境变量的那些坑
codex auth token is unavailable这个报错我遇到过好几次,原因五花八门。最常见的是环境变量没 export,或者 export 在了错误的 shell 里。比如你在.zshrc里 export,但 tmux 启动时用的是.bashrc,那就读不到。另一个坑是密钥带了多余的空格或换行,从网页复制的时候很容易带上。建议用echo -n "$KEY" | wc -c检查长度,和预期对不上就是有问题。
还有一点,LiteLLM 的 master key 和上游真实 key 是两回事。下游工具用的是 master key,代理层用 master key 鉴权后,再用真实 key 去请求上游。不要把真实 key 配到下游工具里,那样代理层就失去意义了。
4.3 本地模型接入的上下文窗口问题
用 LM Studio 跑本地模型的时候,context_window这个参数很关键。Claude Code 默认会按 200k 上下文来组织请求,如果你的本地模型只支持 32k,请求会被截断或者直接报错。解决办法是在代理层做上下文裁剪,或者在下游工具里显式设置最大 token 数。LiteLLM 支持max_input_tokens参数,可以强制限制。
另外,本地模型的推理速度和显存占用要提前评估。32B 的模型在消费级显卡上跑,首 token 延迟可能到好几秒,交互体验和云端 API 差距明显。我的建议是本地模型只用来做代码补全和简单重构,复杂任务还是走云端。
4.4 tmux 会话管理的实用技巧
tmux 用久了会发现几个痛点:会话名记不住、窗口太多找不到、断线后会话丢失。针对第一个,可以用tmux ls列出所有会话,配合 alias 简化常用操作。针对第二个,建议给窗口起有意义的名字,用Ctrl-b w可以列出所有窗口快速跳转。针对第三个,tmux 默认在服务器断线后会话还在,但如果是本地机器重启就没了,可以用tmux-resurrect插件做持久化。
还有一个细节:tmux 里的环境变量继承的是启动 tmux 时的环境,如果你在 tmux 外面改了环境变量,里面的会话不会自动更新。要么重启 tmux,要么用tmux setenv手动同步。
4.5 常见问题速查表
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 代理启动报端口占用 | 4000 端口被其他进程占用 | lsof -i :4000找到进程并处理 |
| Claude Code 报 401 | master key 不匹配 | 检查下游 env 和代理 master_key 是否一致 |
| Codex 报 responses 404 | 代理不支持 responses 路径 | 升级代理版本或做路径重写 |
| 本地模型响应超时 | 模型加载慢或显存不足 | 降低模型规模或增加 timeout |
| tmux 里命令找不到 | PATH 未继承 | 在 tmux 内重新 source 环境文件 |
| YAML 解析报错 | 缩进用了 Tab 或冒号后缺空格 | 用 yamllint 校验 |
5. 进阶玩法:让 openrig 真正融入日常开发流
5.1 配置的版本化与团队共享
把 openrig.yaml 和 litellm_config.yaml 纳入 Git 管理,是这套方案从"个人玩具"变成"团队基础设施"的关键一步。但要注意,密钥绝对不能进仓库。我的做法是仓库里只放模板文件,用${VAR}占位,实际值放在.env里,.env加入.gitignore。新成员克隆仓库后,复制.env.example为.env,填入自己的密钥即可。
更进一步,可以用git-crypt或者sops对敏感文件加密,这样连.env都能进仓库,适合小团队协作。不过加密方案会增加上手成本,人少的时候用.env就够了。
5.2 多项目配置的继承与覆盖
一个人同时维护多个项目,每个项目的模型偏好可能不同。openrig 可以通过 YAML 的锚点和合并语法实现配置继承:
defaults: &defaults timeout: 60 retries: 2 models: fast-model: <<: *defaults model: deepseek-chat timeout: 30 heavy-model: <<: *defaults model: qwen2.5-coder-32b timeout: 180<<: *defaults会把 defaults 里的字段合并进来,同名字段以当前层为准。这样公共配置只写一次,项目特有的覆盖掉就行。YAML 的锚点语法初看有点怪,但用熟了能省很多重复。
5.3 监控与成本控制
代理层跑起来之后,日志里会有每个请求的 token 消耗。把这些日志接到一个简单的分析脚本,就能看到每天用了多少 token、花了多少钱。LiteLLM 支持把日志写到数据库,配合 Grafana 可以做可视化。如果不想搞这么重,用grep加awk也能凑合:
grep "completion_tokens" ./logs/proxy.log \ | awk -F'completion_tokens=' '{sum+=$2} END {print sum}'这个命令统计总 completion token 数,虽然粗糙但够用。关键是养成定期看的习惯,避免某个月账单出来才发现超支。
5.4 踩过的坑与个人体会
最后分享几个我实际踩过的坑。第一个是 YAML 的布尔值陷阱,yes、no、on、off在 YAML 1.1 里会被解析成布尔值,如果你本来想写字符串,就会出问题。解决办法是加引号。第二个是 tmux 的send-keys对特殊字符的处理,命令里如果有$或者反引号,会被 shell 提前展开,要用单引号包裹或者转义。第三个是代理层的超时设置,默认值往往偏短,本地模型跑长任务容易断,建议把 timeout 调到 300 秒以上。
这套方案不是银弹,它解决的是"多工具、多模型、多项目"场景下的配置一致性问题。如果你的场景很简单,手工配置反而更直接。但一旦工具数量超过两个,或者需要在不同模型间频繁切换,openrig 这种"YAML 驱动、tmux 承载、代理统一"的思路就能明显降低心智负担。我现在的日常是:早上打开终端,一条命令拉起整个 AI 开发环境,三个窗口各司其职,改配置只改一份 YAML,剩下的交给脚本。这种顺畅感,值得花一个下午把它搭起来。