Skip to content

Provider 抽象與 9 家內建註冊

源码版本v0.73.1

@mariozechner/pi-ai 對 provider 的定義只有三個欄位:apistreamstreamSimpleregister-builtins.tsregisterApiProvider 把 9 家廠商的實作註冊進表裡。註冊時不直接 import 具体 provider 模組,而是用 createLazyStream / createLazySimpleStream 包一層 dynamic import——首次呼叫才載入 SDK,模組體積和啟動時間都不被拖累。

職責

  1. 定義 Provider 抽象:ApiProvider 介面要求 api 識別碼 + stream / streamSimple 兩個函式,見 packages/ai/src/api-registry.ts:23-27
  2. 懶載入包裝:createLazyStream / createLazySimpleStream 回傳一個同步 stream 函式,內部 import() 具體模組後才 forwardStream,見 packages/ai/src/providers/register-builtins.ts:159-178
  3. 9 家註冊:registerBuiltInApiProviders 逐家 registerApiProvider,見 packages/ai/src/providers/register-builtins.ts:342-396
  4. 模組載入即註冊:檔案結尾頂層 registerBuiltInApiProviders() 呼叫,只要 stream.ts import 了它就觸發,見 packages/ai/src/providers/register-builtins.ts:403-403
  5. 9 種 Api 型別:KnownApi 聯合型別列舉 9 個字面量,見 packages/ai/src/types.ts:6-17

設計動機

為什麼所有 provider 都懶載入?因為有些 SDK 重——Bedrock 拖 AWS SDK 整個 @aws-sdk/client-bedrock-runtime,OpenAI Responses 拉一堆依賴。如果頂層靜態 import,即便使用者只用 Anthropic,也得載入所有 SDK。createLazyStream 把首次呼叫變成 dynamic import,啟動只解析 register-builtins.ts 本身這幾十行,真正用哪家才載入哪家。

為什麼 registerBuiltInApiProviders 在檔案末尾頂層呼叫而不是匯出後由呼叫方決定?因為 stream.ts 頂部就 import "./providers/register-builtins.js",這是副作用 import——只要有人 import 了 @mariozechner/pi-aistream,註冊就發生了。呼叫方零配置就能用。resetApiProviders 給測試場景重置用。

關鍵檔案

懶載入包裝的核心,外層 stream 立刻回傳,載入失敗也走 push 轉 error 事件:

typescript
// packages/ai/src/providers/register-builtins.ts:159-178
function createLazyStream<TApi extends Api, TOptions extends StreamOptions, TSimpleOptions extends SimpleStreamOptions>(
	loadModule: () => Promise<LazyProviderModule<TApi, TOptions, TSimpleOptions>>,
): StreamFunction<TApi, TOptions> {
	return (model, context, options) => {
		const outer = new AssistantMessageEventStream();
		loadModule()
			.then((module) => {
				const inner = module.stream(model, context, options);
				forwardStream(outer, inner);
			})
			.catch((error) => {
				const message = createLazyLoadErrorMessage(model, error);
				outer.push({ type: "error", reason: "error", error: message });
				outer.end(message);
			});
		return outer;
	};
}

9 家註冊就是逐個 registerApiProvider,每家一個 api 字面量:

typescript
// packages/ai/src/providers/register-builtins.ts:343-347
	registerApiProvider({
		api: "anthropic-messages",
		stream: streamAnthropic,
		streamSimple: streamSimpleAnthropic,
	});

資料流

provider 註冊與首次呼叫兩段:

邊界與失敗

小結

Provider 抽象 = api + stream + streamSimple 三件套,9 家內建全部走 createLazyStream 懶載入。門面派發邏輯看 stream/complete 門面,註冊表資料結構看 Provider 註冊表,具體某家的 SSE 解析看 Anthropic SSE 實作