Abstraction provider et 9 intégrations enregistrées
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
- Définir l'abstraction Provider : l'interface
ApiProviderexige un identifiantapi+ deux fonctionsstream/streamSimple, voirpackages/ai/src/api-registry.ts:23-27. - Enveloppe de lazy loading :
createLazyStream/createLazySimpleStreamrenvoient une fonction stream synchrone; en interne, ilsimport()le module concret puisforwardStream, voirpackages/ai/src/providers/register-builtins.ts:159-178. - Enregistrement des 9 :
registerBuiltInApiProvidersappelleregisterApiProviderpour chaque fournisseur, voirpackages/ai/src/providers/register-builtins.ts:342-396. - Chargement du module = enregistrement : appel
registerBuiltInApiProviders()au niveau top-level en fin de fichier; il se déclenche dès questream.tsl'importe, voirpackages/ai/src/providers/register-builtins.ts:403-403. - 9 types Api : le type union
KnownApiénumère 9 littéraux, voirpackages/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
packages/ai/src/types.ts:6-17— 9 littérauxKnownApi+Apiqui autorise l'extension par chaîne.packages/ai/src/api-registry.ts:23-27— interfaceApiProvider, le triplet.packages/ai/src/providers/register-builtins.ts:159-178—createLazyStream: outer stream + chargement dynamique + forwardStream + push d'erreur.packages/ai/src/providers/register-builtins.ts:180-220—createLazySimpleStream, version simple sur le même motif.packages/ai/src/providers/register-builtins.ts:323-340— 9 fonctions lazy + Bedrock lazy privé.packages/ai/src/providers/register-builtins.ts:342-396— corps deregisterBuiltInApiProviders.packages/ai/src/providers/register-builtins.ts:398-403—resetApiProviderset enregistrement top-level.packages/ai/src/types.ts:155-159— signature deStreamFunction, renvoieAssistantMessageEventStream.
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 :
// 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 :
// 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
- Échec du lazy loading :
loadModule().catchemballe l'erreur en événement{ type: "error" }poussé dans l'outer stream, l'appelant le reçoit viafor await, voirpackages/ai/src/providers/register-builtins.ts:170-174. - Liste des 9 :
anthropic-messages,openai-completions,mistral-conversations,openai-responses,azure-openai-responses,openai-codex-responses,google-generative-ai,google-vertex,bedrock-converse-stream, voirpackages/ai/src/providers/register-builtins.ts:343-395. - Bedrock à part : vu le poids de l'AWS SDK,
streamBedrockLazy/streamSimpleBedrockLazyne sont pas exportés, usage interne seulement, voirpackages/ai/src/providers/register-builtins.ts:339-340. - Enregistrement par extension : une extension tierce peut appeler
registerApiProviderpour enregistrer sa propre chaîneapi; le typeApiest rattrapé parstring & {}, voirpackages/ai/src/types.ts:17-17.
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.