深入剖析 openai-node 源码:一个工业级 TypeScript SDK 的架构之美

摘要:拆解 openai-node 九大设计亮点------惰性 Promise、SSE 三层流水线、零拷贝流分裂、自动分页迭代器、工厂错误体系等,带你领略工业级 TypeScript SDK 的架构之美。

一、引言 🚀

作为一名长期关注 AI 应用开发的工程师,我一直在寻找一个值得深入学习的 SDK 代码库。直到我打开了 openai-node 的源码,发现这不仅是一个 API 封装库,更是一本关于"如何构建工业级 TypeScript SDK"的教科书。

为什么选择分析 openai-node?

  1. 官方出品:这是 OpenAI 官方维护的 TypeScript/JavaScript SDK
  2. Stainless 生成 :由 Stainless 代码生成平台构建,代表了现代 API SDK 的最佳实践
  3. 跨平台支持:支持 Node.js / Browser / Deno / Cloudflare Workers / Vercel Edge,是真正的"universal" SDK
  4. 设计精良:大量使用高级 TypeScript 特性,设计模式运用得当

本文分析的版本是 6.32.0,让我们一起揭开这个优秀 SDK 的神秘面纱。


二、架构全景:五层分层设计 🏗️

在深入细节之前,我们先鸟瞰整体架构。openai-node 采用了清晰的五层分层设计:

graph TB subgraph "用户接口层 Public API" A[OpenAI Client] B[20+ Resource Namespaces] A --> B end subgraph "核心抽象层 Core Abstractions" C[APIPromise] D[Stream] E[AbstractPage] F[APIError] end subgraph "高级助手层 Helper Libraries" G[ChatCompletionStream] H[ChatCompletionRunner] I[AssistantStream] J[Zod Integration] end subgraph "内部基础设施层 Internal Infrastructure" K[Request Builder] L[Header Builder] M[Response Parser] N[Upload Handler] O[Platform Detection] end subgraph "传输层 Transport" P[fetch/HTTP] Q[WebSocket/Realtime] end B --> C B --> D B --> E C --> F D --> F G --> D H --> C I --> D J --> G C --> K C --> L C --> M D --> M K --> P Q --> Q

分层职责

层级 职责 核心组件
用户接口层 提供友好的 API 调用接口 OpenAI, client.chat.completions
核心抽象层 封装通用的请求/响应处理 APIPromise, Stream, AbstractPage
高级助手层 提供流式处理、工具调用等高级功能 ChatCompletionStream, runTools()
内部基础设施层 处理请求构建、响应解析、平台适配 buildRequest(), buildHeaders()
传输层 实际的网络通信 fetch, WebSocket

设计洞察:每层职责清晰、依赖单向向下,这种分层让代码高度可测试、可扩展。比如 Azure 适配只需要在用户接口层做很小的改动,底层完全复用。


三、设计亮点一:惰性 Promise --- APIPromise 的精妙设计 ✨

问题引入

传统 Promise 在创建时就开始执行,但 API 响应的解析方式多种多样:

  • 有时候需要 JSON 解析
  • 有时候需要流式处理
  • 有时候只要原始 Response 对象
  • 有时候需要同时拿到数据和 headers

如何在不牺牲类型安全的前提下,提供这种灵活性?

解决方案

APIPromise 继承自 Promise,但构造函数中 resolve(null) ,不触发解析。只有在 .then/.catch/.finally 被调用时才真正解析响应。

源码分析

typescript 复制代码
// src/core/api-promise.ts
export class APIPromise<T> extends Promise<WithRequestID<T>> {
  private parsedPromise: Promise<WithRequestID<T>> | undefined;
  #client: OpenAI;

  constructor(
    client: OpenAI,
    private responsePromise: Promise<APIResponseProps>,
    private parseResponse: (
      client: OpenAI,
      props: APIResponseProps,
    ) => PromiseOrValue<WithRequestID<T>> = defaultParseResponse,
  ) {
    super((resolve) => {
      // 关键!这里 resolve(null) 而不是立即解析
      // 这让 Promise 处于 fulfilled 状态,但实际解析被延迟
      resolve(null as any);
    });
    this.#client = client;
  }

  // 惰性解析:只在第一次需要时才解析,且结果被缓存
  private parse(): Promise<WithRequestID<T>> {
    if (!this.parsedPromise) {
      this.parsedPromise = this.responsePromise.then((data) =>
        this.parseResponse(this.#client, data),
      ) as any as Promise<WithRequestID<T>>;
    }
    return this.parsedPromise;
  }

  // 重写 then/catch/finally,重定向到 parse()
  override then<TResult1 = WithRequestID<T>, TResult2 = never>(
    onfulfilled?: ((value: WithRequestID<T>) => TResult1 | PromiseLike<TResult1>) | undefined | null,
    onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
  ): Promise<TResult1 | TResult2> {
    return this.parse().then(onfulfilled, onrejected);
  }

  // 获取原始 Response(不解析 body)
  asResponse(): Promise<Response> {
    return this.responsePromise.then((p) => p.response);
  }

  // 同时获取解析数据 + 原始 Response + request_id
  async withResponse(): Promise<{ data: T; response: Response; request_id: string | null }> {
    const [data, response] = await Promise.all([this.parse(), this.asResponse()]);
    return { data, response, request_id: response.headers.get('x-request-id') };
  }

  // 支持响应字段提取变换
  _thenUnwrap<U>(transform: (data: T, props: APIResponseProps) => U): APIPromise<U> {
    return new APIPromise(this.#client, this.responsePromise, async (client, props) =>
      addRequestID(transform(await this.parseResponse(client, props), props), props.response),
    );
  }
}

使用方式展示

typescript 复制代码
// 标准用法 - 自动解析 JSON
const completion = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Hello!' }],
});

// 获取原始 Response(不解析 body)
const response = await client.chat.completions.create({...}).asResponse();
console.log(response.status);
console.log(response.headers.get('x-request-id'));

// 同时获取数据和元信息
const { data, response, request_id } = await client.chat.completions.create({...}).withResponse();
console.log(`Request ID: ${request_id}`);
console.log(`Data: ${data.choices[0].message.content}`);

// 字段提取变换(内部使用)
const unwrapped = promise._thenUnwrap((data) => data.choices[0].message);

设计洞察

这种"惰性 Promise"模式解决了一个常见矛盾:想获取 headers 但又想要类型安全的解析结果

传统做法是返回一个包含 dataresponse 的对象,但这破坏了直接 await 的简洁性。APIPromise 通过继承 Promise 并重写方法,既保留了 await 的简洁,又提供了 asResponse()withResponse() 两个逃生通道。

