Part III · Providers

10 认证与账号 API key / OAuth / IAM 三条路,以及它们共同的过期问题

已发布凭证类型 3 类解析时机 每次调用
§1 Mental Model 一句话心智模型

先记住这一句

pi 支持三类凭证:API key、OAuth 订阅账号、云厂商 IAM。它们的共同点是都可能在一个 run 中途失效,所以认证不是启动时解析一次,而是每次 LLM 调用重新解析。

这一章短,但有一个容易被忽略的结构性后果:因为认证要动态解析,第 03 章那个循环必须持有一个 getApiKey 回调而不是一个字符串。一个看似琐碎的运维事实,反向约束了核心 API 的形状。

§2 Evidence 代码实证

解析顺序:显式值永远赢

集合在 models.stream() 时经所属 provider 解析认证并合并进请求,而每次请求里显式传的值优先于一切——环境变量、存储的凭证、OAuth token 都在其后。ai/README.md:330

循环里的那个回调

packages/agent/src/agent-loop.ts原文 L305–L306
const resolvedApiKey =
    (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
注意这行在 streamAssistantResponse 内部,每个 turn 都会执行一次。README 对该字段的注释写得很直白:给会过期的 OAuth token 用。agent-loop.ts:305

如果认证在构造 Agent 时固定成一个字符串,一个跑 40 分钟的 run 会在 token 过期的那一刻整体失败——而且失败在最不该失败的地方(工具都跑完了,只差最后一次总结)。

可以不发请求就查解析结果

models.getAuth(provider) 返回 provider 级认证;传 model 则会带上该模型的静态 headers。ai/README.md:343

这个 API 的存在本身说明了一件事:当有三类凭证来源时,「现在到底用的哪个」是高频问题,值得一个专门的查询入口。

§3 Design 设计动机与权衡

为什么支持订阅账号是个大决定

支持 Claude Pro / ChatGPT 这类订阅账号(而不只是 API key),意味着要实现 OAuth 流程、token 刷新、以及各家不同的账号态处理。这是 packages/ai 里 auth/oauth 单独成目录的原因。

收益是准入门槛:用户不需要先去开一个 API 账单账户。对一个想要广泛使用的开源 CLI,这可能是采用率上最重要的一个功能——但它的成本全部落在 pi-ai 这一层,且每家 provider 的实现都不一样。

「显式值永远赢」这条规则的价值

多来源凭证系统最容易出的问题是优先级不确定:环境变量和存储凭证同时存在时用哪个?pi 的答案是把优先级写成一条可背诵的规则,并且让最显式的那个赢。

这条规则让调试可行:怀疑认证有问题时,直接在调用里传一个 key,如果好了就说明是解析问题而不是凭证问题。

§4 Limits 边界与失效条件
§5 Takeaway 拿走什么
§ Self-check 自测

给场景,判断 pi 会怎么做

场景一个跑了 40 分钟的 run,中途 OAuth token 过期了。pi 的行为是什么?

看答案

B。认证在每个 turn 的 LLM 调用前重新解析,不在构造时固定。agent-loop.ts:305

场景环境变量里有 ANTHROPIC_API_KEY,你调用时又显式传了另一个 key。哪个生效?

看答案

B。解析顺序的第一条规则:显式值优先于 provider 解析出来的一切。ai/README.md:330

基于 earendil-works/pi @ ac4ac9e(2026-08-07 快照)。
仓库 commit 速度约 428/月,行号引用仅对该 SHA 有效。