Skip to content

第 3 章 · Provider 与模型目录

本章目标:掌握内置 Provider 全景、getModel 精确查找、模型元数据字段与静态目录/动态 Provider 的区别。

3.1 内置 Provider 全景

pi-ai 只收录支持工具调用的模型(这是 Agent 工作流的硬需求)。主要厂商:

ProviderID环境变量
OpenAIopenaiOPENAI_API_KEY
AnthropicanthropicANTHROPIC_API_KEYANTHROPIC_OAUTH_TOKEN
GooglegoogleGEMINI_API_KEY
DeepSeekdeepseekDEEPSEEK_API_KEY
xAIxaiXAI_API_KEY
GroqgroqGROQ_API_KEY
CerebrascerebrasCEREBRAS_API_KEY
MistralmistralMISTRAL_API_KEY
OpenRouteropenrouterOPENROUTER_API_KEY
Amazon Bedrockamazon-bedrockAWS 凭证链
Vertex AIgoogle-vertexADC / GOOGLE_CLOUD_API_KEY
GitHub Copilotgithub-copilotOAuth 登录
MoonshotmoonshotMOONSHOT_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() 会怎样?

🛠️ 动手实践

  1. 打印 builtinModels() 下 Anthropic 与 OpenAI 各自的全部模型,找出上下文窗口最大与最便宜的各一个。
  2. 写一个函数 pick(modelsWithReasoning):输入任意 provider 名,返回其中第一个支持推理的模型,并用 hasApi 安全地开启 thinking。
  3. getBuiltinProviders() 列出全部内置厂商 ID 数量,并与本章表格对比。

熟悉了目录之后,进入第 4 章,正式创建你的第一个 Agent。