这是我见过最优雅的 API 响应处理设计。


四、设计亮点二:SSE 流处理 --- 三层流水线架构 🌊

问题引入

OpenAI API 的流式响应使用 SSE (Server-Sent Events) 格式:

vbnet 复制代码
event: message
data: {"id": "chatcmpl-xxx", "choices": [{"delta": {"content": "Hello"}}]}

event: message
data: {"id": "chatcmpl-xxx", "choices": [{"delta": {"content": " World"}}]}

data: [DONE]

处理这种格式需要:

  1. ReadableStream 读取二进制块
  2. \n\n 分割成 SSE 消息
  3. 解析 event:data: 字段
  4. 同时支持 AsyncIterable 接口

三层流水线

graph LR A[ReadableStream] --> B[iterSSEChunks] B --> C[LineDecoder] C --> D[SSEDecoder] D --> E[AsyncIterable] B -->|扫描双换行| B C -->|按行分割| C D -->|状态机解析| D

源码分析

第一层:iterSSEChunks --- 二进制块累积

typescript 复制代码
// src/core/streaming.ts
async function* iterSSEChunks(iterator: AsyncIterableIterator<Bytes>): AsyncGenerator<Uint8Array> {
  let data = new Uint8Array();

  for await (const chunk of iterator) {
    if (chunk == null) continue;

    // 统一转为 Uint8Array
    const binaryChunk =
      chunk instanceof ArrayBuffer ? new Uint8Array(chunk)
      : typeof chunk === 'string' ? encodeUTF8(chunk)
      : chunk;

    // 累积到 buffer
    let newData = new Uint8Array(data.length + binaryChunk.length);
    newData.set(data);
    newData.set(binaryChunk, data.length);
    data = newData;

    // 扫描双换行边界(\n\n 或 \r\n\r\n)
    let patternIndex;
    while ((patternIndex = findDoubleNewlineIndex(data)) !== -1) {
      yield data.slice(0, patternIndex);
      data = data.slice(patternIndex);
    }
  }

  if (data.length > 0) {
    yield data;
  }
}

第二层:LineDecoder --- 按行分割

typescript 复制代码
// src/internal/decoders/line.ts
export class LineDecoder {
  #buffer: Uint8Array;
  #carriageReturnIndex: number | null;

  constructor() {
    this.#buffer = new Uint8Array();
    this.#carriageReturnIndex = null;
  }

  decode(chunk: Bytes): string[] {
    // 处理 \n 和 \r\n 两种换行格式
    // 使用 #carriageReturnIndex 处理跨块的 \r\n
    // ...
  }

  flush(): string[] {
    if (!this.#buffer.length) return [];
    return this.decode('\n');
  }
}

第三层:SSEDecoder --- 状态机解析

typescript 复制代码
// src/core/streaming.ts
class SSEDecoder {
  private data: string[];
  private event: string | null;
  private chunks: string[];

  decode(line: string) {
    // 处理行尾 \r
    if (line.endsWith('\r')) {
      line = line.substring(0, line.length - 1);
    }

    // 空行表示消息结束
    if (!line) {
      if (!this.event && !this.data.length) return null;

      const sse: ServerSentEvent = {
        event: this.event,
        data: this.data.join('\n'),  // 多行 data 拼接
        raw: this.chunks,
      };

      // 重置状态
      this.event = null;
      this.data = [];
      this.chunks = [];
      return sse;
    }

    this.chunks.push(line);

    // 注释行(以 : 开头)
    if (line.startsWith(':')) return null;

    // 解析 field: value
    let [fieldname, _, value] = partition(line, ':');
    if (value.startsWith(' ')) value = value.substring(1);

    if (fieldname === 'event') {
      this.event = value;
    } else if (fieldname === 'data') {
      this.data.push(value);
    }

    return null;
  }
}

Stream 类设计

typescript 复制代码
// src/core/streaming.ts
export class Stream<Item> implements AsyncIterable<Item> {
  controller: AbortController;

  constructor(
    private iterator: () => AsyncIterator<Item>,
    controller: AbortController,
    client?: OpenAI,
  ) {
    this.controller = controller;
  }

  // 工厂方法:从 SSE Response 创建 Stream
  static fromSSEResponse<Item>(
    response: Response,
    controller: AbortController,
    client?: OpenAI,
  ): Stream<Item> {
    let consumed = false;

    async function* iterator(): AsyncIterator<Item> {
      if (consumed) {
        throw new OpenAIError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
      }
      consumed = true;
      
      let done = false;
      try {
        for await (const sse of _iterSSEMessages(response, controller)) {
          if (done) continue;
          if (sse.data.startsWith('[DONE]')) {
            done = true;
            continue;
          }
          
          // 解析 JSON,处理 error 事件
          const data = JSON.parse(sse.data);
          if (data?.error) {
            throw new APIError(undefined, data.error, undefined, response.headers);
          }
          yield data;
        }
      } catch (e) {
        if (isAbortError(e)) return;
        throw e;
      } finally {
        if (!done) controller.abort();
      }
    }

    return new Stream(iterator, controller, client);
  }

  // 零拷贝流分裂
  tee(): [Stream<Item>, Stream<Item>] {
    const left: Array<Promise<IteratorResult<Item>>> = [];
    const right: Array<Promise<IteratorResult<Item>>> = [];
    const iterator = this.iterator();

    const teeIterator = (queue: Array<Promise<IteratorResult<Item>>>): AsyncIterator<Item> => {
      return {
        next: () => {
          if (queue.length === 0) {
            const result = iterator.next();
            left.push(result);   // 两个队列共享同一个 Promise
            right.push(result);
          }
          return queue.shift()!;
        },
      };
    };

    return [
      new Stream(() => teeIterator(left), this.controller),
      new Stream(() => teeIterator(right), this.controller),
    ];
  }

  // 转为 NDJSON ReadableStream(用于服务端转发)
  toReadableStream(): ReadableStream {
    const self = this;
    let iter: AsyncIterator<Item>;

    return makeReadableStream({
      async start() {
        iter = self[Symbol.asyncIterator]();
      },
      async pull(ctrl: any) {
        const { value, done } = await iter.next();
        if (done) return ctrl.close();
        ctrl.enqueue(encodeUTF8(JSON.stringify(value) + '\n'));
      },
      async cancel() {
        await iter.return?.();
      },
    });
  }

  [Symbol.asyncIterator](): AsyncIterator<Item> {
    return this.iterator();
  }
}

使用方式

typescript 复制代码
// 基本流式消费
const stream = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Write a poem' }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || '');
}

// 流分裂:一份用于日志,一份用于业务处理
const [logStream, bizStream] = stream.tee();

