Skip to content

stream/complete: Fassadeneingang des LLM-Aufrufs

源码版本v0.73.1

stream.ts ist die dünnste Schicht von @mariozechner/pi-ai nach außen — vier Funktionen stream / complete / streamSimple / completeSimple, plus eine interne resolveApiProvider. Sie parst kein SSE, baut keinen Request-Body, macht keine Retries, sondern routet nur das Triple (model, context, options) anhand von model.api an den passenden Provider (provider). Man kann sie als „Verteilzentrale zwischen Aufrufer und Provider-Implementierung" begreifen: der Aufrufer sieht nur den einheitlichen Rückgabetyp AssistantMessageEventStream, nicht ob darunter Anthropic SSE, OpenAI Responses oder Bedrock Converse liegt.

Verantwortung

Diese Schicht macht vier Dinge:

  1. Verteilung: stream sucht anhand von model.api den Provider in der Registrierung (registry) und reicht (model, context, options) weiter. Siehe packages/ai/src/stream.ts:25-32.
  2. Synchrone Stream-Rückgabe: egal ob der darunterliegende Provider asynchron initialisiert wird (lazy loading), stream gibt synchron einen AssistantMessageEventStream zurück; Events werden später hineingepusht. Siehe packages/ai/src/stream.ts:30-31.
  3. Simple-Eingänge: streamSimple / completeSimple nehmen SimpleStreamOptions (nur die für UIs üblichen Felder wie reasoning, thinkingBudgets) und der Provider übersetzt die Simple-Options selbst zu vollen Options. Siehe packages/ai/src/stream.ts:43-50.
  4. Fehler bei fehlendem Provider: resolveApiProvider wirft No API provider registered for api: <api>, wenn nichts registriert ist, anstatt an der Aufrufstelle stumm zu scheitern. Siehe packages/ai/src/stream.ts:17-23.

Entwurfsmotivation

Warum diese dünne Schicht? Weil die zwei Dinge, die den Aufrufer interessieren — „welches Modell rufe ich auf" und „streaming oder Endergebnis" — vom konkreten Provider entkoppelt sind. stream.ts zieht diese zwei Dimensionen auf vier Funktionen zusammen; AgentSession von pi-coding-agent, CLI-Werkzeuge und Erweiterungen nutzen alle denselben Eingang und müssen keine konkreten Provider-Module direkt importieren. So können Provider lazy geladen, ausgetauscht oder durch Erweiterungen mit neuem api registriert werden — der Aufrufercode bleibt unverändert.

complete implementiert keine eigene nicht-streaming-Logik, sondern wiederverwendet stream und ruft s.result() auf — die Streaming-Infrastruktur und die Einmal-Rückgabe teilen sich eine Pipeline; Provider brauchen nur die Streaming-Variante.

Wichtige Dateien

Die gesamte Implementierung von stream ist resolve + weiterreichen, ohne Geschäftslogik:

typescript
// packages/ai/src/stream.ts:25-32
export function stream<TApi extends Api>(
	model: Model<TApi>,
	context: Context,
	options?: ProviderStreamOptions,
): AssistantMessageEventStream {
	const provider = resolveApiProvider(model.api);
	return provider.stream(model, context, options as StreamOptions);
}

complete wiederverwendet stream und holt dann result(), ohne die Verteillogik zu duplizieren:

typescript
// packages/ai/src/stream.ts:34-41
export async function complete<TApi extends Api>(
	model: Model<TApi>,
	context: Context,
	options?: ProviderStreamOptions,
): Promise<AssistantMessage> {
	const s = stream(model, context, options);
	return s.result();
}

Datenfluss

Die Verteilungskette von stream(model, ctx, opts):

Grenzen und Fehler

  • Fehlender Provider: resolveApiProvider wirft direkt, statt einen leeren Stream zu liefern; siehe packages/ai/src/stream.ts:19-21. Wenn ein per Erweiterung dynamisch registrierter Provider nicht registriert ist, erfährt der Aufrufer es sofort.
  • Lazy geladene Provider: das konkrete Provider-Modul ist mit createLazyStream umwickelt; stream gibt synchron einen outer stream zurück; sobald das Modul geladen ist, wird per forwardStream an den inner weitergeleitet, siehe packages/ai/src/providers/register-builtins.ts:159-178. stream selbst weiß nichts vom Lazy Loading.
  • Type Assertion: options as StreamOptions castet ProviderStreamOptions (mit erweiterungsspezifischen Feldern) auf den vom Provider erwarteten Typ; der Provider liest Felder nach Bedarf.
  • options optional: alle drei Funktionen erlauben options? zu fehlen; der Provider fängt mit Default-Werten ab.

Zusammenfassung

stream.ts ist eine 60-Zeilen-Fassade, die (model, context, options) an den Provider in der Registrierung routet. stream / complete sind die Voll-Optionen-Variante, streamSimple / completeSimple die UI-freundliche Teilmenge. Die Struktur der Registrierung selbst steht in Provider-Registrierung, die Registrierung der 9 eingebauten Provider in Provider-Abstraktion und Built-ins, und der zurückgegebene AssistantMessageEventStream in EventStream Async-Iterator.