Skip to content

Provider 抽象と 9 社の組み込み登録

源码版本v0.73.1

@mariozechner/pi-ai が provider に要求する定義は三つのフィールドだけだ: apistreamstreamSimpleregister-builtins.tsregisterApiProvider を使って 9 社の実装をレジストリに登録する。登録時に具体的な provider モジュールを直接 import するのではなく、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 を export して呼び出し側に委ねるのではなく、ファイル末尾のトップレベルで呼ぶのか? stream.ts の先頭が import "./providers/register-builtins.js" しており、副作用のための import だからだ。誰かが @mariozechner/pi-aistream を import すれば、登録が起きる。呼び出し側は設定ゼロで使える。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 実装 を参照。