Pi 0.84.1配置自定义API网关只需5分钟JSON文件;详细列出推理+Claude需关闭supportsDeveloperRole、计费需手动添加cost block、baseUrl覆盖策略等三个常见故障。
Pi 内置了 36 个 API Key 提供方和六个订阅登录,已经可以开箱即用,但你的提供商大概率不在其中。把它指向别处只需要一个 JSON 文件和大约五分钟,这部分很简单。有趣的是之后会出问题的三件事,没有一件会直接表明自己是配置问题。
以下所有内容均于 2026-08-18 在 macOS 上的 pi 0.84.1 中运行,Gateway 通过 OpenAI 兼容端点提供 131 个模型。
配置文件: ~/.pi/agent/models.json
Provider 字段: baseUrl, api, apiKey, models[]
协议: openai-completions, openai-responses,
anthropic-messages, google-generative-ai
从环境变量读取: "$OFOX_API_KEY"
从钥匙串读取: "!security find-generic-password -ws ofox"
未列出的模型: 运行,但有警告,实际以 128K 上下文运行
费用计算: $0.00,直到你添加 cost 块
推理 + Claude: 500,直到你添加 supportsDeveloperRole: false
内置覆盖: 仅 baseUrl,在 /v1 之前停止
热重载: 自动,每次 /model 打开时
完成此设置后你能做什么,不能做什么?
你可以使用 Gateway 提供的所有模型,在 Pi 自己的循环中运行,用一个 Key 计费。你无法获得从 Gateway 获取的模型列表、可用 的费用数字,也无法在模型悄悄以实际上下文窗口的八分之一运行时收到任何警告。
支持的协议包括:任何支持 OpenAI Chat Completions、OpenAI Responses、Anthropic Messages 或 Google Generative AI 的端点。
通过 /model 在会话中途切换模型,包括跨提供商切换,因为文件在每次打开选择器时都会重新读取。
可以从环境变量或 shell 命令中读取密钥,所以 JSON 中无需存放任何密钥。
支持的特性:Thinking levels、图片输入和工具调用,只要你为每个模型声明了它们。
不能立即工作的部分:
无目录发现。将 Pi 指向一个记录每个请求的本地服务器,Pi 在每次运行中向 /v1/chat/completions 发送四次 POST,但没有一次 GET 请求到 /v1/models。你输入什么它就知道什么。
无费用数字。Token 计数精确,但金额为零,直到你自己写入费率。
无协议嗅探。用错误的 base path 覆盖内置 provider,返回的错误描述的是认证问题,而非协议问题。
应该把 Pi 指向 Gateway 还是直接使用内置 Provider?
如果你已经直接付费给 Anthropic、OpenAI 或 Google,使用内置选项。把自定义 provider 添加进来,当且仅当:你想用的模型不在那个集合中,或者一把钥匙跨所有工具比按供应商分开看仪表盘更重要。
自定义 provider 真正发挥价值的场景:
你运行的模型没有任何第一方提供商提供,实际上这意味着大多数中国开源权重旗舰模型,以及任何非官方托管的模型。
你已经通过 Gateway 路由 Claude Code 或 Codex CLI,想要一把钥匙、一张账单,而不是四把。
你想 A/B 测试一个便宜的默认模型和一个昂贵的升级模型,而不用为第二个模型开第二个账户。
不值得为此添加配置文件的场景:
你只用一个供应商和一个计划。/login 直接覆盖六个订阅,包括 ChatGPT Plus 和 Pro、Claude Pro 和 Max、GitHub Copilot、xAI 和 OpenRouter,完全不需要这个。
你在本地运行时上。Ollama、vLLM 和 llama.cpp 是有文档记录的用例,只需要 baseUrl 加一个模型 ID,本文的其他内容都不需要。
你只是想更改内置 provider 的 Key。那是一行覆盖,在文章末尾会讲到。
停止规则:如果 pi --list-models 已经显示了你打算运行的模型,关闭这个页面。本文所有内容都是为了添加 Pi 不知道的模型。
开始之前你需要什么?
Node 22 或更高版本、一个 Key,以及一个你已经 curl 过一次的 base URL。
如果还没安装,执行以下命令:
npm install -g @earendil-works/pi-coding-agent
pi --version
在写文件之前有一件事值得决定:你选的 provider 名称会成为每个 --provider 标志和每个会话记录的一部分。之后重命名它,旧会话会指向一个已经不存在的 provider。
如何向 Pi 添加自定义 Provider?
一个文件里四个字段,然后一条命令验证它能工作。
第一步:写 provider 块
~/.pi/agent/models.json 存放所有配置。最小可运行条目:
{
"providers": {
"ofox": {
"baseUrl": "https://api.ofox.ai/v1",
"api": "openai-completions",
"apiKey": "$OFOX_API_KEY",
"models": [
{ "id": "deepseek/deepseek-v4-flash", "contextWindow": 1000000, "maxTokens": 384000 }
]
}
}
}
openai-completions 是最先要尝试的。它是实现最广泛的格式,在我们这个 Gateway 上 openai-responses 对同一模型也有效,但这不能类推到其他情况。
第二步:把 Key 放在环境变量里,而不是文件里
export OFOX_API_KEY=sk-...
apiKey 有三种解析方式:字面量字符串、$VAR 或 ${VAR} 插值,以及 !command,后者运行一个 shell 命令并使用其 stdout。第三种形式适用于共享机器:
"apiKey": "!security find-generic-password -ws ofox"
第三步:确认 Pi 能看到模型
pi --list-models ofox
provider model context max-out thinking images
ofox deepseek/deepseek-v4-flash 1M 384K no no
ofox moonshotai/kimi-k3 1M 1M no no
ofox z-ai/glm-5.2 1M 128K yes no
这些列来自你的文件,而非来自 Gateway。从某个条目中删除 contextWindow 和 maxTokens,同一个命令会为它打印 128K 和 16.4K,这是 Pi 的内置默认值。如果一个能推理的模型显示 thinking 为 no,那是你的声明缺失,不是端点拒绝。
第四步:运行一个会触碰磁盘的东西
Print 模式是最快的验证,因为它会锻炼整个工具循环,而不只是补全端点:
pi --provider ofox --model deepseek/deepseek-v4-flash -p \
"Read buggy.py, run it, and state the one-line bug. Do not edit files."
在一个包含两行文件的临时目录中——一个 add 函数里是 return a - b,DeepSeek V4 Flash 读取了文件、通过 bash 工具运行了解释器,第一次尝试就给出了正确答案。这就是完整的集成测试:文件读取、shell 执行、回答。
openai-responses 也能用吗?
在我们的 Gateway 上可以,对同一模型,只需把协议名改一下。把 "api": "openai-completions" 换成 "api": "openai-responses" 并重新运行同样的提示词,得到了同样的答案。
不要把这个当成普遍规律。Responses 支持是由托管方按模型决定的,不是按 Gateway 来的,所以一个端点对一个模型能响应 /v1/responses,对下一个模型可能根本没有 Responses 路由。openai-completions 是覆盖最广的格式,除非特定模型需要,否则选其他没有任何好处。Codex CLI 是迫使这个问题出现的工具,因为它只说 Responses。
为什么 pi auth check 显示 Ready 但 Key 却不工作?
因为它只检查 Key 是否存在,不检查它是否有效。我们把 provider 指向一个故意写错的 Key,然后执行:
pi auth check --provider ofox
# ready
同样的配置,一次请求之后:
401: {"message":"Invalid or expired API key","type":"invalid_api_key","code":401}
ready 只意味着 Pi 把某个东西解析进了 apiKey 槽。当成对你环境变量的拼写检查就好了,不要当作更多含义。真正的就绪测试是第四步。
为什么 Pi 报告每个会话都是 $0?
因为自定义 provider 没有价目表,Pi 不会自己编一个。Token 统计是精确的。以下是 Pi 为那第一次运行的两个回合写的使用记录,来自 ~/.pi/agent/sessions/ 下的会话文件:
表中两件事值得分开看。cacheRead 数字是真的:Gateway 在第二次调用时返回了 prompt_tokens_details.cached_tokens,Pi 记录了下来。cost 列不是真的——它是空的。cost 下的每个字段都是零,因为模型条目从未声明过费率。
加上它们,算数就开始工作了:
{
"id": "anthropic/claude-sonnet-5",
"contextWindow": 1000000,
"maxTokens": 128000,
"cost": { "input": 2, "output": 10, "cacheRead": 0.2, "cacheWrite": 2.5 }
}
费率是每百万 Token,从提供商的官网定价页获取,而非从厂商获取。针对 Claude Sonnet 5 的下一次运行记录了 4,111 个输入 Token 和 4 个输出 Token,计费为 $0.008222 加上 $0.00004,总计 $0.008262,这是那些计数乘以每百万 $2 和 $10 的结果。Pi 在本地做这个算术,所以数字的真实性取决于你输入的费率是否准确。输入 Gateway 的费率,而非模型制造商的,并在页面更新时重新检查。
contextWindow 同理。Sonnet 5 在这个 Gateway 上是 1M 上下文模型,条目里必须声明这一点,否则 128K 默认值会悄悄接管。
因为 reasoning: true 会让 Pi 将系统提示词作为 developer 角色消息发送,而并非所有上游都接受这个角色。报错非常明显,看起来像是服务端故障:
500: {"code":null,"message":"Request error: failed to convert messages:
unsupported message role: developer","param":null,"type":"api_error"}
这条错误信息里没有任何内容指向你的配置,所以值得单独隔离出来排查。同一网关、同一模型、每次只改一个字段,跑三遍:
触发原因是 developer 角色,有两种修复方案,成本不同。用本地服务器日志打印请求体,对同样的三个配置进行对比,结果如下:
compat 开关将系统提示词移到了 system 消息中,同时保留了 reasoning_effort,所以思考能力得以保留。将 reasoning: false 也能清除错误,但代价是完全从请求中丢弃了 reasoning_effort,这通常是个错误的取舍。
同样的抓包还回答了人们常问的关于 maxTokensField 的问题:在 openai-completions 模式下,Pi 发送的是 max_completion_tokens,而不是 max_tokens。如果你的端点只认旧字段,那就是需要切换的开关。
这个角色问题只在部分模型目录上出现。同一个网关、同样的 reasoning: true,三个模型家族的结果:
问题出在上游的形状上,而非网关策略。Anthropic 的 API 没有 developer 角色,所以将 OpenAI 形状的请求翻译成 Messages 的网关没有可映射的对象。OpenAI 形状的上游会接受它并继续处理。这和 Codex CLI 发送空的工具描述是一类问题——有些上游会校验它,有些则忽略它——我们在用九个测试框架对一个网关进行测试时就遇到了。教训再次印证:当客户端和端点之间出现分歧时,先读请求体,再去改设置。
Pi 的文档列出了同一系列的另外两个开关:supportsReasoningEffort 用于那些拒绝 thinking 参数的服务器,maxTokensField 用于那些想要 max_completion_tokens 而不是 max_tokens 的服务器。如果某个模型在开启 thinking 的瞬间 400 了,接下来就试这两个。
不需要,而且在一个有 131 个模型的网关上你根本不该尝试。Pi 从未见过的 id 照样能跑:
pi --provider ofox --model z-ai/glm-5.2 -p "say ok"
# Warning: Model "z-ai/glm-5.2" not found for provider "ofox". Using custom model id.
# ok
这个备用机制就是五行配置和五百行配置的区别。它也有代价。未列出的模型会继承 Pi 的默认值:文档记载为 128,000 context 和 16,384 最大输出,而 pi --list-models 对任何省略了这些字段的条目都只打印这两个数字。自动压缩会在 contextTokens > contextWindow - reserveTokens 时触发,reserveTokens 默认为 16,384,所以一个 1M context 的模型会在大约 111,600 tokens 处就开始总结自己,而不是接近一百万。输出中没有任何信息说明原因。人们确实在项目社区里反映过压缩来得比预期早的现象,这至少是导致该问题的机制之一,在责怪模型之前排除它成本很低。
实际的分法是:让未列出的 id 覆盖探索场景,然后为日常跑的两三个模型写真正的条目,把 contextWindow、maxTokens、reasoning、input 和 cost 都填上。Pi 显示的关于一个模型的所有信息——包括它是否接受图片——都来自那个条目,而不是来自端点。
可以,而且是更好的 Claude 模型路由,只要你给它 Anthropic 的 base path 而不是 OpenAI 的。覆盖只需一行,也不需要自己的模型列表:
{ "providers": { "anthropic": { "baseUrl": "https://api.ofox.ai/anthropic", "apiKey": "$OFOX_API_KEY" } } }
pi --provider anthropic --model claude-sonnet-5 -p "Reply with exactly: ok"
# ok
Pi 保留了整套内置的 Claude 目录,窗口大小已经正确——Claude Fable 5 为 1M,Opus 和 Haiku 条目为 200K。无需声明什么,无需保持同步,而且因为 Messages 是原生形状,所以根本不会有 developer 角色的问题。供应商 id 直接可用,无需网关前缀。
把 base URL 配错了会产生两个都描述了错误问题的错误。把它指向 OpenAI 路径:
401 {"error":{"message":"You didn't provide an API key. You need to provide your API key
in an Authorization header using Bearer auth ...","type":"invalid_request_error","code":401}}
那里没有认证 bug。Pi 在说 Messages,所以它发送的是 x-api-key,而 OpenAI 路径只接受 Authorization: Bearer。通过 provider 的 headers 字段添加一个 Bearer header,真正的答案就出现了:404 Unsupported OpenAI API endpoint。401 只是协议不匹配披了层认证的外衣。
另一种出错的方式是重复了版本段:
404 {"error":{"message":"Unsupported Anthropic API endpoint. ...","code":404}}
那是配置里写了 .../anthropic/v1。Pi 本身会追加 /v1/messages,所以 base URL 应该停在 /anthropic。
所以 Claude 有两条路用同一个 key:通过上面的内置覆盖,或者用自定义的 openai-completions 条目加上网关自己的 id——这就是成本示例用的方式。覆盖更少打字,而且完全避免了 developer 角色。自定义条目则用于那些想要 Pi 尚不知道的 per-model cost 和 contextWindow 值时。
它们能用,但 Pi 自己的作者已经记录了最新版模型上的一个 schema 问题。写于 2026-07-04,Armin Ronacher 报告说"新版 Claude 模型有时会用嵌套的 edits[] 数组中额外捏造的字段来调用 Pi 的 edit 工具",结果是"模型编造了不存在的键,Pi 因此拒绝该工具调用并要求重试"。他对这个趋势的总结才是令人不安的部分:"随着 Anthropic 新模型,这个问题越来越严重,Opus 4.8 和 Sonnet 5 都有这个问题,但旧模型都没有。"
这是训练和工具链之间的不匹配,不是改个 base URL 能解决的,而且每次触发都要浪费一次重试。它不会让 Claude 在 Pi 里不可用。但这意味着如果你要为某个其 edit 工具属于自己的测试框架选一个默认模型,最新的 Claude 并不自动是最安全的选择,值得留意你的 session 日志中是否有同一 edit 上的重复工具调用。
用 curl 复现那个调用,然后和 Pi 记录的内容对比。两个文件加一条命令覆盖大部分你需要的东西,都不需要代理。
session 日志是第一站。每次运行都会在 ~/.pi/agent/sessions/<project>/ 下写入一个 JSON Lines 文件,每条事件一条记录,包括一条 model_change 行命名 Pi 解析出的 provider 和 model id,以及一条带有 usage 块的 assistant 消息。如果那个文件里的 model id 不是你打算跑的,问题出在你的 flag 或者 fallback 上,再怎么调 provider 都修不了。
端点是第二站。自己发送同样形状的请求:
curl -s https://api.ofox.ai/v1/chat/completions \
-H "Authorization: Bearer $OFOX_API_KEY" -H 'Content-Type: application/json' \
-d '{"model":"deepseek/deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":8}' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["usage"])'
usage 中有两个值值得仔细读。prompt_tokens_details.cached_tokens 是 Pi 映射到其 cacheRead 列的值,所以如果那个键从不出现,你的 session 日志中的缓存数字就会一直是零,无论你的前缀有多稳定。而 completion_tokens_details.reasoning_tokens 告诉你 thinking 是否真的在跑,这比读输出然后猜要快得多。
在修改配置之前就这样做,是我们写过的每一个测试框架集成案例的完整教训。错误字符串是由抛出它的人写的,而那个出错的人往往并不是真正有问题的组件。
六个失败场景,其中五个在 0.84.1 版本上针对真实端点复现,还有一个用 Pi 自己的模型表演示。
第四行和第五行是最费时间的,因为两个错误信息描述的都是与实际问题不同的事。
共享文件,不要共享 key。models.json 在每个 apiKey 都是 $VAR 或 !command 时不包含任何秘密,可以安全地提交到 dotfiles 仓库或引导脚本里。
一个能经受住多个开发者接触的拆分方式:
提交 provider 块:base URL、协议,以及完整的模型条目,包含 contextWindow、maxTokens、reasoning 和 cost。这些是关于端点的事实,对每个人都一样,把它们搞错了才会产生静默的提前压缩和虚假的 $0 账单。
永远不要提交密钥。共享文件中写 "apiKey": "$OFOX_API_KEY",真实值放在每位开发者的 shell 配置文件或钥匙串中。
锁定你验证过的版本。Pi 大约每周发布一次,不到一个月就从 0.82.1 到了 0.84.2。记录你配置所对应的测试版本。
给所有人同一个 base URL。一个端点意味着同一套模型目录、同一组速率限制池和同一个消费查看入口,而不是每人各自猜测。
最后一点正是团队容易跳过的一点,也正是这一点把"你现在用哪个模型?"从一个问题变成了一次查询。
每个 harness 都以自己的方式存储模型访问配置。Claude Code 读取 ANTHROPIC_BASE_URL 和 ANTHROPIC_AUTH_TOKEN。Codex CLI 需要在 config.toml 中添加 model_providers 块,并且拒绝任何非 Responses API 的内容。Cline 有自己的设置面板。DeepSeek Harness 需要一个自定义 provider 表单或 DEEPSEEK_BASE_URL。Pi 需要上面提到的 JSON 文件。五个工具,五个地方要轮换密钥,五份模型列表逐渐漂移。
它们都通过 HTTP 与 OpenAI 兼容端点或 Anthropic 兼容端点通信,所以修复方法在每个工具里都是一样的:一个 base URL、一个密钥,唯一变化的是模型字符串。这就是每个工具的自定义 provider 表单都有相同四个字段的原因。
在 ofox,那个端点是 https://api.ofox.ai/v1,使用 openai-completions,一个密钥在 2026-08-18 当天访问到了 131 个模型,包括 Kimi K3 和 MiniMax M3,以及上面用到的 DeepSeek、GLM 和 Claude 系列模型。其他工具的等效配置方法见我们的 Codex CLI 自定义 provider 指南、OpenCode 配置教程,以及 Cursor、Claude Code 和 Cline 的安装说明。
同样的工作,更小的接触面,以及一个坦诚表达"我假设了多少"的配置文件。Pi 给模型四个工具和一个扩展 API,而 Claude Code 开箱即提供 hooks、subagents、skills 和 MCP 服务器。没有绝对的好坏。问题在于你想要的是成品还是需要组装的零件。
自定义 provider 路径揭示的是,这种极简主义是有代价的。没有模型目录获取、没有价格表、没有协议探测。本文中三个问题都是 Pi 拒绝替你猜测任何东西,而每个修复方法都需要你自己把这个事实写下来。
关于 Pi 在整个领域中的位置,包括人们在每个 harness 中实际运行了哪些模型的 OpenRouter 使用数据,请参阅九大 harness 横评。对于终端 agent,特别是 Claude Code vs Codex CLI vs Cursor,有更深入的分析。
自定义模型配置指南
Provider 列表和 /login 订阅
压缩与分支摘要
Armin Ronacher,"Better Models, Worse Tools"(2026-07-04)
Simon Willison 对同一篇文章的笔记
常见问题
Pi 编程 agent 是什么?
一款来自 Armin Ronacher 和 Mario Zechner 的终端编程 agent,MIT 许可,现由 Earendil 在 github.com/earendil-works/pi 开发。它给模型提供四个内置工具(read、write、edit、bash),几乎不附带其他东西,暴露的是一个扩展 API 而不是 hooks、subagents 和 skills。截至 2026-08-18,该仓库已有 92,619 颗星,npm 包每周有 137 万次下载。
哪个 npm 包安装 Pi?
@earendil-works/pi-coding-agent,当前版本 0.84.2,engines node >=22.19.0。旧包 @mariozechner/pi 停在 0.70.6,每周只有几百次下载;它是收购前的渠道,安装它拿到的是转给 Earendil 之前的版本。
Pi 支持第三方 API 端点吗?
支持,通过 ~/.pi/agent/models.json。一个 provider 条目包含 baseUrl、api、apiKey 和一个 models 数组,其中 api 可以是 openai-completions、openai-responses、anthropic-messages 或 google-generative-ai。不需要代码改动也不需要 fork,且文件每次打开 /model 选择器时都会重新加载。
Pi 能从环境变量读取 API 密钥吗?
能。apiKey 支持 $VAR 和 ${VAR} 变量插值,也支持 !command,后者运行一个 shell 命令并把 stdout 作为密钥。第二种形式可以让你从系统钥匙串读取密钥,而不必在 JSON 文件中留一个secret。使用 $$ 表示字面量美元符号。
Pi 需要 models.json 中列出每个模型吗?
不需要。传入一个不在列表中的模型 id 会打印 Warning: Model not found for provider,然后把它作为自定义模型 id 运行。问题是未列出的模型会继承默认值:128,000 上下文和 16,384 最大输出,所以一个 1M 上下文的模型会比实际需要更早地进行压缩。
为什么 Pi 显示每个请求的花费是 $0?
因为自定义 provider 不携带定价元数据。令牌计数在会话文件中正确记录了,但 cost 下的每个字段都保持为零,除非你在模型条目中添加一个 cost 块,写明每百万令牌的 input、output、cacheRead 和 cacheWrite 费率。
Pi 能把内置的 Anthropic provider 指向一个网关吗?
能,如果你给的是网关的 Anthropic Messages base 路径而不是它的 OpenAI 路径。覆盖内置 anthropic provider 的 baseUrl 可以让 Pi 保留完整的 Claude 目录和正确的上下文窗口,且不需要 models 数组。URL 要截取到版本段之前,因为 Pi 自己会附加 /v1/messages,如果指向 OpenAI 路径会返回 401——那其实是一个协议不匹配的错误。
Pi 报错 unsupported message role: developer 是什么意思?
意思是模型条目中 reasoning 设置为 true,所以 Pi 把系统提示作为 developer 角色消息发送,而上游某个东西拒绝了这个角色。添加 compat 并设置 supportsDeveloperRole: false 可以保持 thinking 能力,或者把 reasoning 设为 false 来去掉它。在 OpenAI 形状的网关上,这个问题只出现在 Anthropic 系列的模型上。
Originally published on ofox.ai/blog.