第 3 章 · Provider 与模型目录
本章目标:掌握内置 Provider 全景、
getModel精确查找、模型元数据字段与静态目录/动态 Provider 的区别。
3.1 内置 Provider 全景
pi-ai 只收录支持工具调用的模型(这是 Agent 工作流的硬需求)。主要厂商:
| Provider | ID | 环境变量 |
|---|---|---|
| OpenAI | openai | OPENAI_API_KEY |
| Anthropic | anthropic | ANTHROPIC_API_KEY 或 ANTHROPIC_OAUTH_TOKEN |
google | GEMINI_API_KEY | |
| DeepSeek | deepseek | DEEPSEEK_API_KEY |
| xAI | xai | XAI_API_KEY |
| Groq | groq | GROQ_API_KEY |
| Cerebras | cerebras | CEREBRAS_API_KEY |
| Mistral | mistral | MISTRAL_API_KEY |
| OpenRouter | openrouter | OPENROUTER_API_KEY |
| Amazon Bedrock | amazon-bedrock | AWS 凭证链 |
| Vertex AI | google-vertex | ADC / GOOGLE_CLOUD_API_KEY |
| GitHub Copilot | github-copilot | OAuth 登录 |
| Moonshot | moonshot | MOONSHOT_API_KEY |
此外还有 Azure OpenAI、NVIDIA NIM、Together、Fireworks、MiniMax、Qwen 等,以及任意 OpenAI 兼容端点(Ollama/vLLM/LM Studio)——第 14 章会讲如何自定义。
3.2 getModel:同步精确查找
集合上的读取都是同步的,返回最后已知的目录:
typescript
// 三种常用的查询方法
const models = builtinModels();
// 1. 查单个模型:provider id + model id
const model = models.getModel("anthropic", "claude-sonnet-4-6");
if (!model) throw new Error("Model not found"); // 查不到返回 undefined
// 2. 列出某厂商的全部模型
const anthropicModels = models.getModels("anthropic");
// 3. 列出所有已注册 Provider
const providers = models.getProviders();3.3 读懂模型元数据
每个 Model 对象携带决策所需的关键信息:
typescript
for (const m of models.getModels("openai").slice(0, 5)) {
console.log(`${m.id}: ${m.name}`);
console.log(` API 协议: ${m.api}`); // openai-responses 等
console.log(` 上下文窗口: ${m.contextWindow}`); // token 上限
console.log(` 视觉输入: ${m.input.includes("image")}`); // 能否读图
console.log(` 支持推理: ${m.reasoning}`); // 有无 thinking 能力
}这些字段可以直接用来做运行时选型逻辑:
typescript
// 例:自动挑一个支持视觉且上下文最大的模型
const visionModel = models
.getModels("google")
.filter((m) => m.input.includes("image"))
.sort((a, b) => b.contextWindow - a.contextWindow)[0];
console.log(visionModel?.name);3.4 hasApi 类型守卫
动态查询到的模型是宽泛类型,需要用 hasApi() 收窄才能获得 API 特有选项的完整类型提示:
typescript
import { hasApi } from "@earendil-works/pi-ai";
const m = models.getModel("anthropic", "claude-sonnet-4-6")!;
if (hasApi(m, "anthropic-messages")) {
// 此处 m 的类型收窄为 Model<'anthropic-messages'>,
// stream 选项拥有完整的 Anthropic 原生参数与自动补全
models.stream(m, context, {
thinkingEnabled: true,
thinkingBudgetTokens: 2048,
});
}3.5 静态目录 vs 动态刷新
typescript
import {
getBuiltinModel, // 静态查询单个(字面量类型完整,ID 可自动补全)
getBuiltinProviders,
} from "@earendil-works/pi-ai/providers/all";
// 静态读取:不依赖任何集合实例,适合构建工具链/配置校验
const m2 = getBuiltinModel("openai", "gpt-4o-mini");
// 动态 Provider(如本地 llama.cpp 服务、OpenRouter 在线列表)
// 目录需要显式异步刷新:
await models.refresh({ providers: ["openrouter"] }); // 只刷新一家
await models.refresh(); // 并发刷新全部(尽力而为)
const fresh = models.getModel("llamacpp", "qwen3-30b");静态内置 Provider 对 refresh() 是 no-op;只有动态 Provider 会真正发网络请求拉取最新列表。读取永远同步——先 refresh,后 read。
3.6 本章小结
- 内置 Provider 覆盖主流厂商 + 任意 OpenAI 兼容端点;只收录支持工具调用的模型;
getModel(provider, id)同步查找,未命中返回undefined;- 模型元数据含
api/contextWindow/input/reasoning/cost,可用于运行时选型; hasApi()把宽泛模型收窄为具体 API 类型以获得完整选项提示;- 动态 Provider 的目录需显式
refresh()后再同步读取。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. pi-ai 为什么只收录支持工具调用(function calling)的模型?
2. `models.getModel("anthropic", "不存在")` 会发生什么?
3. 关于 hasApi(m, "anthropic-messages"),正确的说法是?
4. 对静态内置 Provider 调用 models.refresh() 会怎样?
🛠️ 动手实践
- 打印
builtinModels()下 Anthropic 与 OpenAI 各自的全部模型,找出上下文窗口最大与最便宜的各一个。 - 写一个函数
pick(modelsWithReasoning):输入任意 provider 名,返回其中第一个支持推理的模型,并用hasApi安全地开启 thinking。 - 用
getBuiltinProviders()列出全部内置厂商 ID 数量,并与本章表格对比。
熟悉了目录之后,进入第 4 章,正式创建你的第一个 Agent。