// 日志记录
(async () => {
  for await (const chunk of logStream) {
    console.log('[LOG]', JSON.stringify(chunk));
  }
})();

// 业务处理
for await (const chunk of bizStream) {
  // 处理业务逻辑
}

// 转为 ReadableStream 用于 HTTP 响应转发
return new Response(stream.toReadableStream(), {
  headers: { 'Content-Type': 'application/x-ndjson' },
});

设计洞察

流水线模式让每一层职责单一:

  • iterSSEChunks 只负责找 \n\n 边界
  • LineDecoder 只负责按 \n 分行
  • SSEDecoder 只负责解析 SSE 协议

tee() 的实现尤其精妙:两个队列共享同一个底层迭代器的 Promise,避免了数据复制。只有当两个消费者速度不一致时,才会在各自队列中暂存未消费的 Promise。


五、设计亮点三:资源模式 --- 优雅的命名空间映射 📦

设计理念

每个 API endpoint 映射为一个 APIResource 子类,通过属性嵌套形成层级命名空间:

typescript 复制代码
client.chat.completions.create()
client.files.create()
client.beta.assistants.create()

APIResource 基类

这可能是整个 SDK 中最简洁的类:

typescript 复制代码
// src/core/resource.ts
export abstract class APIResource {
  protected _client: OpenAI;

  constructor(client: OpenAI) {
    this._client = client;
  }
}

只有 12 行代码!所有子类通过 this._client.post() 等方法发起请求。

资源实例化

在 Client 构造函数中一次性创建所有资源实例:

typescript 复制代码
// src/client.ts
export class OpenAI {
  completions: API.Completions = new API.Completions(this);
  chat: API.Chat = new API.Chat(this);
  embeddings: API.Embeddings = new API.Embeddings(this);
  files: API.Files = new API.Files(this);
  images: API.Images = new API.Images(this);
  audio: API.Audio = new API.Audio(this);
  moderations: API.Moderations = new API.Moderations(this);
  models: API.Models = new API.Models(this);
  fineTuning: API.FineTuning = new API.FineTuning(this);
  beta: API.Beta = new API.Beta(this);
  batches: API.Batches = new API.Batches(this);
  uploads: API.Uploads = new API.Uploads(this);
  responses: API.Responses = new API.Responses(this);
  realtime: API.Realtime = new API.Realtime(this);
  // ... 20+ resources
}

嵌套资源

Chat 类包含 Completions 子资源:

typescript 复制代码
// src/resources/chat/completions/completions.ts
export class Completions extends APIResource {
  messages: MessagesAPI.Messages = new MessagesAPI.Messages(this._client);

  create(body: ChatCompletionCreateParamsNonStreaming, options?: RequestOptions): APIPromise<ChatCompletion>;
  create(body: ChatCompletionCreateParamsStreaming, options?: RequestOptions): APIPromise<Stream<ChatCompletionChunk>>;
  create(body: ChatCompletionCreateParamsBase, options?: RequestOptions): APIPromise<Stream<ChatCompletionChunk> | ChatCompletion>;
  create(body: ChatCompletionCreateParams, options?: RequestOptions): APIPromise<ChatCompletion> | APIPromise<Stream<ChatCompletionChunk>> {
    return this._client.post('/chat/completions', { body, ...options, stream: body.stream ?? false });
  }
}

类型安全的流式重载

注意 create 方法的四个重载签名!这是 TypeScript 函数重载的典型应用:

typescript 复制代码
// stream: false 或 undefined → 返回 APIPromise<ChatCompletion>
const completion = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [...],
  stream: false,  // 或不传
});
// TypeScript 推断 completion 类型为 ChatCompletion

// stream: true → 返回 APIPromise<Stream<ChatCompletionChunk>>
const stream = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [...],
  stream: true,
});
// TypeScript 推断 stream 类型为 Stream<ChatCompletionChunk>

设计洞察

这种资源模式让 API 调用像操作对象属性一样自然:client.chat.completions.create() 读起来就像 "client 的 chat 的 completions 的 create 方法"。

TypeScript 重载让流式/非流式返回类型在编译期就确定,而不是运行时通过类型断言。这是真正的类型安全。


六、设计亮点四:自动分页 --- PagePromise 的双重身份 📄

问题引入

分页 API 需要手动管理页码/游标:

typescript 复制代码
// 传统做法:手动翻页
let page = await client.models.list();
while (page.hasNextPage()) {
  for (const model of page.data) {
    console.log(model.id);
  }
  page = await page.getNextPage();
}

能否让用户像遍历数组一样遍历所有数据?

解决方案

PagePromise 同时继承 APIPromise 和实现 AsyncIterable --- 可以 await 获取单页,也可以 for-await 遍历所有。

源码分析

AbstractPage:分页基类

typescript 复制代码
// src/core/pagination.ts
export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
  #client: OpenAI;
  protected options: FinalRequestOptions;
  protected response: Response;
  protected body: unknown;

  // 子类实现:获取当前页的数据
  abstract getPaginatedItems(): Item[];
  
  // 子类实现:构造下一页的请求参数
  abstract nextPageRequestOptions(): PageRequestOptions | null;

  hasNextPage(): boolean {
    const items = this.getPaginatedItems();
    if (!items.length) return false;
    return this.nextPageRequestOptions() != null;
  }

  async getNextPage(): Promise<this> {
    const nextOptions = this.nextPageRequestOptions();
    if (!nextOptions) {
      throw new OpenAIError('No next page expected');
    }
    return await this.#client.requestAPIList(this.constructor as any, nextOptions);
  }

  // 遍历所有页
  async *iterPages(): AsyncGenerator<this> {
    let page: this = this;
    yield page;
    while (page.hasNextPage()) {
      page = await page.getNextPage();
      yield page;
    }
  }

  // 遍历所有数据项
  async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
    for await (const page of this.iterPages()) {
      for (const item of page.getPaginatedItems()) {
        yield item;
      }
    }
  }
}

CursorPage:游标分页实现

typescript 复制代码
// src/core/pagination.ts
export class CursorPage<Item extends { id: string }> extends AbstractPage<Item> {
  data: Array<Item>;
  has_more: boolean;

  getPaginatedItems(): Item[] {
    return this.data ?? [];
  }

  override hasNextPage(): boolean {
    if (this.has_more === false) return false;
    return super.hasNextPage();
  }

  nextPageRequestOptions(): PageRequestOptions | null {
    const data = this.getPaginatedItems();
    const id = data[data.length - 1]?.id;  // 用最后一项的 id 作为游标
    if (!id) return null;

    return {
      ...this.options,
      query: { ...this.options.query, after: id },
    };
  }
}

