Una llamada lenta no debe bloquear nada
Una llamada a un modelo puede tardar varios segundos. Tu programa no debe quedarse bloqueado mientras espera. El código asíncrono le permite esperar sin bloquearse.
El flujo va un paso más allá. La respuesta llega por trozos, y quien la usa puede empezar a leer enseguida.
Al final de esta lección
Nota
Puedo decir por qué se espera una llamada lenta y cómo llega una respuesta en flujo.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Promesa | un sustituto de un resultado que aún no ha llegado |
| Await | pausar una función hasta que una promesa tenga su resultado |
| Función async | una función que puede usar await |
| Flujo | una respuesta enviada por trozos a medida que se produce |
| Fragmento | un trozo de una respuesta en flujo |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una llamada lenta devuelve una promesa
Cuando lanzas un trabajo lento, la llamada vuelve enseguida con una promesa. El resultado aún no está. Tu programa sigue con la línea siguiente.
Cuando llega el resultado, la promesa lo entrega. El orden importa: las líneas posteriores a la llamada pueden ejecutarse antes que el resultado.
Mira el orden en que pasan las cosas
docker exec -w /home/node/project agent node -e "const wait=(ms,t)=>new Promise(r=>setTimeout(()=>r(t),ms));console.log('1 inicio');wait(300,'3 la llamada lenta ha terminado').then(console.log);console.log('2 el programa sigue')"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Await pausa una función, no el programa
Dentro de una función async, await pausa esa función hasta que la promesa tiene su resultado. El resto del programa no se bloquea.
Dos llamadas lentas que no dependen una de otra pueden salir juntas con Promise.all. Tardan más o menos lo que la más lenta, no la suma de las dos.
Await una a una, luego a la vez
docker exec -w /home/node/project agent node -e "const wait=(ms,t)=>new Promise(r=>setTimeout(()=>r(t),ms));const tick=(t0)=>Math.round((Date.now()-t0)/100)*100;(async()=>{let t0=Date.now();await wait(200,'a');await wait(200,'b');console.log('una tras otra: unos '+tick(t0)+' ms');t0=Date.now();await Promise.all([wait(200,'a'),wait(200,'b')]);console.log('a la vez: unos '+tick(t0)+' ms')})()"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Una respuesta en flujo llega en fragmentos
Si pides stream: true, el servicio envía la respuesta a medida que la produce. La mayoría de las API de modelos usan eventos enviados por el servidor: líneas que empiezan por data:, cada una con un trocito de texto.
La última línea es data: [DONE]. Une los trozos en orden para obtener la respuesta entera.
Ejemplo resuelto: un flujo, línea a línea
| Línea recibida | Qué da |
|---|---|
| data: {"choices":[{"delta":{"content":"Ho"}}]} | Ho |
| data: {"choices":[{"delta":{"content":"la"}}]} | la |
| data: [DONE] | El final, sin texto |
| Unido | Hola |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Lee una respuesta en flujo
docker exec -w /home/node/project agent node -e "const u=(process.env.AI_URL||'http://ai:4000')+'/v1/chat/completions';(async()=>{try{const r=await fetch(u,{method:'POST',headers:{'content-type':'application/json',authorization:'Bearer lab'},body:JSON.stringify({model:'lab',messages:[{role:'user',content:'Cuenta de uno a cinco con palabras.'}],max_tokens:40,stream:true})});if(r.ok===false)throw new Error(r.status);const d=new TextDecoder();let b='',n=0,t='';for await(const p of r.body){b+=d.decode(p,{stream:true});const L=b.split(String.fromCharCode(10));b=L.pop();for(const l of L){const x=l.slice(6);if(l.startsWith('data: ')&&(x==='[DONE]')===false){const c=JSON.parse(x).choices?.[0]?.delta?.content;if(c){n++;t+=c}}}}console.log('Fragmentos con texto: '+n);console.log('Texto unido: '+t)}catch(e){console.log('Sin flujo ('+e.message+'). Muestra grabada:');console.log('Fragmentos con texto: 5');console.log('Texto unido: uno, dos, tres, cuatro, cinco')}})()"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un error frecuente
Advertencia
Mucha gente cree que el flujo hace el modelo más rápido. La respuesta entera tarda más o menos lo mismo. Ves las primeras palabras antes, y por eso parece más rápido. Un flujo también puede cortarse a medias: comprueba que llegó la marca de final.