Provider 抽象與 9 家內建註冊
@mariozechner/pi-ai 對 provider 的定義只有三個欄位:api、stream、streamSimple。register-builtins.ts 用 registerApiProvider 把 9 家廠商的實作註冊進表裡。註冊時不直接 import 具体 provider 模組,而是用 createLazyStream / createLazySimpleStream 包一層 dynamic import——首次呼叫才載入 SDK,模組體積和啟動時間都不被拖累。
職責
- 定義 Provider 抽象:
ApiProvider介面要求api識別碼 +stream/streamSimple兩個函式,見packages/ai/src/api-registry.ts:23-27。 - 懶載入包裝:
createLazyStream/createLazySimpleStream回傳一個同步 stream 函式,內部import()具體模組後才forwardStream,見packages/ai/src/providers/register-builtins.ts:159-178。 - 9 家註冊:
registerBuiltInApiProviders逐家registerApiProvider,見packages/ai/src/providers/register-builtins.ts:342-396。 - 模組載入即註冊:檔案結尾頂層
registerBuiltInApiProviders()呼叫,只要stream.tsimport 了它就觸發,見packages/ai/src/providers/register-builtins.ts:403-403。 - 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-ai 的 stream,註冊就發生了。呼叫方零配置就能用。resetApiProviders 給測試場景重置用。
關鍵檔案
packages/ai/src/types.ts:6-17—KnownApi9 個字面量 +Api允許擴充字串。packages/ai/src/api-registry.ts:23-27—ApiProvider介面,三件套。packages/ai/src/providers/register-builtins.ts:159-178—createLazyStream:外層 stream + 動態載入 + forwardStream + 錯誤 push。packages/ai/src/providers/register-builtins.ts:180-220—createLazySimpleStream,simple 版本同模式。packages/ai/src/providers/register-builtins.ts:323-340— 9 家 lazy 函式 + Bedrock 私有 lazy。packages/ai/src/providers/register-builtins.ts:342-396—registerBuiltInApiProviders主體。packages/ai/src/providers/register-builtins.ts:398-403—resetApiProviders和頂層註冊。packages/ai/src/types.ts:155-159—StreamFunction簽名,回傳AssistantMessageEventStream。
懶載入包裝的核心,外層 stream 立刻回傳,載入失敗也走 push 轉 error 事件:
// 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 字面量:
// packages/ai/src/providers/register-builtins.ts:343-347
registerApiProvider({
api: "anthropic-messages",
stream: streamAnthropic,
streamSimple: streamSimpleAnthropic,
});資料流
provider 註冊與首次呼叫兩段:
邊界與失敗
- 懶載入失敗:
loadModule().catch把 error 包成{ type: "error" }事件 push 進 outer stream,呼叫方for await能收到,見packages/ai/src/providers/register-builtins.ts:170-174。 - 9 家清單:
anthropic-messages、openai-completions、mistral-conversations、openai-responses、azure-openai-responses、openai-codex-responses、google-generative-ai、google-vertex、bedrock-converse-stream,見packages/ai/src/providers/register-builtins.ts:343-395。 - Bedrock 單獨處理:由於 AWS SDK 重量,
streamBedrockLazy/streamSimpleBedrockLazy沒 export,只內部用,見packages/ai/src/providers/register-builtins.ts:339-340。 - 擴充註冊:第三方擴充可以調
registerApiProvider註冊自己的api字串,Api型別用string & {}兜底,見packages/ai/src/types.ts:17-17。
小結
Provider 抽象 = api + stream + streamSimple 三件套,9 家內建全部走 createLazyStream 懶載入。門面派發邏輯看 stream/complete 門面,註冊表資料結構看 Provider 註冊表,具體某家的 SSE 解析看 Anthropic SSE 實作。