PagePromise:桥接 Promise 和 AsyncIterable

typescript 复制代码
// src/core/pagination.ts
export class PagePromise<PageClass extends AbstractPage<Item>, Item>
  extends APIPromise<PageClass>
  implements AsyncIterable<Item>
{
  constructor(
    client: OpenAI,
    request: Promise<APIResponseProps>,
    Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass,
  ) {
    super(
      client,
      request,
      async (client, props) =>
        new Page(client, props.response, await defaultParseResponse(client, props), props.options),
    );
  }

  // 实现 AsyncIterable:自动遍历所有页的所有数据
  async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
    const page = await this;  // 先 await 获取第一页
    for await (const item of page) {  // 再遍历所有数据
      yield item;
    }
  }
}

使用方式

typescript 复制代码
// 方式一:自动遍历所有页的所有数据(最简洁)
for await (const model of client.models.list()) {
  console.log(model.id);
}

// 方式二:手动翻页(需要更多控制时)
const page = await client.models.list();
console.log(page.data);  // 第一页数据

if (page.hasNextPage()) {
  const nextPage = await page.getNextPage();
  console.log(nextPage.data);  // 第二页数据
}

// 方式三:遍历所有页(需要页级元信息时)
for await (const page of (await client.models.list()).iterPages()) {
  console.log(`Page has ${page.data.length} items`);
}

设计洞察

PagePromise 的双重身份让分页 API 的使用变得极其灵活:

  • 简单场景:for await (const item of client.models.list()) 一行搞定
  • 复杂场景:await client.models.list() 拿到 Page 对象后手动控制

这种设计让简单的事情保持简单,复杂的事情成为可能。


七、设计亮点五:错误处理 --- 工厂方法 + 类型体操 ⚠️

错误层级

graph TB A[OpenAIError] --> B[APIError] A --> L[LengthFinishReasonError] A --> M[ContentFilterFinishReasonError] B --> C[APIConnectionError] B --> D[BadRequestError<400>] B --> E[AuthenticationError<401>] B --> F[PermissionDeniedError<403>] B --> G[NotFoundError<404>] B --> H[ConflictError<409>] B --> I[UnprocessableEntityError<422>] B --> J[RateLimitError<429>] B --> K[InternalServerError<5xx>] C --> N[APIConnectionTimeoutError] C --> O[APIUserAbortError]

工厂方法模式

typescript 复制代码
// src/core/error.ts
export class APIError<
  TStatus extends number | undefined = number | undefined,
  THeaders extends Headers | undefined = Headers | undefined,
  TError extends Object | undefined = Object | undefined,
> extends OpenAIError {
  readonly status: TStatus;
  readonly headers: THeaders;
  readonly error: TError;
  readonly code: string | null | undefined;
  readonly param: string | null | undefined;
  readonly type: string | undefined;
  readonly requestID: string | null | undefined;

  // 工厂方法:根据 HTTP 状态码创建对应的错误子类
  static generate(
    status: number | undefined,
    errorResponse: Object | undefined,
    message: string | undefined,
    headers: Headers | undefined,
  ): APIError {
    if (!status || !headers) {
      return new APIConnectionError({ message, cause: castToError(errorResponse) });
    }

    const error = (errorResponse as Record<string, any>)?.['error'];

    if (status === 400) return new BadRequestError(status, error, message, headers);
    if (status === 401) return new AuthenticationError(status, error, message, headers);
    if (status === 403) return new PermissionDeniedError(status, error, message, headers);
    if (status === 404) return new NotFoundError(status, error, message, headers);
    if (status === 409) return new ConflictError(status, error, message, headers);
    if (status === 422) return new UnprocessableEntityError(status, error, message, headers);
    if (status === 429) return new RateLimitError(status, error, message, headers);
    if (status >= 500) return new InternalServerError(status, error, message, headers);

    return new APIError(status, error, message, headers);
  }
}

// 具体错误类型通过泛型参数精确描述
export class BadRequestError extends APIError<400, Headers> {}
export class RateLimitError extends APIError<429, Headers> {}

重试策略

typescript 复制代码
// src/client.ts
private async shouldRetry(response: Response): Promise<boolean> {
  // 服务端明确告知是否应该重试
  const shouldRetryHeader = response.headers.get('x-should-retry');
  if (shouldRetryHeader === 'true') return true;
  if (shouldRetryHeader === 'false') return false;

  // 请求超时、锁超时、限流、服务端错误 → 重试
  if (response.status === 408) return true;  // Request Timeout
  if (response.status === 409) return true;  // Conflict (lock timeout)
  if (response.status === 429) return true;  // Rate Limit
  if (response.status >= 500) return true;   // Server Error

  return false;
}

private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number {
  const initialRetryDelay = 0.5;  // 500ms
  const maxRetryDelay = 8.0;      // 8s

  const numRetries = maxRetries - retriesRemaining;

  // 指数退避:0.5s * 2^n,最大 8s
  const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay);

  // 25% 抖动,避免惊群效应
  const jitter = 1 - Math.random() * 0.25;

  return sleepSeconds * jitter * 1000;
}

使用方式

typescript 复制代码
try {
  await client.chat.completions.create({...});
} catch (e) {
  if (e instanceof OpenAI.RateLimitError) {
    // 限流,等待后重试
    console.log('Rate limited, retrying...');
  } else if (e instanceof OpenAI.AuthenticationError) {
    // API Key 无效
    console.error('Invalid API key');
  } else if (e instanceof OpenAI.APIConnectionError) {
    // 网络问题
    console.error('Network error:', e.message);
  } else {
    throw e;
  }
}

设计洞察

工厂方法 APIError.generate() 让 catch 可以精确捕获特定错误类型,而不是检查 error.status === 429

三个泛型参数 APIError<TStatus, THeaders, TError> 让子类通过具体类型参数精确描述:

  • BadRequestErrorstatus 一定是 400
  • RateLimitErrorstatus 一定是 429

这是 TypeScript 类型系统的高级应用。


八、设计亮点六:请求生命周期 --- 建造者模式的优雅编排 🔄

请求流程时序图

sequenceDiagram participant User participant Resource as resource.create() participant Client as client.post() participant Request as methodRequest() participant APIPromise participant BuildRequest as buildRequest() participant Fetch as fetchWithTimeout() participant Parse as defaultParseResponse() User->>Resource: create(params) Resource->>Client: post(path, opts) Client->>Request: methodRequest('post', path, opts) Request->>APIPromise: new APIPromise(makeRequest()) Note over APIPromise: 惰性 Promise
此时不执行请求 APIPromise-->>User: 返回 APIPromise User->>APIPromise: await / .then() APIPromise->>BuildRequest: buildRequest(options) Note over BuildRequest: URL 构建
Body 构建
Header 构建 BuildRequest-->>APIPromise: { req, url, timeout } APIPromise->>Fetch: fetchWithTimeout(url, req) Fetch-->>APIPromise: Response APIPromise->>Parse: defaultParseResponse() Parse-->>User: 解析后的数据

