Un modelo con una caja de herramientas
Con una herramienta todo es fácil. Un asistente real tiene varias: una consulta de precios, un conversor de unidades, un sitio donde guardar notas. El modelo elige una para cada petición, y tu programa debe enviar cada petición a la función correcta.
También debe saber reaccionar cuando el modelo pide una herramienta que no existe.
Al final de esta lección
Nota
Puedo ofrecer más de una herramienta y enviar cada petición a la función correcta.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Enrutador | código que envía una petición a la función correcta |
| Tabla de herramientas | una tabla que une cada nombre de herramienta con su función |
| Herramienta desconocida | un nombre que no está en tu tabla de herramientas |
| Argumento obligatorio | un valor sin el cual la herramienta no puede ejecutarse |
| Validación | comprobar una petición antes de actuar |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una tabla de herramientas une nombres y funciones
Guarda tus herramientas en una sola tabla. La clave es el nombre que usa el modelo. El valor es tu función.
Para enrutar una petición, busca el nombre en la tabla y llama a lo que encuentres. Añadir una herramienta es entonces una línea, y la tabla es la única lista de lo que el modelo puede usar.
Ejemplo resuelto: una caja de tres herramientas
| Nombre | Qué hace | Argumento obligatorio |
|---|---|---|
| get_price | Busca el precio de un artículo | item |
| km_to_miles | Convierte kilómetros en millas | km |
| add_note | Guarda una nota corta | text |
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.
Rechaza una petición que no puedes atender
Un modelo puede pedir un nombre que no está en tu tabla. Puede enviar argumentos ilegibles. Puede olvidar un argumento obligatorio. Comprueba los tres casos antes de llamar a nada.
Un rechazo es un mensaje de vuelta al modelo, como error: herramienta desconocida delete_all. Nunca es un fallo del programa, y no se ejecuta nada.
Enruta cuatro peticiones, dos de ellas malas
docker exec -w /home/node/project agent node -e "const F={get_price:a=>({manzana:40,pera:45})[a.item],km_to_miles:a=>a.km*0.62,add_note:a=>'guardada'};const R={get_price:['item'],km_to_miles:['km'],add_note:['text']};const route=c=>{if(Object.hasOwn(F,c.name)===false)return 'error: herramienta desconocida '+c.name;let a;try{a=JSON.parse(c.arguments)}catch(e){return 'error: argumentos no válidos'}for(const k of R[c.name])if((k in a)===false)return 'error: falta '+k;return String(F[c.name](a))};for(const c of [{name:'get_price',arguments:JSON.stringify({item:'pera'})},{name:'delete_all',arguments:'{}'},{name:'get_price',arguments:'{item:'},{name:'km_to_miles',arguments:'{}'}])console.log(c.name+' -> '+route(c))"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ofrece tres herramientas a un modelo
docker exec -w /home/node/project agent node -e "const{ask}=require('./ai.cjs');const t=(n,d,k)=>({type:'function',function:{name:n,description:d,parameters:{type:'object',properties:{[k]:{type:k=='km'?'number':'string'}}}}});const T=[t('get_price','Precio de un artículo.','item'),t('km_to_miles','Convierte km en millas.','km'),t('add_note','Guarda una nota.','text')];const F={get_price:a=>({manzana:40,pera:45})[a.item],km_to_miles:a=>Math.round(a.km*621.371)/1000,add_note:a=>'guardada: '+a.text};(async()=>{let c;try{c=(await ask([{role:'user',content:'¿Cuántas millas son 10 km?'}],T,'required')).tool_calls[0]}catch(e){console.log('Sin modelo ('+e.message+'). Muestra grabada:');c={function:{name:'km_to_miles',arguments:JSON.stringify({km:10})}}}const f=c.function;console.log('Petición: '+f.name+' '+f.arguments);console.log('Resultado: '+(Object.hasOwn(F,f.name)?F[f.name](JSON.parse(f.arguments)):'herramienta desconocida'))})()"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 modelo solo pide las herramientas que ofreciste. Puede pedir cualquier nombre, y enviar argumentos que no encajan. Comprueba siempre el nombre en tu tabla y los argumentos según lo que necesita la herramienta.
Para ir más lejos: añade una cuarta herramienta
En la ejecución del enrutador, añade una herramienta llamada shout que devuelva su argumento text en mayúsculas. Añádela a F y a R, y envía una petición para ella.
Escribe en cuántos sitios tuviste que cambiar el código.