第一章:pi-ai 包概述 -- 为什么需要统一 LLM API
面临的问题
假设你要做一个 AI 编码助手。你的用户可能使用不同的 LLM 服务商:
- 有人用 OpenAI 的 GPT 系列
- 有人用 Anthropic 的 Claude 系列
- 有人用 Google 的 Gemini 系列
- 有人用 xAI、Groq、Mistral 等等
问题是,每个服务商的 API 都不一样:
- 请求格式不同:OpenAI 用
messages数组里放role: "user"和role: "assistant";Anthropic 的消息结构不同;Google 用完全不同的 protobuf 格式 - 流式响应格式不同:OpenAI 返回 SSE(Server-Sent Events)格式的 JSON 块;Anthropic 有自己的 event 类型
- 功能差异:有的支持"思考/推理"功能(thinking),有的不支持;有的支持图片输入,有的不支持
- 认证方式不同:有的用 API Key,有的用 OAuth(类似微信扫码登录),有的用 AWS 的复杂签名
如果你的应用直接对接每个服务商,代码会变成一团乱麻。每增加一个新服务商,你就要在无数地方加 if-else。
pi-ai 的解决方案:适配器模式(Adapter Pattern)
pi-ai 包的核心思路很简单:
定义一套统一的类型和接口,然后为每个服务商写一个"适配器"(adapter),把各家不同的 API 翻译成统一格式。
这就像一个万能充电头:无论你的插座是美标、欧标还是英标,插上适配器后,你的设备用同一根线就能充电。
你的应用代码
↓ (只用统一类型)
┌─────────────────────────┐
│ pi-ai 统一层 │
│ Model, Context, stream()│
└─────────┬───────────────┘
│ (适配器翻译)
┌─────┼─────┬─────┬──────┐
▼ ▼ ▼ ▼ ▼
OpenAI Claude Google xAI Mistral ...
这个包解决了哪些具体问题
| 问题 | pi-ai 的解决方案 |
|---|---|
| 不同 API 请求格式 | 统一的 Context 类型(systemPrompt + messages + tools) |
| 不同的响应格式 | 统一的 AssistantMessage 类型 |
| 流式响应差异 | 统一的 AssistantMessageEvent 事件流协议 |
| 模型元数据不统一 | 统一的 Model 类型,自动生成的模型注册表 |
| 工具调用差异 | 统一的 Tool 定义 + ToolCall / ToolResultMessage |
| 费用计算 | 每个模型携带定价信息,自动计算 |
| 跨模型切换 | 同一段上下文可以在不同 provider 之间无缝传递 |
包的目录结构
packages/ai/src/
├── types.ts # 核心类型定义(本章重点)
├── stream.ts # 四个顶层调用入口
├── api-registry.ts # API 注册表(路由到具体 provider)
├── models.ts # 模型查询函数
├── models.generated.ts # 自动生成的模型数据库(384KB)
├── env-api-keys.ts # 环境变量中的 API Key 检测
├── index.ts # 包的公开导出
├── providers/ # 各服务商的适配器实现
│ ├── register-builtins.ts # 注册所有内置适配器
│ ├── transform-messages.ts # 通用消息转换
│ ├── simple-options.ts # 简化选项映射
│ ├── anthropic.ts # Anthropic 适配器
│ ├── openai-completions.ts # OpenAI Chat Completions 适配器
│ ├── openai-responses.ts # OpenAI Responses 适配器
│ ├── google.ts # Google Gemini 适配器
│ ├── mistral.ts # Mistral 适配器
│ ├── amazon-bedrock.ts # AWS Bedrock 适配器
│ ├── faux.ts # 测试用假 provider
│ └── ...
└── utils/ # 工具函数
├── event-stream.ts # EventStream 异步队列
├── validation.ts # 工具参数验证
├── json-parse.ts # 增量 JSON 解析
└── oauth/ # OAuth 认证工具
核心设计原则
-
调用者无需关心 provider 差异:你写
stream(model, context)就够了,具体走哪个 API 由model.api字段自动决定 -
错误不抛异常,而是编码在事件流中:这一点很重要。传统做法是出错就
throw Error,但在流式场景下,你可能已经收到了一部分响应,直接抛异常会丢失这些数据。pi-ai 的做法是通过error事件通知你出错了,同时保留已收到的部分内容 -
懒加载 provider:并非启动时就加载所有 20+ 个 provider 的代码。而是当你实际使用某个 provider 时才加载它的实现模块,节省启动时间和内存
-
模型数据自动生成:通过脚本从各服务商的 API 拉取最新模型列表,生成
models.generated.ts文件。每次发版时更新,保持模型信息最新
下一步
下一章我们会详细拆解核心类型系统 -- 这是理解整个项目的基础。