buildRequest 流水线

typescript 复制代码
// src/client.ts
async buildRequest(
  inputOptions: FinalRequestOptions,
  { retryCount = 0 }: { retryCount?: number } = {},
): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> {
  const options = { ...inputOptions };
  const { method, path, query, defaultBaseURL } = options;

  // 1. URL 构建
  const url = this.buildURL(path!, query as Record<string, unknown>, defaultBaseURL);
  
  // 2. 超时配置
  if ('timeout' in options) validatePositiveInteger('timeout', options.timeout);
  options.timeout = options.timeout ?? this.timeout;
  
  // 3. Body 构建(类型检测:JSON/FormData/Stream/Binary)
  const { bodyHeaders, body } = this.buildBody({ options });
  
  // 4. Header 构建(幂等性/认证/平台检测/NullableHeaders)
  const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount });

  const req: FinalizedRequestInit = {
    method,
    headers: reqHeaders,
    ...(options.signal && { signal: options.signal }),
    ...(body && { body }),
    ...this.fetchOptions,
    ...options.fetchOptions,
  };

  return { req, url, timeout: options.timeout };
}

Body 类型检测

typescript 复制代码
// src/client.ts
private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }) {
  if (!body) return { bodyHeaders: undefined, body: undefined };

  const headers = buildHeaders([rawHeaders]);
  
  if (
    ArrayBuffer.isView(body) ||              // TypedArray
    body instanceof ArrayBuffer ||           // ArrayBuffer
    body instanceof DataView ||              // DataView
    (typeof body === 'string' && headers.values.has('content-type')) ||  // 已指定 content-type 的字符串
    ((globalThis as any).Blob && body instanceof Blob) ||     // Blob/File
    body instanceof FormData ||              // FormData → multipart/form-data
    body instanceof URLSearchParams ||       // URLSearchParams → application/x-www-form-urlencoded
    ((globalThis as any).ReadableStream && body instanceof ReadableStream)  // 流式上传
  ) {
    return { bodyHeaders: undefined, body: body as BodyInit };
  } else if (typeof body === 'object' && (Symbol.asyncIterator in body || Symbol.iterator in body)) {
    // AsyncIterable → ReadableStream
    return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable<Uint8Array>) };
  } else {
    // 默认:JSON 序列化
    return this.#encoder({ body, headers });
  }
}

Header 构建与优先级

typescript 复制代码
// src/client.ts
private async buildHeaders({
  options,
  method,
  bodyHeaders,
  retryCount,
}: {
  options: FinalRequestOptions;
  method: HTTPMethod;
  bodyHeaders: HeadersLike;
  retryCount: number;
}): Promise<Headers> {
  // 幂等性 Header(非 GET 请求)
  let idempotencyHeaders: HeadersLike = {};
  if (this.idempotencyHeader && method !== 'get') {
    if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey();
    idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey;
  }

  // 合并 Headers(按优先级从低到高)
  const headers = buildHeaders([
    idempotencyHeaders,
    {
      Accept: 'application/json',
      'User-Agent': this.getUserAgent(),
      'X-Stainless-Retry-Count': String(retryCount),
      ...getPlatformHeaders(),           // 平台检测 Header
      'OpenAI-Organization': this.organization,
      'OpenAI-Project': this.project,
    },
    await this.authHeaders(options),     // 认证 Header
    this._options.defaultHeaders,        // 客户端默认 Header
    bodyHeaders,                         // Body 相关 Header
    options.headers,                     // 请求级 Header(最高优先级)
  ]);

  this.validateHeaders(headers);
  return headers.values;
}

NullableHeaders:显式移除默认 Header

typescript 复制代码
// src/internal/headers.ts
export type NullableHeaders = {
  [brand_privateNullableHeaders]: true;
  values: Headers;
  nulls: Set<string>;  // 显式设置为 null 的 header 名称
};

export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => {
  const targetHeaders = new Headers();
  const nullHeaders = new Set<string>();
  
  for (const headers of newHeaders) {
    for (const [name, value] of iterateHeaders(headers)) {
      if (value === null) {
        targetHeaders.delete(name);
        nullHeaders.add(name.toLowerCase());
      } else {
        targetHeaders.append(name, value);
        nullHeaders.delete(name.toLowerCase());
      }
    }
  }
  
  return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders };
};

使用示例:

typescript 复制代码
// 移除默认的 User-Agent header
await client.chat.completions.create({...}, {
  headers: { 'User-Agent': null },
});

设计洞察

建造者模式让每个构建步骤独立可测、可覆盖:

  • buildURL() 可以在子类中重写(Azure 需要不同的 URL 格式)
  • authHeaders() 可以在子类中重写(Azure 使用不同的认证方式)
  • buildBody() 智能检测类型,自动选择序列化方式

配置优先级 环境变量 → ClientOptions → RequestOptions 三层覆盖,让默认值合理、覆盖简单。


九、设计亮点七:高级流式助手 --- 事件驱动的 Delta 累积 🎯

ChatCompletionStream 设计

