Part III · Providers

09 pi-ai 的抽象 统一 39 家 provider 的代价,藏在一个 .gitignore 条目里

已发布provider 39 个模型数据 不入库包规模 58449 行
§1 Mental Model 一句话心智模型

先记住这一句

pi-ai 用 39 家 provider 换来一个统一 API,代价藏在一个容易忽略的地方:模型清单根本不在仓库里。它是构建时从上游目录拉取的产物,.gitignore 掉了。

这层抽象要解决的问题不只是「请求格式不同」。真正难的是三件事:认证方式各不相同(API key / OAuth / 订阅账号 / 云厂商 IAM)、流式事件语义不同、以及模型能力矩阵每周都在变(上下文窗口、价格、是否支持 thinking、是否支持工具)。

前两件靠代码解决,第三件靠不把它当代码——生成、不入库、构建时刷新。

§2 Evidence 代码实证

模型数据不在 git 里

packages/ai/src/providers/anthropic.models.ts原文全文(8 行)
// This file is auto-generated by scripts/generate-models.ts
// Do not edit manually - run 'npm run generate-models' to update

import values from "./data/anthropic.json" with { type: "json" };
import { flattenModelCatalog, type ModelCatalog } from "../model-catalog.ts";

export const ANTHROPIC_MODELS: ModelCatalog<typeof values, "anthropic"> =
	flattenModelCatalog("anthropic", values);
39 个 provider 各有一个这样的 8 行文件。真正的数据在 ./data/*.json——而整个 data/ 目录被 gitignore。.gitignore:11

后果直接写在 README 的开发命令里:npm run build 会先刷新模型数据再构建;想不联网构建必须用 build:offline。README.md:56

发布用的源码归档里则包含这份生成数据,这样打包者能离线复现官方二进制。README.md:74

一条显式的收录标准

库只收支持 tool calling 的模型。ai/README.md:5 这一句把 pi-ai 从「通用 LLM SDK」区分了出来——它是 agent 的基础设施,不是中立的多家封装。

认证:显式值永远赢

调 models.stream() 时,认证经由所属 provider 解析并合并进请求;每次请求里显式给的值永远优先(环境变量、存储的凭证、OAuth token 都排在后面)。ai/README.md:330

并且解析结果可以不发请求就查——models.getAuth(provider) 或 getAuth(model)。对一个要同时支持 API key、OAuth 订阅、云厂商凭证的库来说,「能单独查认证怎么解析出来的」是必要的可调试性。

这也解释了 第 03 章里循环为什么要 getApiKey 回调:OAuth token 会过期,而一个 run 可能跑很久,认证必须在每次 LLM 调用时重新解析,不能在构造时固定。

§3 Design 设计动机与权衡

把模型元数据当构建产物,而不是源码

好处:模型清单每周都在变(新模型、改价、改窗口),入库会让 git 历史里塞满与代码无关的数据变更,而且永远滞后。构建时刷新意味着发版时的清单总是新的。

代价有三条,都很实在:

与 第 16 章那套「依赖精确锁版本、拒绝同日发布」的谨慎相比,这里的取舍是反向的:依赖代码要钉死,模型数据要最新。区分依据是——依赖代码会执行,模型元数据只是数据。

为什么值得自己写,而不是用现成的多 provider 库

5.8 万行是很大的投入。从收录标准(只要支持 tool calling 的模型)和认证设计(订阅账号、OAuth)能看出理由:通用库优化的是「换 provider 容易」,agent 需要的是「工具调用、流式、成本、thinking 级别在所有 provider 上语义一致」——后者要深入到每家的细节里去抹平,而不是找最大公约数。

这个判断是否值 5.8 万行,取决于你要支持几家。支持 2 家不值,支持 39 家大概值。

上游讲这条理由,是在仓库之外:Earendil《What is a Harness?》(2026-08-20),比 ac4ac9e 晚 13 天。文中的理由是用户侧的选择权,直说是为了 “take power and leverage away from the AI labs and into the hands of end users”。

§4 Limits 边界与失效条件

这条证据的级别与本书其余部分不同。《What is a Harness?》是第一方立场文,能证明的只有「上游对外这么讲」,不能证明「代码这么跑」——它不是代码,也不是仓库文档,没有行号,也不该有。本条同样不进漂移记录:那里收的是同一 SHA 内「文档 vs 代码」的双向举证,这里有一侧根本不在仓库里。

§5 Takeaway 拿走什么
§ Self-check 自测

给场景,判断 pi 会怎么做

场景同一个 commit,你在相隔两周的两天各跑一次 npm run build。两次产物一致吗?

看答案

B。模型元数据被当作构建产物而非源码。.gitignore:11 想复现官方发布要用发布归档里附带的数据快照 + build:offline。

场景你想在 pi 里接一个不支持 tool calling 的纯对话模型。可行路径是什么?

看答案

B。收录标准明确只收支持 tool calling 的模型——A 不会发生,这是设计选择而非疏漏;绕开目录的路径是自定义 provider。

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