Módulo 2: Latencia y costo como arquitectura
6. Async y streaming: cuando el usuario no puede esperar
Descripción
Al terminar esta lección vas a saber atacar el problema que ni el cascade ni la caché resuelven: la latencia percibida en las operaciones que sí llegan al LLM y tardan segundos. Hay dos técnicas, y las dos parten de una distinción clave: no es lo mismo el tiempo real que tarda una operación que el tiempo que el usuario percibe esperando. El streaming aprovecha eso mostrando la respuesta token a token —el usuario ve texto casi de inmediato en vez de mirar una pantalla en blanco hasta que el bloque completo esté listo—, así que la latencia percibida se desploma aunque el trabajo real tarde lo mismo. El async va más lejos: cuando una operación tarda demasiado para hacer esperar a nadie, la saca del camino crítico por completo —encola el trabajo, responde "en proceso" al instante, y entrega el resultado cuando esté listo—. Vas a medir las dos: cómo el streaming baja la latencia percibida de 1500 ms a 303 ms, y cómo el async la baja de 2100 ms a 15 ms moviendo el trabajo a otro lado.
Esto importa porque el error de latencia más común no es que el LLM sea lento —eso es un hecho de la lección 2— sino ponerlo síncrono en el camino crítico cuando tarda segundos. Síncrono significa que el usuario se queda esperando, bloqueado, hasta que el LLM termina. Con una operación de milisegundos, eso está bien; con una de dos segundos, el usuario mira una pantalla congelada y se pregunta si algo se rompió. El streaming y el async son las dos respuestas de arquitectura a esa espera: una la hace tolerable mostrando progreso, la otra la elimina sacando el trabajo de la vista. Saber cuál usar —y cuándo el síncrono simplemente no es aceptable— es lo que separa una feature de IA que se siente ágil de una que se siente rota, incluso cuando el modelo tarda exactamente lo mismo en las dos.
Conexión con el módulo: esta lección completa el juego de técnicas. La lección 3 fijó los dos presupuestos; la 4 (cascade) y la 5 (caché) atacaron el de costo; esta ataca el de latencia —y específicamente la latencia de cola que la lección 4 dejó sin resolver (el cascade baja la latencia media, pero las queries difíciles siguen tardando lo mismo en el strong model)—. El streaming es la respuesta a esa cola: no acelera la generación, pero hace que el usuario perciba una respuesta rápida. El async es la respuesta a las operaciones que no deberían estar en el camino crítico en absoluto. La lección 7 va a componer costo (cascade + caché) y latencia (streaming) en un solo camino de petición. Y la idea de "sacar el trabajo del camino crítico" conecta con el patrón de eventos y colas de las guías de arquitectura del ecosistema —aquí lo aplicamos al componente de IA—.
La cocina que te avisa cuando el plato está listo
Piénsalo así. Entras a un restaurante y pides un plato que tarda. Hay tres formas de que la cocina maneje tu espera, y cada una se siente radicalmente distinta aunque el plato tarde lo mismo en cocinarse.
La primera, la peor: el mesero toma tu orden, se va a la cocina, y no vuelve hasta que el plato entero está listo. Te quedas en la mesa mirando la pared, sin pan, sin agua, sin señal de que algo pasa, quince minutos. El plato tardó quince minutos —un hecho—, pero tu experiencia fue quince minutos de incertidumbre. Eso es el síncrono bloqueante: el usuario espera el resultado completo sin ninguna señal, mirando una pantalla en blanco.
La segunda, mucho mejor: el mesero te trae el pan de inmediato, luego la entrada, luego el plato principal por partes conforme salen. El plato principal sigue tardando quince minutos en total, pero tú estás comiendo desde el primer minuto —la espera se llenó de progreso visible, y ni notaste los quince minutos—. Eso es el streaming: la respuesta llega por partes, el usuario ve algo casi de inmediato, y aunque el trabajo total tarde lo mismo, la espera percibida se desploma.
La tercera, para platos que tardan de verdad: la cocina te da un buscapersonas —"cuando esté listo, vibra"— y tú te vas a tu mesa, platicas, revisas el teléfono, vives tu vida. No estás esperando en la barra; el plato se cocina fuera de tu camino, y te avisan cuando está. Eso es el async: la operación se saca del camino crítico por completo, el usuario recibe un acuse instantáneo ("en proceso") y sigue con lo suyo hasta que el resultado llega.
El plato tarda lo mismo en los tres casos —la cocina no cocina más rápido—. Lo que cambia es cómo se maneja la espera, y eso lo decide todo. El streaming y el async son el pan-por-partes y el buscapersonas de una feature de IA: no hacen al LLM más rápido, pero hacen que la espera sea tolerable o invisible. Esta lección es aprender cuál de los tres usa cada operación —y por qué el primero, el mesero que desaparece, es un error de diseño cuando el plato tarda segundos—.
Ejemplo trabajado: streaming y async, medidos
Vamos a medir las dos técnicas sobre dos operaciones reales de Mercado. El presupuesto de latencia es 800 ms (el de la búsqueda). No hay LLM real: la latencia total se calcula con el stub —recuerda de la lección 2 que la latencia crece con los tokens de salida—.
Parte A, streaming: una respuesta del agente de soporte de 400 tokens con el strong model. Bloqueante, el usuario no ve nada hasta el último token. Con streaming, ve el primer token casi de inmediato —el time-to-first-token (TTFT)— y lee mientras el resto se genera.
Parte B, async: el generador "describe tu producto" para vendedores, 600 tokens con el strong model. Síncrono, el vendedor espera todo. Async, se encola el job, se responde "en proceso" al instante, y el texto llega después.
# Leccion 06 — async y streaming donde el usuario no puede esperar el bloque completo
# STUB del LLM: todo SIMULADO. Cero red, cero API, cero claves.
MODELS = {
"cheap": dict(usd_in=0.0008, usd_out=0.004, base_ms=90, ms_per_tok=0.4),
"strong": dict(usd_in=0.008, usd_out=0.040, base_ms=300, ms_per_tok=3.0),
}
def total_latency(model, out_tokens):
m = MODELS[model]
return m["base_ms"] + out_tokens * m["ms_per_tok"]
LATENCY_BUDGET_MS = 800
# --- Parte A: streaming — time-to-first-token (TTFT) vs tiempo total ---
# La respuesta del agente de soporte: 400 tokens de salida con el modelo strong.
# Bloqueante: el usuario no ve NADA hasta el ultimo token.
# Streaming: el usuario ve el primer token casi de inmediato y lee mientras se genera.
m = MODELS["strong"]
OUT = 400
blocking_ms = total_latency("strong", OUT) # espera el bloque completo
ttft_ms = m["base_ms"] + 1 * m["ms_per_tok"] # primer token: base + 1 token
print("== Parte A: streaming (respuesta del agente de soporte, 400 tokens) ==")
print(f"{'mode':<12}{'perceived_ms':>14}{'verdict_vs_budget':>20}")
print(f"{'blocking':<12}{blocking_ms:>14.1f}{('VIOLA' if blocking_ms>LATENCY_BUDGET_MS else 'OK'):>20}")
print(f"{'streaming':<12}{ttft_ms:>14.1f}{('VIOLA' if ttft_ms>LATENCY_BUDGET_MS else 'OK'):>20}")
print(f" percepcion: el usuario ve texto a los {ttft_ms:.0f} ms en vez de esperar {blocking_ms:.0f} ms "
f"(-{(1-ttft_ms/blocking_ms)*100:.0f}%)")
# --- Parte B: async — sacar el trabajo pesado del camino de la peticion ---
# El generador "describe tu producto" para vendedores: 600 tokens con el modelo strong.
GEN_OUT = 600
sync_ms = total_latency("strong", GEN_OUT) # el vendedor espera TODO
ENQUEUE_MS = 15 # encolar el job y responder "en proceso"
async_ms = ENQUEUE_MS # percibido por el vendedor
print("\n== Parte B: async ('describe tu producto', 600 tokens) ==")
print(f"{'mode':<12}{'perceived_ms':>14}{'real_work_ms':>14}{'verdict_vs_budget':>20}")
print(f"{'sync':<12}{sync_ms:>14.1f}{sync_ms:>14.1f}{('VIOLA' if sync_ms>LATENCY_BUDGET_MS else 'OK'):>20}")
print(f"{'async':<12}{async_ms:>14.1f}{sync_ms:>14.1f}{('VIOLA' if async_ms>LATENCY_BUDGET_MS else 'OK'):>20}")
print(f" el trabajo real ({sync_ms:.0f} ms) no desaparece: se mueve fuera del camino critico;")
print(f" el vendedor recibe 'en proceso' en {async_ms} ms y el texto llega cuando esta listo.")
Qué esperar. Al correrlo:
== Parte A: streaming (respuesta del agente de soporte, 400 tokens) ==
mode perceived_ms verdict_vs_budget
blocking 1500.0 VIOLA
streaming 303.0 OK
percepcion: el usuario ve texto a los 303 ms en vez de esperar 1500 ms (-80%)
== Parte B: async ('describe tu producto', 600 tokens) ==
mode perceived_ms real_work_ms verdict_vs_budget
sync 2100.0 2100.0 VIOLA
async 15.0 2100.0 OK
el trabajo real (2100 ms) no desaparece: se mueve fuera del camino critico;
el vendedor recibe 'en proceso' en 15 ms y el texto llega cuando esta listo.
Lee las dos partes, porque cada una muestra una idea distinta de qué se puede hacer con la espera.
El streaming baja la latencia percibida sin acelerar nada. La respuesta del agente de soporte tarda 1500 ms en generarse completa (300 base + 400 tokens × 3 ms) —eso viola el presupuesto de 800 ms, y el usuario bloqueante mira una pantalla congelada segundo y medio—. Con streaming, el primer token aparece a los 303 ms (300 base + 1 token), y a partir de ahí el usuario lee mientras el resto se genera. La latencia percibida —lo que el usuario espera antes de ver algo— cae 80%, de 1500 a 303 ms, y ahora cabe en el presupuesto. Y aquí está lo crucial: el trabajo real no cambió. El modelo sigue tardando 1500 ms en producir los 400 tokens; el streaming no lo acelera un microsegundo. Lo único que cambió es cuándo el usuario ve el primer resultado —y eso es lo que decide si la feature se siente rápida o rota—. El streaming es el pan que llega de inmediato mientras el plato se cocina.
El async saca el trabajo de la vista por completo. El generador "describe tu producto" tarda 2100 ms (300 base + 600 tokens × 3 ms). Síncrono, el vendedor espera esos 2100 ms bloqueado —viola el presupuesto por más del doble—. Async, el sistema encola el trabajo y le responde "en proceso" al vendedor en 15 ms (lo que tarda encolar), y el texto llega cuando está listo. La latencia percibida por el vendedor cae de 2100 a 15 ms —cabe de sobra en el presupuesto—. Y otra vez, lo crucial: el trabajo real de 2100 ms no desaparece; se mueve. El modelo sigue generando los 600 tokens en 2100 ms, pero lo hace fuera del camino crítico, en segundo plano, mientras el vendedor sigue con lo suyo. El async es el buscapersonas: la operación se cocina aparte y te avisan.
Cuándo cada uno. La diferencia entre las dos partes no es el modelo ni los tokens: es si el usuario necesita el resultado para continuar. En el agente de soporte, el usuario quiere leer la respuesta ahora —no puede seguir sin ella—, así que se usa streaming: el resultado llega, pero por partes, para que la espera sea tolerable. En "describe tu producto", el vendedor no necesita la descripción en ese instante —la pidió, puede seguir editando otras cosas y revisarla en un minuto—, así que se usa async: se saca del camino por completo. La regla: si el usuario espera el resultado para seguir, usa streaming (muéstralo por partes); si el usuario no tiene que esperarlo, usa async (sácalo del camino). Y el error que las dos corrigen es el mismo: el síncrono bloqueante en una operación de segundos —el mesero que desaparece—.
Profundización: por qué el streaming funciona, y qué gana el async además de latencia
Por qué el streaming baja tanto la percepción. La clave está en la lección 2: la latencia del LLM crece con los tokens de salida —cada token añade tiempo—. Eso significa que la respuesta se produce incrementalmente, token a token, no de golpe al final. El modelo ya tiene el primer token a los ~300 ms; lo que tarda 1500 ms es terminar los 400. El streaming simplemente entrega cada token cuando está listo en vez de esperar a tenerlos todos. Como los humanos leemos más lento de lo que el modelo genera, para cuando terminas de leer el primer párrafo, el siguiente ya llegó —la espera desaparece dentro de la lectura—. El streaming no es un truco visual: aprovecha la naturaleza incremental de la generación para convertir "esperar todo y luego leer" en "leer mientras se genera". Por eso casi toda interfaz de chat con LLM hace streaming: sin él, cada respuesta sería una pantalla en blanco de varios segundos.
El async gana más que latencia: robustez. Sacar el trabajo del camino crítico no solo mejora la latencia percibida; hace el sistema más robusto. Cuando la generación de "describe tu producto" corre en segundo plano, encolada, pasan cosas buenas: si el LLM está lento o caído en ese momento, el job espera en la cola y se procesa cuando el modelo revive —el vendedor no ve un error, solo que su descripción tarda un poco más—. Si síncrono, en cambio, un LLM caído significa que el vendedor recibe un error en la cara. La cola desacopla al usuario de la disponibilidad del LLM en ese instante exacto. (Los patrones de resiliencia a fondo —reintentos, circuit breakers, la mecánica de las colas— son de la guía de resiliencia y del módulo 5 de esta guía; aquí basta con ver que async compra latencia y desacople.) Esta es una razón extra para preferir async en operaciones pesadas y no urgentes: no solo se siente instantáneo, aguanta mejor los fallos.
El costo no cambia con streaming ni async. Un matiz honesto: ni el streaming ni el async ahorran dinero. El modelo genera los mismos tokens en los dos casos, así que el costo por llamada es idéntico —estas son técnicas de latencia, no de costo—. El ahorro de costo lo dan el cascade y la caché (lecciones 4 y 5). Es importante no confundir los ejes: si tu problema es la factura, streaming/async no lo tocan; si tu problema es que la feature se siente lenta o bloqueada, cascade/caché no lo tocan del todo (el cascade baja la latencia media pero no la percibida en la cola). Cada palanca a su problema —y por eso la lección 7 las compone, cada una atacando su restricción—.
Async no es gratis en complejidad. El buscapersonas tiene un costo: montar async significa una cola, un worker que procesa los jobs, un mecanismo para entregar el resultado (polling, notificación, websocket) y un manejo de estados ("en proceso", "listo", "falló"). Eso es más piezas que operar que una simple llamada síncrona. Por eso async se reserva para operaciones donde de verdad vale la pena —pesadas, no urgentes— y no se usa para todo. Una búsqueda que tarda 200 ms no necesita async: sería sobre-ingeniería. La regla: async para lo que tarda segundos y el usuario no debe esperar; síncrono (con streaming si genera texto) para lo que el usuario sí espera.
Errores comunes
Síncrono bloqueante en una operación de segundos (de latencia percibida). Qué pasa: se pone el LLM síncrono en el camino crítico para una operación que tarda uno o dos segundos, y el usuario mira una pantalla congelada sin ninguna señal. La feature funciona —la respuesta llega— pero se siente rota, y los usuarios abandonan creyendo que se colgó. Por qué pasa: síncrono es lo más fácil de programar (llamas, esperas, devuelves) y en desarrollo, con una query, el par de segundos se tolera. Cómo detectarlo: si tu feature de IA genera texto y el usuario ve una pantalla en blanco hasta que termina, no le pusiste streaming. Cómo corregirlo: si el usuario espera el resultado, haz streaming (muestra el primer token en ~300 ms); si no lo espera, hazlo async.
Streaming donde debió ser async (de camino crítico). Qué pasa: una operación pesada y no urgente —generar una descripción larga, un reporte— se hace con streaming, así que el usuario ve el texto aparecer, pero queda atado a la pantalla los dos segundos que tarda, cuando podría haber seguido con otra cosa. Por qué pasa: streaming se siente moderno y se aplica por default a todo lo que genera texto, sin preguntarse si el usuario de verdad necesita quedarse mirando. Cómo detectarlo: si el usuario no necesita el resultado ahora mismo pero igual lo tienes esperando (aunque sea viendo streaming), lo dejaste en el camino crítico sin necesidad. Cómo corregirlo: para operaciones que el usuario no consume al instante, usa async —sácalo del camino, dale un buscapersonas— en vez de encadenarlo a la pantalla con streaming.
Creer que streaming o async ahorran dinero (de eje equivocado). Qué pasa: alguien mete streaming esperando que baje el costo, y se sorprende de que la factura no cambie. Streaming y async son técnicas de latencia; el modelo genera los mismos tokens, así que el costo es idéntico. Por qué pasa: se mezclan los dos ejes del taxímetro (tiempo y dinero) y se asume que mejorar uno mejora el otro. Cómo detectarlo: si esperabas un ahorro de costo de streaming/async, confundiste la palanca. Cómo corregirlo: usa cada palanca para su eje —cascade y caché para el costo, streaming y async para la latencia— y compón las que ataquen tus dos problemas (lección 7).
Ejercicios
Ejercicio 1 — Elige la técnica. Para cada operación de Mercado, di si usarías síncrono-bloqueante, streaming o async, y por qué: (a) el agente de soporte respondiendo un mensaje del cliente en una conversación en vivo; (b) generar un resumen semanal de ventas para el vendedor, que se manda por correo; (c) la búsqueda semántica devolviendo productos ordenados (150 tokens, 750 ms).
Ver solución
- (a) Agente de soporte en vivo → streaming. El cliente está en una conversación y espera la respuesta para seguir; no puede continuar sin ella. Pero la respuesta puede tardar más de un segundo si es larga. Streaming es lo ideal: el cliente ve el texto aparecer casi de inmediato (TTFT ~300 ms) y lee mientras se completa, en vez de mirar una pantalla congelada. Es exactamente el caso de la Parte A del ejemplo.
- (b) Resumen semanal por correo → async. El vendedor no espera este resultado en tiempo real —lo recibe por correo cuando esté listo—. Es una operación pesada (generar un resumen es mucho texto) y no urgente. Async es lo correcto: se encola el trabajo, se genera en segundo plano (aunque tarde varios segundos, a nadie le importa), y se entrega por correo. Sería absurdo tener al vendedor esperando en una pantalla mientras se genera.
- (c) Búsqueda semántica de 750 ms → síncrono-bloqueante (o streaming si aplica). 750 ms cabe en el presupuesto de 800 ms, y el resultado (una lista de productos) probablemente se muestra de golpe, no como texto que se lee palabra por palabra. Síncrono está bien aquí —es rápido y el resultado es una lista, no una narración—. Si la búsqueda a veces genera respuestas más largas que se acercan al presupuesto, streaming ayudaría; pero para 750 ms con una salida estructurada, el síncrono simple es adecuado y no vale la pena complicarlo. La regla: no metas streaming ni async donde el síncrono ya cabe en el presupuesto —sería sobre-ingeniería—.
Ejercicio 2 — Calcula el TTFT. Con el stub (strong: base 300 ms, 3 ms/token), calcula el time-to-first-token de una respuesta y el tiempo total para tres longitudes de salida: 100, 400 y 1000 tokens. ¿Qué le pasa al TTFT conforme la respuesta se alarga? ¿Y al tiempo total? ¿Qué te dice eso sobre por qué el streaming importa más en respuestas largas?
Ver solución
TTFT = base + 1×ms_per_tok = 300 + 3 = 303 ms (¡constante!). Tiempo total = base + out_tokens×3:
| out_tokens | TTFT | total |
|---|---|---|
| 100 | 303 ms | 300 + 300 = 600 ms |
| 400 | 303 ms | 300 + 1200 = 1500 ms |
| 1000 | 303 ms | 300 + 3000 = 3300 ms |
Lo que le pasa a cada uno: el TTFT es constante en 303 ms sin importar la longitud —el primer token siempre está listo a los ~300 ms, porque solo depende de la latencia base más un token—. El tiempo total, en cambio, crece con la salida: 600 ms para 100 tokens, 3300 ms para 1000.
Qué dice eso sobre el streaming: la brecha entre lo que el usuario espera con streaming (303 ms, constante) y sin streaming (el total, creciente) se agranda con la longitud de la respuesta. Para 100 tokens, streaming ahorra 600 − 303 = 297 ms (útil pero modesto). Para 1000 tokens, ahorra 3300 − 303 = ~3000 ms (¡enorme!). Por eso el streaming importa muchísimo más en respuestas largas: el bloqueante hace esperar los 3.3 segundos completos, mientras que el streaming muestra texto siempre a los 303 ms. En una respuesta corta el síncrono se tolera; en una larga, sin streaming, es una pantalla en blanco de varios segundos. La longitud de la salida —otra vez la protagonista de la lección 2— decide cuánto vale el streaming.
Ejercicio 3 — El async que también robustece. El generador "describe tu producto" es async. Un lunes, el proveedor del LLM tiene una caída de 20 minutos. Describe qué experimenta un vendedor que pide una descripción durante la caída, y compáralo con lo que experimentaría si la operación fuera síncrona. ¿Qué propiedad, además de latencia, le dio el async a la feature?
Ver solución
Con async (lo real): el vendedor pide la descripción, recibe "en proceso" en 15 ms (como siempre), y sigue editando su producto. El job entra a la cola, pero como el LLM está caído, espera en la cola sin poder procesarse. Cuando el LLM revive 20 minutos después, el worker toma el job encolado y lo procesa; la descripción llega (con retraso, pero llega). El vendedor experimenta, a lo sumo, "mi descripción tardó un rato en aparecer" —una molestia menor, no un error—.
Con síncrono (el contrafactual): el vendedor pide la descripción, el sistema llama al LLM en el camino crítico… y el LLM está caído. El vendedor recibe un error en la cara: "no se pudo generar la descripción, intenta de nuevo". Y si reintenta durante la caída, otro error. Su experiencia es una feature rota durante 20 minutos.
La propiedad extra que el async le dio a la feature, además de bajar la latencia percibida, es robustez / desacople: la cola separa al vendedor de la disponibilidad del LLM en ese instante exacto. El trabajo se hace cuando el modelo puede, no cuando el usuario lo pide, así que una caída temporal del modelo se absorbe como un retraso en vez de propagarse como un error. Sacar el trabajo del camino crítico no solo mejora cómo se siente la espera; hace que el sistema no se caiga cuando el LLM se cae —que es exactamente el tipo de resiliencia que el módulo 5 de esta guía trata a fondo—. El async es, a la vez, una técnica de latencia y una primera línea de defensa contra los fallos del LLM.
Resumen y siguiente paso
En esta lección atacaste la latencia percibida, el problema que ni el cascade ni la caché resuelven. Con la cocina de tres esperas viste la distinción central: el plato tarda lo mismo, pero cómo se maneja la espera lo decide todo. El streaming —el pan que llega de inmediato— muestra la respuesta token a token: mediste que el TTFT del agente de soporte cae de 1500 ms bloqueante a 303 ms (-80%), aunque el trabajo real siga tardando 1500, porque la generación es incremental y el primer token ya está a los ~300 ms. El async —el buscapersonas— saca el trabajo del camino crítico: el "describe tu producto" cae de 2100 ms síncrono a 15 ms percibidos, moviendo el trabajo a segundo plano. Aprendiste cuándo cada uno (streaming si el usuario espera el resultado, async si no lo espera), que el error que ambos corrigen es el síncrono bloqueante en operaciones de segundos, que async además robustece (la cola desacopla al usuario de la disponibilidad del LLM), y que ninguno ahorra dinero —son palancas de latencia, no de costo—.
Antes de avanzar deberías poder: distinguir latencia real de latencia percibida; elegir entre síncrono, streaming y async según si el usuario espera el resultado; explicar por qué el streaming baja tanto la percepción (generación incremental, TTFT constante) y por qué importa más en respuestas largas; y saber que streaming/async atacan latencia, no costo, y que async trae desacople de regalo.
Lo que sigue es juntarlo todo. Tienes ya las cuatro piezas —presupuesto, cascade, caché, async/streaming— y en la lección 7 vas a componerlas en un solo camino de petición consciente del costo, aplicado al agente de soporte de Mercado. Vas a ver en qué orden se aplican (caché primero, luego cascade, con el presupuesto como compuerta), y vas a medir —ejecutando— que las técnicas se acumulan pero no suman (se solapan sobre el mismo tráfico barato), y que ninguna sola basta pero juntas meten la feature dentro de su presupuesto. Es la síntesis del módulo.
Recursos
- Anthropic — Streaming (docs de Claude) — cómo se recibe la respuesta del modelo token a token (Server-Sent Events); la base técnica de por qué el TTFT es bajo y constante mientras el total crece con la salida, sin fijar versión.
- Anthropic — Message Batches / procesamiento por lotes (docs de Claude) — el patrón de sacar el trabajo del camino crítico y procesarlo de forma asíncrona por lotes (más barato, sin latencia interactiva); el respaldo conceptual del async de esta lección.
- martinfowler.com — "Emerging Patterns in Building GenAI Applications", Bharani Subramaniam y Martin Fowler — el tratamiento de streaming y de sacar operaciones lentas del camino crítico como patrones de arquitectura para features con LLM.