typescript 复制代码
// src/lib/ChatCompletionStream.ts
export class ChatCompletionStream<ParsedT = null>
  extends AbstractChatCompletionRunner<ChatCompletionStreamEvents<ParsedT>, ParsedT>
  implements AsyncIterable<ChatCompletionChunk>
{
  #params: ChatCompletionCreateParams | null;
  #currentChatCompletionSnapshot: ChatCompletionSnapshot | undefined;

  // 工厂方法
  static createChatCompletion<ParsedT>(
    client: OpenAI,
    params: ChatCompletionStreamParams,
    options?: RequestOptions,
  ): ChatCompletionStream<ParsedT> {
    const runner = new ChatCompletionStream<ParsedT>(params);
    runner._run(() =>
      runner._runChatCompletion(
        client,
        { ...params, stream: true },
        { ...options, headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' } },
      ),
    );
    return runner;
  }
}

Delta 累积器

每个 SSE chunk 携带增量 delta,Stream 将其累积到 ChatCompletionSnapshot

typescript 复制代码
// src/lib/ChatCompletionStream.ts
#accumulateChatCompletion(chunk: ChatCompletionChunk): ChatCompletionSnapshot {
  let snapshot = this.#currentChatCompletionSnapshot;
  const { choices, ...rest } = chunk;
  
  if (!snapshot) {
    snapshot = this.#currentChatCompletionSnapshot = { ...rest, choices: [] };
  } else {
    Object.assign(snapshot, rest);
  }

  for (const { delta, finish_reason, index, logprobs = null, ...other } of chunk.choices) {
    let choice = snapshot.choices[index];
    if (!choice) {
      choice = snapshot.choices[index] = { finish_reason, index, message: {}, logprobs, ...other };
    }

    // 累积 content
    if (delta.content) {
      choice.message.content = (choice.message.content || '') + delta.content;
      
      // 支持自动解析的响应格式时,尝试增量解析
      if (this.#getAutoParseableResponseFormat()) {
        choice.message.parsed = partialParse(choice.message.content);
      }
    }

    // 累积 refusal
    if (delta.refusal) {
      choice.message.refusal = (choice.message.refusal || '') + delta.refusal;
    }

    // 累积 tool_calls
    if (delta.tool_calls) {
      if (!choice.message.tool_calls) choice.message.tool_calls = [];
      
      for (const { index, id, type, function: fn } of delta.tool_calls) {
        const tool_call = (choice.message.tool_calls[index] ??= {});
        if (id) tool_call.id = id;
        if (type) tool_call.type = type;
        if (fn) tool_call.function ??= { name: fn.name ?? '', arguments: '' };
        if (fn?.name) tool_call.function!.name = fn.name;
        if (fn?.arguments) {
          tool_call.function!.arguments += fn.arguments;
          
          // 增量解析 JSON 参数
          if (shouldParseToolCall(this.#params, tool_call)) {
            tool_call.function!.parsed_arguments = partialParse(tool_call.function!.arguments);
          }
        }
      }
    }
  }
  
  return snapshot;
}

细粒度事件

typescript 复制代码
// src/lib/ChatCompletionStream.ts
export interface ChatCompletionStreamEvents<ParsedT = null> {
  // 内容事件
  content: (contentDelta: string, contentSnapshot: string) => void;
  'content.delta': (props: ContentDeltaEvent) => void;
  'content.done': (props: ContentDoneEvent<ParsedT>) => void;

  // 拒绝事件
  'refusal.delta': (props: RefusalDeltaEvent) => void;
  'refusal.done': (props: RefusalDoneEvent) => void;

  // 工具调用事件
  'tool_calls.function.arguments.delta': (props: FunctionToolCallArgumentsDeltaEvent) => void;
  'tool_calls.function.arguments.done': (props: FunctionToolCallArgumentsDoneEvent) => void;

  // Log probs 事件
  'logprobs.content.delta': (props: LogProbsContentDeltaEvent) => void;
  'logprobs.content.done': (props: LogProbsContentDoneEvent) => void;

  // Chunk 事件
  chunk: (chunk: ChatCompletionChunk, snapshot: ChatCompletionSnapshot) => void;
}

Tool Calling 自动循环

typescript 复制代码
// src/lib/AbstractChatCompletionRunner.ts
const DEFAULT_MAX_CHAT_COMPLETIONS = 10;

protected async _runTools<FunctionsArgs extends BaseFunctionsArgs>(
  client: OpenAI,
  params: ChatCompletionToolRunnerParams<FunctionsArgs>,
  options?: RunnerOptions,
) {
  const { maxChatCompletions = DEFAULT_MAX_CHAT_COMPLETIONS } = options || {};

  for (let i = 0; i < maxChatCompletions; ++i) {
    const chatCompletion = await this._createChatCompletion(client, {
      ...restParams,
      tool_choice,
      tools,
      messages: [...this.messages],
    }, options);

    const message = chatCompletion.choices[0]?.message;
    if (!message?.tool_calls?.length) {
      return;  // 没有工具调用,结束循环
    }

    // 处理每个工具调用
    for (const tool_call of message.tool_calls) {
      if (tool_call.type !== 'function') continue;
      
      const fn = functionsByName[tool_call.function.name];
      
      // 解析参数
      const parsed = isRunnableFunctionWithParse(fn) 
        ? await fn.parse(tool_call.function.arguments) 
        : tool_call.function.arguments;

      // 调用函数
      const rawContent = await fn.function(parsed, this);
      const content = this.#stringifyFunctionCallResult(rawContent);
      
      // 将结果添加到消息列表
      this._addMessage({ role: 'tool', tool_call_id: tool_call.id, content });
    }
  }
}

使用方式

typescript 复制代码
// 基本流式消费
const stream = client.beta.chat.completions.stream({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Tell me a story' }],
});

// 监听细粒度事件
stream.on('content.delta', ({ delta, snapshot }) => {
  process.stdout.write(delta);
});

stream.on('tool_calls.function.arguments.done', ({ name, arguments: args }) => {
  console.log(`Function called: ${name}(${args})`);
});

// 等待完成
const final = await stream.finalChatCompletion();
console.log('\n\nFinal:', final.choices[0].message.content);

// 使用 runTools 自动循环
const runner = client.chat.completions.runTools({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'What is the weather in Tokyo?' }],
  tools: [{
    type: 'function',
    function: {
      name: 'get_weather',
      parameters: { type: 'object', properties: { city: { type: 'string' } } },
      function: async ({ city }) => {
        return { temperature: 22, condition: 'sunny' };
      },
    },
  }],
});

const result = await runner.finalContent();

设计洞察

EventEmitter + Accumulator 是流式 AI 应用的最佳拍档

  • EventEmitter 提供灵活的订阅机制
  • Accumulator 将增量 delta 累积成完整状态
  • 用户可以选择监听 delta(实时显示)或等待 done(完整数据)

部分 JSON 解析(partialParse)让流式 tool call 参数可以在完成前就开始处理,进一步提升用户体验。


十、设计亮点八:Azure 适配 --- 模板方法的教科书应用 ☁️

问题

Azure OpenAI 服务与 OpenAI API 有以下差异:

  1. URL 格式/deployments/{model}/chat/completions vs /chat/completions
  2. 认证方式api-key header vs Authorization: Bearer
  3. 查询参数 :需要 api-version

如何最小化代码改动来支持 Azure?

解决方案

AzureOpenAI 继承 OpenAI,只重写 buildRequest()authHeaders() 两个方法:

typescript 复制代码
// src/azure.ts
export class AzureOpenAI extends OpenAI {
  deploymentName: string | undefined;
  apiVersion: string = '';

