El bucle detrás de todo modelo con herramientas
Una llamada a una herramienta es solo la mitad de una conversación. El modelo pide una llamada, tu programa la ejecuta, y el modelo debe ver el resultado antes de poder responder. Por eso el intercambio es un bucle.
Todo asistente que usa herramientas ejecuta este bucle, y siempre debe tener una forma de pararse.
Al final de esta lección
Nota
Puedo ejecutar el bucle de preguntar, llamar, responder y volver a preguntar.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Bucle | pasos que el programa repite hasta una condición de parada |
| Historial | la lista de mensajes que se envía en cada pregunta |
| Resultado de herramienta | el mensaje que lleva al modelo la respuesta de una llamada |
| Paso | una pregunta y su respuesta |
| Límite de pasos | el máximo de pasos que puede dar un bucle |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
El bucle vuelve a preguntar tras cada llamada
Envía los mensajes y las herramientas al modelo. Si la respuesta trae tool_calls, ejecuta cada llamada, añade los resultados a los mensajes y pregunta otra vez. Si la respuesta son palabras, es la respuesta final y el bucle termina.
El modelo no guarda nada entre una pregunta y otra. Cada pregunta lleva todo el historial.
Ejemplo resuelto: el historial tras cada paso
| Paso | Qué ocurre | Mensajes que se envían después |
|---|---|---|
| Inicio | El usuario pregunta por una pera | user |
| 1 | El modelo pide get_price | user, assistant |
| 1 | Tu programa añade el resultado | user, assistant, tool |
| 2 | El modelo responde con palabras | user, assistant, tool, assistant |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Guarda un ayudante que llama al modelo
docker exec -w /home/node/project agent node -e "const ask=async(m,t,c)=>{const r=await fetch((process.env.AI_URL||'http://ai:4000')+'/v1/chat/completions',{method:'POST',headers:{'content-type':'application/json',authorization:'Bearer lab'},body:JSON.stringify({model:'lab',messages:m,tools:t,tool_choice:c,max_tokens:120})});const j=await r.json();if(r.ok)return j.choices[0].message;throw Error(r.status+' '+j.error.message)};require('fs').writeFileSync('ai.cjs','exports.ask='+ask+';');console.log('Guardado ai.cjs: ask(messages, tools) devuelve el mensaje del modelo.')"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un resultado de herramienta lleva el id
Después de una llamada añades un mensaje con el rol tool. Lleva el tool_call_id de la petición a la que responde y el resultado como texto.
El modelo usa el id para unir cada resultado con su petición. Sin él, una respuesta con dos llamadas no podría recibir sus dos resultados.
Ejecuta el bucle, como mucho tres pasos
docker exec -w /home/node/project agent node -e "const{ask}=require('./ai.cjs');const P={manzana:40,pera:45};const T=[{type:'function',function:{name:'get_price',description:'Precio de un artículo.',parameters:{type:'object',properties:{item:{type:'string'}}}}}];let rec=0;const fake=m=>m.at(-1).role=='tool'?{role:'assistant',content:'Cuesta 45 céntimos.'}:{role:'assistant',tool_calls:[{id:'c1',function:{name:'get_price',arguments:JSON.stringify({item:'pera'})}}]};(async()=>{const m=[{role:'user',content:'¿Cuánto cuesta una pera?'}];for(let s=1;s<4;s++){let a;try{if(rec)throw 0;a=await ask(m,T)}catch(e){rec||console.log('Sin modelo ('+e.message+'). Muestra grabada:');rec=1;a=fake(m)}m.push(a);if(a.tool_calls==null){console.log('Respuesta: '+a.content);return}for(const c of a.tool_calls){const v=P[JSON.parse(c.function.arguments).item];console.log('Paso '+s+': '+c.function.name+' -> '+v);m.push({role:'tool',tool_call_id:c.id,content:String(v)})}}console.log('Límite alcanzado')})()"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un límite de pasos frena el bucle
Un modelo puede pedir una llamada una y otra vez. Cada paso cuesta tiempo y crédito, así que cuentas los pasos y paras en un límite. Tres es un buen comienzo.
Cuando el límite llega primero, tu programa lo dice. No finge tener una respuesta.
Un error frecuente
Advertencia
Mucha gente cree que el modelo recuerda las preguntas anteriores. No es así. Solo ve los mensajes que tú envías. Si te dejas el resultado de la herramienta, el modelo no puede saber qué devolvió la llamada: conserva todos los mensajes.
Para ir más lejos: cambia la pregunta
Cambia la pregunta de la ejecución a ¿Cuánto cuestan una manzana y una pera?. Ejecútala tres veces y cuenta los pasos cada vez.
Después cambia s<4 por s<2. Escribe qué imprime el programa cuando el límite llega primero.