第 2 章 · pi-ai 快速入门:统一 LLM API
本章目标:掌握 pi-ai 的核心抽象
Models集合,学会用createModels()/setProvider()注册厂商,并用streamSimple发起第一次流式调用。
2.1 核心心智模型:Models 集合
pi-ai 的一切都围绕一个概念:Models 集合。它是一个「路由器」——持有若干 Provider,每个请求根据模型 ID 被转发给拥有该模型的 Provider:
text
models.complete(model, context)
│
▼
┌──────────────────┐ ┌──────────────────┐
│ anthropicProvider│ │ openaiProvider │ …其他已注册 Provider
│ (认证+目录+流式) │ │ (认证+目录+流式) │
└──────────────────┘ └──────────────────┘Provider 是运行时单元,它负责三件事:模型目录(有哪些模型)、认证(API Key 如何解析)、流式行为(如何调 wire API)。
2.2 创建集合的两种方式
typescript
// 方式一:按需注册(推荐生产使用,bundle 更小)
import { createModels } from "@earendil-works/pi-ai";
import { anthropicProvider } from "@earendil-works/pi-ai/providers/anthropic";
import { deepseekProvider } from "@earendil-works/pi-ai/providers/deepseek";
const models = createModels();
// 每个 factory 子路径只引入该厂商的目录与懒加载 SDK
models.setProvider(anthropicProvider());
models.setProvider(deepseekProvider());
// 方式二:一键注册全部内置厂商(原型开发最省事)
import { builtinModels } from "@earendil-works/pi-ai/providers/all";
const all = builtinModels(); // 等价于把所有 factory 都 setProvider 一遍bundle 体积
providers/all 会拉起所有内置目录与 SDK 包装。对体积敏感的应用(尤其浏览器端)请用方式一按需注册。
2.3 streamSimple:最简单的流式调用
streamSimple 提供跨提供商统一的简化选项(如 reasoning: 'medium'),是 Agent 场景的默认选择:
typescript
// 完整的最小流式示例
import { createModels } from "@earendil-works/pi-ai";
import { anthropicProvider } from "@earendil-works/pi-ai/providers/anthropic";
const models = createModels();
models.setProvider(anthropicProvider());
const model = models.getModel("anthropic", "claude-sonnet-4-6")!;
// Context 是纯数据:系统提示词 + 消息数组 + 工具列表
const context = {
systemPrompt: "你是一个简洁的助手。",
messages: [
// 每条消息都必须带 timestamp(毫秒)
{ role: "user", content: "解释什么是闭包", timestamp: Date.now() },
],
};
// 发起流式请求;认证由 Provider 自动从 ANTHROPIC_API_KEY 解析
const stream = models.streamSimple(model, context);
for await (const event of stream) {
switch (event.type) {
case "text_delta":
// 增量文本:直接写入终端实现打字机效果
process.stdout.write(event.delta);
break;
case "thinking_delta":
// 推理型模型的思考增量(若开启 reasoning)
process.stdout.write(`🤔 ${event.delta}`);
break;
case "done":
// 结束原因:stop / length / toolUse / error / aborted
console.log("\n[完成]", event.reason);
break;
}
}
// result() 返回最终完整消息(含 usage 计费信息)
const final = await stream.result();
console.log(`输入 ${final.usage.input} tokens,输出 ${final.usage.output} tokens`);
console.log(`花费 $${final.usage.cost.total.toFixed(4)}`);2.4 completeSimple:不关心流式时
如果只是要最终答案(例如批处理任务),用 completeSimple 一行搞定:
typescript
// 非流式:等待完整响应再返回
const response = await models.completeSimple(model, context);
// 响应的 content 是内容块数组:text / thinking / toolCall
for (const block of response.content) {
if (block.type === "text") console.log(block.text);
}| API | 适用场景 | 返回 |
|---|---|---|
streamSimple | UI 实时输出、需要观察中间事件 | 异步事件迭代器 + .result() |
completeSimple | 后台任务、简单问答 | 最终 AssistantMessage |
2.5 统一接口的意义
同一份业务代码,切换模型只需改一行查找参数:
typescript
// 同一个 context 可以喂给任何已注册厂商的模型
const gpt = all.getModel("openai", "gpt-5-mini")!;
const ds = all.getModel("deepseek", "deepseek-chat")!;
// Anthropic 的回答
await models.completeSimple(model, context);
// DeepSeek 的回答 —— 业务代码零改动
await models.completeSimple(ds, context);消息格式、工具协议、错误语义全部由 pi-ai 抹平。这就是第 16 章「跨提供商 Handoffs」能无缝工作的根基。
2.6 本章小结
Models集合是路由器:按需setProvider()或用builtinModels()全量注册;streamSimple用统一选项发起流式调用,text_delta是增量文本事件;completeSimple用于不需要流的场景;- 所有响应统一携带
usage(token 数与成本),换模型不改业务代码。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. Provider 在 pi-ai 中负责哪三件事?
2. `streamSimple` 与 `stream` 的区别是什么?
3. 为什么生产环境推荐用 provider factory 按需注册而不是 providers/all?
4. Context 对象中每条消息都必须携带什么字段?
🛠️ 动手实践
- 分别用
streamSimple和completeSimple问同一个问题,对比终端输出形态与耗时感受。 - 注册两个不同的 Provider(如 Anthropic + DeepSeek),用同一个 context 各问一次「1+1」,验证业务代码零改动。
- 打印
finalMessage.usage全部字段,算出这次对话每百万 token 的实际成本。
掌握了统一 API 后,进入第 3 章深挖模型目录与 Provider 查询。