  constructor({
    baseURL = readEnv('OPENAI_BASE_URL'),
    apiKey = readEnv('AZURE_OPENAI_API_KEY'),
    apiVersion = readEnv('OPENAI_API_VERSION'),
    endpoint,
    deployment,
    azureADTokenProvider,
    ...opts
  }: AzureClientOptions = {}) {
    // 验证参数
    if (!apiVersion) {
      throw new Errors.OpenAIError('The OPENAI_API_VERSION environment variable is missing');
    }
    if (!azureADTokenProvider && !apiKey) {
      throw new Errors.OpenAIError('Missing credentials');
    }
    if (azureADTokenProvider && apiKey) {
      throw new Errors.OpenAIError('apiKey and azureADTokenProvider are mutually exclusive');
    }

    // 自动添加 api-version 到所有请求
    opts.defaultQuery = { ...opts.defaultQuery, 'api-version': apiVersion };

    // 构建 baseURL
    if (!baseURL) {
      if (!endpoint) endpoint = process.env['AZURE_OPENAI_ENDPOINT'];
      if (!endpoint) throw new Errors.OpenAIError('Must provide endpoint or baseURL');
      baseURL = `${endpoint}/openai`;
    }

    super({ apiKey: azureADTokenProvider ?? apiKey, baseURL, ...opts });

    this.apiVersion = apiVersion;
    this.deploymentName = deployment;
  }

  // 重写:修改 URL 格式
  override async buildRequest(
    options: FinalRequestOptions,
    props: { retryCount?: number } = {},
  ): Promise<{ req: RequestInit & { headers: Headers }; url: string; timeout: number }> {
    // 对于特定 endpoint,将 model 插入到 URL 中
    if (_deployments_endpoints.has(options.path) && options.method === 'post' && options.body) {
      const model = this.deploymentName || options.body['model'] || options.__metadata?.['model'];
      if (model !== undefined && !this.baseURL.includes('/deployments')) {
        options.path = `/deployments/${model}${options.path}`;
      }
    }
    return super.buildRequest(options, props);
  }

  // 重写:修改认证方式
  protected override async authHeaders(opts: FinalRequestOptions): Promise<NullableHeaders | undefined> {
    if (typeof this._options.apiKey === 'string') {
      return buildHeaders([{ 'api-key': this.apiKey }]);  // 使用 api-key 而非 Authorization
    }
    return super.authHeaders(opts);  // Azure AD Token Provider
  }
}

// 需要 deployment 的 endpoint 列表
const _deployments_endpoints = new Set([
  '/completions',
  '/chat/completions',
  '/embeddings',
  '/audio/transcriptions',
  '/audio/translations',
  '/audio/speech',
  '/images/generations',
  '/batches',
  '/images/edits',
]);

使用方式

typescript 复制代码
import { AzureOpenAI } from 'openai';

const client = new AzureOpenAI({
  endpoint: 'https://my-resource.openai.azure.com',
  apiKey: process.env.AZURE_OPENAI_API_KEY,
  apiVersion: '2024-02-15-preview',
  deployment: 'my-gpt-4-deployment',
});

// 使用方式与 OpenAI 完全相同
const completion = await client.chat.completions.create({
  model: 'gpt-4',  // 会被自动替换为 deployment
  messages: [{ role: 'user', content: 'Hello!' }],
});

设计洞察

这是模板方法模式的教科书应用:

  • 基类 OpenAI 定义了请求的整体流程(模板方法)
  • 子类 AzureOpenAI 通过重写钩子方法(buildRequest, authHeaders)来定制行为
  • 完全不同的认证和路由体系只需要最小的代码改动

这种设计让未来支持其他 OpenAI 兼容服务(如 Anthropic、本地部署)变得非常简单。


十一、设计亮点九:Realtime WebSocket --- 双范式并存 🔌

REST API vs Realtime API

特性 REST API (HTTP) Realtime API (WebSocket)
通信模式 请求-响应 全双工
连接 每次请求新建 长连接
认证 Authorization header subprotocol
数据格式 JSON / SSE JSON events

WebSocket 连接

typescript 复制代码
// src/beta/realtime/websocket.ts
export class OpenAIRealtimeWebSocket extends OpenAIRealtimeEmitter {
  url: URL;
  socket: WebSocket;

  constructor(
    props: { model: string; dangerouslyAllowBrowser?: boolean },
    client?: Pick<OpenAI, 'apiKey' | 'baseURL'>,
  ) {
    super();
    
    // 浏览器安全检查
    if (!dangerouslyAllowBrowser && isRunningInBrowser()) {
      throw new OpenAIError("It looks like you're running in a browser-like environment...");
    }

    // 构建 WebSocket URL
    this.url = buildRealtimeURL(client, props.model);

    // API key 通过 subprotocol 传递(而非 header)
    this.socket = new WebSocket(this.url.toString(), [
      'realtime',
      `openai-insecure-api-key.${client.apiKey}`,  // API key in subprotocol
      'openai-beta.realtime-v1',
    ]);

    // 监听消息
    this.socket.addEventListener('message', (websocketEvent: MessageEvent) => {
      const event = JSON.parse(websocketEvent.data) as RealtimeServerEvent;
      
      this._emit('event', event);
      
      if (event.type === 'error') {
        this._onError(event);
      } else {
        this._emit(event.type, event);
      }
    });

    this.socket.addEventListener('error', (event: any) => {
      this._onError(null, event.message, null);
    });
  }

  // 发送事件
  send(event: RealtimeClientEvent) {
    this.socket.send(JSON.stringify(event));
  }

  // 关闭连接
  close(props?: { code: number; reason: string }) {
    this.socket.close(props?.code ?? 1000, props?.reason ?? 'OK');
  }
}

事件驱动架构

typescript 复制代码
// src/beta/realtime/internal-base.ts
type RealtimeEvents = {
  event: (event: RealtimeServerEvent) => void;
  error: (error: OpenAIRealtimeError) => void;
} & {
  [EventType in RealtimeServerEvent['type']]: (
    event: Extract<RealtimeServerEvent, { type: EventType }>,
  ) => unknown;
};

export abstract class OpenAIRealtimeEmitter extends EventEmitter<RealtimeEvents> {
  abstract send(event: RealtimeClientEvent): void;
  abstract close(props?: { code: number; reason: string }): void;

  protected _onError(event: ErrorEvent | null, message?: string, cause?: any): void {
    const error = new OpenAIRealtimeError(message, event);
    if (cause) error.cause = cause;
    this._emit('error', error);
  }
}

使用方式

typescript 复制代码
import { OpenAIRealtimeWebSocket } from 'openai/beta/realtime/websocket';

const rt = new OpenAIRealtimeWebSocket({
  model: 'gpt-4o-realtime-preview',
  dangerouslyAllowBrowser: true,
});

