Skip to content

第 14 章 · 自定义 Provider 与 OpenAI 兼容端点

本章目标:用 createProvider() 接入本地推理服务器、企业网关与 DeepSeek 等 OpenAI 兼容端点,并用 compat 标志位解决兼容性差异。

14.1 为什么需要自定义 Provider

内置 Provider 目录覆盖主流服务商,但真实世界还有三类需求:

  1. 本地推理——Ollama、llama.cpp 等本地服务器;
  2. 企业代理/网关——统一鉴权、审计、限流的内部 LLM 网关;
  3. OpenAI 兼容三方——DeepSeek、Together AI 等兼容 openai-completions API 的服务商。

pi-ai 的答案是一个统一的组装函数:createProvider()

14.2 createProvider() 四要素

createProvider() 从四个部分构建一个 Provider:身份(id)、认证(auth)、模型列表(models)、API 实现(api):

typescript
import { createModels, createProvider, envApiKeyAuth, type Model } from '@earendil-works/pi-ai';

// 组装一个 Ollama 本地 Provider
const ollama = createProvider({
  id: 'ollama',
  name: 'Ollama (local)',
  auth: envApiKeyAuth('OLLAMA_API_KEY'), // 认证解析方式
  api: 'openai-completions',             // 复用 OpenAI 兼容 API 实现
  baseUrl: 'http://localhost:11434/v1',
  models: [
    {
      id: 'qwen3:32b',
      name: 'Qwen3 32B',
      reasoning: false,
      input: ['text'],
      // ... 其他 Model 元数据
    } as Model,
  ],
});

const models = createModels();
models.setProvider(ollama);

动态发布机制

createProvider() 自动处理模型的动态发布与持久化。自定义 Provider 的 refreshModels() 会收到只读的 context.stored 快照,并通过 context.publish({ persist?, update? }) 发布变更——不要在发布前直接改状态。

14.3 接入 DeepSeek 等 OpenAI 兼容端点

DeepSeek 是最典型的 OpenAI 兼容服务商。好消息是 pi-ai 对已知兼容商自动检测兼容性设置(DeepSeek 在内置列表中),零配置可用:

typescript
import { createModels, createProvider, envApiKeyAuth } from '@earendil-works/pi-ai';

const deepseek = createProvider({
  id: 'deepseek',
  name: 'DeepSeek',
  auth: envApiKeyAuth('DEEPSEEK_API_KEY'),
  api: 'openai-completions',
  baseUrl: 'https://api.deepseek.com/v1',
  models: [
    {
      id: 'deepseek-chat',
      name: 'DeepSeek Chat',
      reasoning: false,
      input: ['text'],
    },
    {
      id: 'deepseek-reasoner',
      name: 'DeepSeek Reasoner',
      reasoning: true, // R1 支持推理
      input: ['text'],
    },
  ],
});

14.4 compat:细粒度兼容性开关

对于不在自动检测名单里的自定义代理,可以用 compat 字段手动声明差异。常用的标志位:

typescript
const proxy = createProvider({
  id: 'corp-proxy',
  name: 'Corp Gateway',
  auth: envApiKeyAuth('CORP_LLM_KEY'),
  api: 'openai-completions',
  baseUrl: 'https://llm-gateway.corp.internal/v1',
  models: [/* ... */],
  compat: {
    supportsStore: false,            // 网关不支持 store 字段
    supportsDeveloperRole: false,    // 只认 system 不认 developer 角色
    maxTokensField: 'max_tokens',    // 老网关不认识 max_completion_tokens
    thinkingFormat: 'deepseek',      // 思考参数按 DeepSeek 格式发送
  },
});

常用 compat 标志速查:

标志作用
supportsStrictMode工具定义是否支持 strict JSON schema
requiresToolResultName工具结果是否必须带 name 字段
requiresThinkingAsText思考块是否必须转成文本
thinkingFormat推理参数格式(openai/deepseek/qwen 等)
cacheControlFormat: 'anthropic'Anthropic 风格的提示缓存控制

部分设置即继承检测结果

compat 只需写有差异的字段——未指定的字段沿用基于 baseUrl 的自动检测默认值。

14.5 直接调用 API 实现与模型级 headers

更底层的玩法是绕过目录直接调用 API 实现;另外单个模型还可以携带专属 headers(比如需要绕过 bot 检测的代理),这些头会自动并入请求:

typescript
// 自定义模型可携带 headers,请求时自动合并
const gateway = createProvider({
  id: 'gateway',
  name: 'LLM Gateway',
  auth: envApiKeyAuth('GATEWAY_KEY'),
  api: 'openai-completions',
  baseUrl: 'https://gw.example.com/v1',
  models: [
    {
      id: 'internal-chat',
      name: 'Internal Chat',
      headers: { 'X-Bot-Detection-Bypass': 'team-token' }, // 模型级请求头
    },
  ],
});

// 多租户网关:同一实现不同 baseUrl
const tenantGateway = createProvider({ /* 类似,换 baseUrl 与租户 key */ });

本章小结

  • 三类场景需要自定义 Provider:本地推理、企业网关、OpenAI 兼容三方;
  • createProvider() 由 id + auth + models + api 四部分组装,动态发布由库托管;
  • DeepSeek 等已知兼容商的 compat 设置会按 baseUrl 自动检测,通常零配置;
  • compat 只需声明有差异的字段,其余继承检测结果;
  • 模型级 headers 可注入绕过检测等自定义请求头。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. createProvider() 构建一个 Provider 需要哪几个核心部分?

2. 使用 DeepSeek 这类已知 OpenAI 兼容商时,compat 设置如何处理?

3. compat 中只设置了 thinkingFormat 一个字段,其他字段会怎样?

4. 某个模型的响应被企业网关的 bot 检测拦截,文档建议的做法是?

🛠️ 动手实践

  1. 用 createProvider() 把第 4 章的 Agent 切换到 Ollama 本地模型,验证离线运行。
  2. 配置 DeepSeek 的两个模型(chat/reasoner),分别跑同一个问题对比效果与成本。
  3. 搭一个最小 Nginx 反代作为"企业网关",故意去掉某个字段的支持,用 compat 标志修复。

测试驱动开发离不开 mock——下一章介绍专为测试设计的 Faux Provider。