第一章:pi-ai 包概述 -- 为什么需要统一 LLM API

面临的问题

假设你要做一个 AI 编码助手。你的用户可能使用不同的 LLM 服务商:

  • 有人用 OpenAI 的 GPT 系列
  • 有人用 Anthropic 的 Claude 系列
  • 有人用 Google 的 Gemini 系列
  • 有人用 xAIGroqMistral 等等

问题是,每个服务商的 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 认证工具

核心设计原则

  1. 调用者无需关心 provider 差异:你写 stream(model, context) 就够了,具体走哪个 API 由 model.api 字段自动决定

  2. 错误不抛异常,而是编码在事件流中:这一点很重要。传统做法是出错就 throw Error,但在流式场景下,你可能已经收到了一部分响应,直接抛异常会丢失这些数据。pi-ai 的做法是通过 error 事件通知你出错了,同时保留已收到的部分内容

  3. 懒加载 provider:并非启动时就加载所有 20+ 个 provider 的代码。而是当你实际使用某个 provider 时才加载它的实现模块,节省启动时间和内存

  4. 模型数据自动生成:通过脚本从各服务商的 API 拉取最新模型列表,生成 models.generated.ts 文件。每次发版时更新,保持模型信息最新

下一步

下一章我们会详细拆解核心类型系统 -- 这是理解整个项目的基础。