Skip to content

Provider-Abstraktion und 9 eingebaute Registrierungen

源码版本v0.73.1

@mariozechner/pi-ai definiert einen Provider über nur drei Felder: api, stream, streamSimple. register-builtins.ts registriert mit registerApiProvider die Implementierungen von 9 Anbietern in der Tabelle. Statt das konkrete Provider-Modul direkt zu importieren, wird ein dynamic import über createLazyStream / createLazySimpleStream gewickelt — erst beim ersten Aufruf wird das SDK geladen; Modulgröße und Startzeit bleiben verschont.

Verantwortung

  1. Provider-Abstraktion definieren: die ApiProvider-Schnittstelle verlangt einen api-Bezeichner plus die zwei Funktionen stream / streamSimple; siehe packages/ai/src/api-registry.ts:23-27.
  2. Lazy-Loading-Wickel: createLazyStream / createLazySimpleStream geben eine synchrone Stream-Funktion zurück, die intern per import() das Modul lädt und dann forwardStream aufruft; siehe packages/ai/src/providers/register-builtins.ts:159-178.
  3. Registrierung von 9 Anbietern: registerBuiltInApiProviders ruft pro Anbieter registerApiProvider auf; siehe packages/ai/src/providers/register-builtins.ts:342-396.
  4. Modul-Laden heißt Registrieren: am Dateiende wird auf oberster Ebene registerBuiltInApiProviders() aufgerufen; sobald stream.ts es importiert, wird es getriggert; siehe packages/ai/src/providers/register-builtins.ts:403-403.
  5. 9 Api-Typen: der KnownApi-Union-Typ zählt 9 String-Literale auf; siehe packages/ai/src/types.ts:6-17.

Entwurfsmotivation

Warum alle Provider lazy laden? Weil manche SDKs schwer sind — Bedrock zieht das gesamte @aws-sdk/client-bedrock-runtime, OpenAI Responses zieht einen Haufen Abhängigkeiten. Bei statischem Top-Level-Import müsste auch geladen werden, wer nur Anthropic nutzt. createLazyStream macht den ersten Aufruf zu einem dynamic import; beim Start wird nur register-builtins.ts selbst mit seinen paar Dutzend Zeilen geparst; erst wer einen Anbieter nutzt, lädt ihn.

Warum wird registerBuiltInApiProviders am Dateiende auf oberster Ebene aufgerufen und nicht exportiert, damit der Aufrufer entscheidet? Weil stream.ts oben import "./providers/register-builtins.js" steht — ein Side-Effect-Import; sobald irgendwer stream aus @mariozechner/pi-ai importiert, ist die Registrierung passiert. Aufrufer haben Zero-Config. resetApiProviders dient dem Reset in Tests.

Wichtige Dateien

Kern des Lazy-Wickels: der outer stream kehrt sofort zurück; bei Lade-Fehlern wird per push ein Error-Event gesendet:

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;
	};
}

Die Registrierung der 9 Anbieter ist jeweils ein registerApiProvider mit einem api-Literal:

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

Datenfluss

Provider-Registrierung und erster Aufruf in zwei Abschnitten:

Grenzen und Fehler

Zusammenfassung

Die Provider-Abstraktion ist das Dreigestirn aus api + stream + streamSimple; alle 9 Built-ins sind über createLazyStream lazy geladen. Die Verteillogik der Fassade steht in stream/complete-Fassade, die Datenstruktur der Registrierung in Provider-Registrierung, und wie eine konkrete Familie SSE parst in Anthropic SSE-Implementierung.