Skip to content

Abstraction provider et 9 intégrations enregistrées

源码版本v0.73.1

La définition d'un provider par @mariozechner/pi-ai tient en trois champs : api, stream, streamSimple. register-builtins.ts utilise registerApiProvider pour enregistrer les implémentations de 9 fournisseurs dans la table. À l'enregistrement, on n'importe pas directement le module provider concret, on l'enveloppe d'un dynamic import via createLazyStream / createLazySimpleStream—le SDK n'est chargé qu'au premier appel, la taille du module et le temps de démarrage n'en pâtissent pas.

Responsabilités

  1. Définir l'abstraction Provider : l'interface ApiProvider exige un identifiant api + deux fonctions stream / streamSimple, voir packages/ai/src/api-registry.ts:23-27.
  2. Enveloppe de lazy loading : createLazyStream / createLazySimpleStream renvoient une fonction stream synchrone; en interne, ils import() le module concret puis forwardStream, voir packages/ai/src/providers/register-builtins.ts:159-178.
  3. Enregistrement des 9 : registerBuiltInApiProviders appelle registerApiProvider pour chaque fournisseur, voir packages/ai/src/providers/register-builtins.ts:342-396.
  4. Chargement du module = enregistrement : appel registerBuiltInApiProviders() au niveau top-level en fin de fichier; il se déclenche dès que stream.ts l'importe, voir packages/ai/src/providers/register-builtins.ts:403-403.
  5. 9 types Api : le type union KnownApi énumère 9 littéraux, voir packages/ai/src/types.ts:6-17.

Motivation de design

Pourquoi tous les provider en lazy loading ? Parce que certains SDK sont lourds—Bedrock traîne tout l'AWS SDK @aws-sdk/client-bedrock-runtime, OpenAI Responses ramène plein de dépendances. Si on les importait en statique au top-level, même un utilisateur qui ne jure que par Anthropic devrait charger tous les SDK. createLazyStream transforme le premier appel en dynamic import; au démarrage on ne parse que les quelques dizaines de lignes de register-builtins.ts lui-même, et chaque SDK n'est chargé que lorsqu'il est réellement utilisé.

Pourquoi registerBuiltInApiProviders est appelée au top-level à la fin du fichier plutôt que d'être exportée pour que l'appelant décide ? Parce que stream.ts fait import "./providers/register-builtins.js" en tête—c'est un import à effet de bord—; dès que quelqu'un importe stream de @mariozechner/pi-ai, l'enregistrement se fait. L'appelant a zéro config à faire. resetApiProviders sert au reset côté tests.

Fichiers clés

Le cœur de l'enveloppe lazy : l'outer stream se retourne tout de suite, et un échec de chargement passe aussi par push pour transformer l'erreur en événement 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;
	};
}

L'enregistrement des 9, c'est un registerApiProvider par fournisseur, avec un littéral api chacun :

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

Flux de données

Enregistrement de provider et premier appel, en deux temps :

Frontières et échecs

Récapitulatif

L'abstraction Provider, c'est le triplet api + stream + streamSimple; les 9 intégrés passent tous par createLazyStream en lazy loading. La logique de dispatch de la façade se lit dans Façade stream/complete, la structure de données du registre (registry) dans Registre des provider, et l'analyse SSE d'un provider concret dans Implémentation Anthropic SSE.