// 监听事件
rt.on('session.created', (event) => {
  console.log('Session created:', event.session.id);
});

rt.on('response.text.delta', (event) => {
  process.stdout.write(event.delta);
});

rt.on('error', (error) => {
  console.error('Error:', error.message);
});

// 发送事件
rt.send({
  type: 'conversation.item.create',
  item: {
    type: 'message',
    role: 'user',
    content: [{ type: 'input_text', text: 'Hello!' }],
  },
});

rt.send({ type: 'response.create' });

设计洞察

Realtime API 复用了核心的 EventEmitter 模式,保持了与 HTTP 流式 API 相似的编程模型:

  • 都是事件驱动
  • 都支持 on() 监听
  • 都有统一的错误处理

唯一的区别是:HTTP 流是单向的(只能接收),WebSocket 是双向的(可以发送和接收)。


十二、工程实践:构建系统与跨平台支持 🛠️

tsc-multi:一次编译,多种输出

json 复制代码
// tsc-multi.json
{
  "targets": [
    {
      "extname": ".js",
      "module": "commonjs",
      "shareHelpers": "internal/tslib.js"
    },
    {
      "extname": ".mjs",
      "module": "esnext",
      "shareHelpers": "internal/tslib.mjs"
    }
  ],
  "projects": ["tsconfig.build.json"]
}

一次 TypeScript 编译同时产出:

  • .js (CommonJS) - Node.js require()
  • .mjs (ESM) - Node.js import / Browser
  • .d.ts - TypeScript 类型定义

package.json exports 字段

json 复制代码
// package.json
{
  "type": "commonjs",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js"
    },
    "./*.mjs": {
      "default": "./dist/*.mjs"
    },
    "./*.js": {
      "default": "./dist/*.js"
    },
    "./*": {
      "import": "./dist/*.mjs",
      "require": "./dist/*.js"
    }
  }
}

这让同一个包可以被 CommonJS 和 ESM 项目使用。

可选 peer dependencies

json 复制代码
{
  "peerDependencies": {
    "ws": "^8.18.0",
    "zod": "^3.25 || ^4.0"
  },
  "peerDependenciesMeta": {
    "ws": { "optional": true },
    "zod": { "optional": true }
  }
}
  • ws:只有使用 Realtime API 时才需要
  • zod:只有使用 Structured Outputs 解析时才需要

这样核心功能不需要额外依赖,减少包体积。

平台检测与 Shim 层

typescript 复制代码
// src/internal/detect-platform.ts
export function isRunningInBrowser(): boolean {
  return (
    typeof window !== 'undefined' &&
    typeof window.document !== 'undefined' &&
    typeof navigator !== 'undefined'
  );
}

export function getPlatformHeaders(): Record<string, string> {
  // 根据运行环境返回不同的 header
  // X-Stainless-Runtime: node / browser / deno / cloudflare-worker / ...
  // X-Stainless-Runtime-Version: ...
}

十三、设计模式总览 📚

设计模式 应用位置 目的
工厂方法 APIError.generate() 根据状态码创建对应错误类型
模板方法 OpenAI.buildRequest() 定义请求构建流程,子类重写钩子
建造者模式 buildHeaders(), buildBody() 分步骤构建复杂对象
观察者模式 EventStream, EventEmitter 事件驱动的流式处理
迭代器模式 AsyncIterable, PagePromise 统一遍历接口
适配器模式 AzureOpenAI 适配不同的 API 服务
策略模式 parseResponse 参数 可替换的响应解析策略
装饰器模式 _thenUnwrap() 响应数据变换
单例模式 parsedPromise 缓存 惰性计算 + 结果缓存
代理模式 APIPromise 延迟 Promise 解析
状态机模式 SSEDecoder SSE 协议解析
流水线模式 SSE 三层解析 职责分离的数据处理

十四、总结与启发 💡

openai-node 的核心设计哲学

  1. 惰性求值 :不到最后一刻不做计算(APIPromise
  2. 类型安全:充分利用 TypeScript 类型系统(函数重载、泛型约束)
  3. 关注点分离:五层架构,每层职责清晰
  4. 最小侵入式扩展:模板方法 + 钩子函数,让扩展不需要修改核心代码

对我们自己构建 SDK 的启发

  1. Promise 可以被创造性地延展

    • 继承 Promise,重写 .then()/.catch()/.finally()
    • 提供 asResponse()withResponse() 等逃生通道
  2. 分层 + 模板方法 = 可扩展的多云适配

    • 核心流程定义在基类
    • 差异点提取为可重写的钩子方法
  3. AsyncIterable 是流和分页的统一抽象

    • 流式响应:for await (const chunk of stream)
    • 分页 API:for await (const item of page)
  4. 工厂方法让错误处理更精确

    • 根据状态码自动选择错误类型
    • 用户可以 catch (e) { if (e instanceof RateLimitError) ... }
  5. EventEmitter + Accumulator 是流式 AI 应用的最佳拍档

    • Delta 增量 + Snapshot 快照
    • 细粒度事件让前端可以做更好的 UX

结语

openai-node 是一个精心设计的 SDK,每一个设计决策都经过深思熟虑。从惰性 Promise 到 SSE 流水线,从资源模式到 Azure 适配,处处体现着工程美学。

如果你正在构建自己的 API SDK,强烈建议通读这份源码。它不仅教你如何封装 API,更教你如何写出可维护、可扩展、类型安全的 TypeScript 代码。

希望这篇分析对你有帮助。如果有任何问题或想法,欢迎讨论!


相关推荐
岛雨QA2 小时前
Skill学习指南🧑‍💻
人工智能·agent·ai编程
波动几何2 小时前
从人性到无名:一条向内的觉悟之路
人工智能
毛骗导演2 小时前
@tencent-weixin/openclaw-weixin 插件深度解析(四):API 协议与数据流设计
前端·架构
毛骗导演2 小时前
@tencent-weixin/openclaw-weixin 插件深度解析(二):消息处理系统架构
前端·架构
EllenLiu2 小时前
架构演进与性能压榨:在金融 RAG 中引入条款森林 (FoC)
人工智能·架构
IT_陈寒2 小时前
深入理解JavaScript:核心原理与最佳实践
前端·人工智能·后端
Presto2 小时前
AI 时代 .env 文件不再安全——我试图找到替代方案,然后撞上了一堵墙
人工智能
IT WorryFree2 小时前
OpenClaw-Medical-Skills 仓库介绍
人工智能·skill·openclaw
多年小白2 小时前
今日AI科技简报 | 2026年3月19日
人工智能·科技·ai编程