Skip to content

stream/complete: fachada de entrada a invocaciones LLM

源码版本v0.73.1

stream.ts es la capa más fina de @mariozechner/pi-ai hacia el exterior: cuatro funciones stream / complete / streamSimple / completeSimple, más un resolveApiProvider interno. No parsea SSE, no construye el cuerpo de la petición, no gestiona reintentos; sólo enruta la terna (model, context, options) al provider correspondiente según model.api. Se puede entender como una "centralita de despacho" entre quien llama y la implementación del provider: el llamador sólo ve el tipo de retorno unificado AssistantMessageEventStream y no sabe si debajo hay SSE de Anthropic, Responses de OpenAI o Converse de Bedrock.

Responsabilidades

Esta capa hace cuatro cosas:

  1. Despachar: stream busca en el registro el provider según model.api y le entrega (model, context, options). Ver packages/ai/src/stream.ts:25-32.
  2. Retorno síncrono del stream: aunque el provider tarde en inicializarse (lazy loading), stream devuelve síncronamente un AssistantMessageEventStream; los eventos se emiten cuando lleguen. Ver packages/ai/src/stream.ts:30-31.
  3. Entrada simplificada: streamSimple / completeSimple aceptan SimpleStreamOptions (sólo exponen reasoning, thinkingBudgets y otros campos típicos de UI), y el provider traduce las opciones simples a opciones completas. Ver packages/ai/src/stream.ts:43-50.
  4. Error si falta provider: resolveApiProvider lanza No API provider registered for api: <api> cuando no hay registro, sin fallar silenciosamente en el punto de invocación. Ver packages/ai/src/stream.ts:17-23.

Motivación de diseño

¿Por qué tener una capa tan fina? Porque las dos cosas que le importan al llamador —"qué modelo usar" y "streaming o resultado final"— están desacopladas del provider concreto. stream.ts condensa esas dos dimensiones en cuatro funciones; el AgentSession de pi-coding-agent, las herramientas del CLI y las extensiones usan la misma entrada, sin necesidad de importar directamente el módulo de un provider concreto. Así la implementación del provider puede ser lazy-loaded, reemplazable, o registrar nuevas api desde extensiones, sin tocar el código del llamador.

complete no duplica la lógica no-streaming, sino que reutiliza stream y luego s.result(): la infraestructura de streaming y la de resultado único comparten una sola tubería; cada provider sólo implementa la versión streaming.

Archivos clave

La implementación entera de stream es resolve + delegar, sin lógica de negocio:

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 reutiliza stream y luego toma result(), sin duplicar la lógica de despacho:

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

Flujo de datos

Cadena de despacho de stream(model, ctx, opts):

Límites y fallos

  • Provider ausente: resolveApiProvider lanza directamente, no devuelve un stream vacío. Ver packages/ai/src/stream.ts:19-21. Si una extensión registra un provider dinámicamente y aún no está registrado, el llamador se entera al instante.
  • Provider lazy-loaded: los módulos provider concretos se envuelven con createLazyStream; stream devuelve síncronamente el outer stream, y cuando el módulo termina de cargar hace forwardStream al inner. Ver packages/ai/src/providers/register-builtins.ts:159-178. stream no se entera del lazy loading.
  • Type assertion: options as StreamOptions hace un cast fuerte de ProviderStreamOptions (con campos personalizados de extensión) al tipo que espera el provider; el provider lee los campos que necesita.
  • options opcional: las tres funciones permiten omitir options?; el provider usa valores por defecto.

Resumen

stream.ts es una fachada de despacho de 60 líneas que enruta (model, context, options) al provider del registro. stream / complete son las versiones con todas las opciones, streamSimple / completeSimple son las versiones friendly para UI. La estructura del registro se ve en registro de provider; el registro de los 9 providers integrados en abstracción de provider y built-ins; y el AssistantMessageEventStream final en EventStream async iterator.