Un pequeño asistente que usa tus funciones
Ya tienes todas las piezas de un asistente que usa herramientas. Necesita herramientas que el modelo sepa nombrar y un bucle que las ejecute. Necesita un límite de pasos, errores devueltos como resultados y una persona que apruebe las llamadas arriesgadas.
Esta lección une esas piezas en un solo programa pequeño.
Al final de esta lección
Nota
Puedo construir un pequeño asistente que usa mis propias funciones.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Asistente | un programa que usa un modelo y herramientas para un trabajo |
| Bucle | pasos repetidos hasta una respuesta o un límite |
| Enrutador | código que envía cada petición a su función |
| Aprobación | el sí de una persona antes de una herramienta de escritura |
| Salvaguarda | una comprobación que impide que una llamada se ejecute |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un asistente son cuatro piezas que trabajan juntas
Cada pieza ya la has construido. Las herramientas son tus funciones y sus descripciones. El bucle pregunta, ejecuta y vuelve a preguntar. El enrutador envía cada petición a su función.
Las salvaguardas son el límite de pasos, los resultados de error y la aprobación de las escrituras. Quita una sola y un fallo se convierte en un problema.
Ejemplo resuelto: de qué protege cada pieza
| Pieza | Qué hace | Qué falla sin ella |
|---|---|---|
| Límite de pasos | Se detiene tras tres pasos | Un bucle que no termina |
| Tabla del enrutador | Rechaza una herramienta desconocida | Una petición ejecuta otra cosa |
| Resultados de error | Devuelve un fallo | Una caída en lugar de una recuperación |
| Aprobación | Pregunta antes de una escritura | Un error cambia tus datos |
| Registro | Anota cada llamada | Nadie sabe qué ocurrió |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Guarda las ayudas del modelo y la aprobación
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)};const tool=(n,d,k)=>({type:'function',function:{name:n,description:d,parameters:{type:'object',properties:{[k]:{type:'string'}}}}});const mk=()=>{const rl=require('readline').createInterface({input:process.stdin});const it=rl[Symbol.asyncIterator]();return{yes:async q=>{process.stdout.write(q+' (s/n) ');const l=await it.next();const ok=l.value=='s';console.log(ok?'s':'n');return ok},close:()=>rl.close()}};require('fs').writeFileSync('agent.cjs','exports.ask='+ask+';exports.tool='+tool+';exports.mk='+mk+';');console.log('Guardado agent.cjs')"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Guarda las herramientas de la tienda
docker exec -w /home/node/project agent node -e "const make=()=>{const{tool}=require('./agent.cjs');const N=[];const k=(i,n,o)=>({role:'assistant',tool_calls:[{id:i,function:{name:n,arguments:JSON.stringify(o)}}]});return{N,T:[tool('get_price','Precio de un artículo.','item'),tool('add_note','Guarda una nota.','text')],F:{get_price:a=>({manzana:40,pera:45})[a.item]||'Error: no encontrado',add_note:a=>N.push(a.text)},FK:[k('c1','get_price',{item:'pera'}),k('c2','add_note',{text:'pera 45 c'}),{role:'assistant',content:'Guardada.'}]}};require('fs').writeFileSync('tools.cjs','exports.make='+make+';');console.log('Guardado tools.cjs')"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Comprueba una llamada en un orden fijo
Para cada petición, haz las mismas preguntas en el mismo orden. ¿Está el nombre en la tabla? ¿Necesita aprobación? ¿Dijo la persona s? Ejecútala y convierte cualquier error en un resultado.
Una llamada que falla una pregunta nunca llega a la siguiente. Cada resultado, ejecutado o no, se convierte en un mensaje de herramienta y una línea del registro.
Ejecuta todo el asistente
printf 's\n' | docker exec -i -w /home/node/project agent node -e "const{ask,mk}=require('./agent.cjs');const{T,F,N,FK}=require('./tools.cjs').make();const{yes,close}=mk();let rec=0;(async()=>{const m=[{role:'user',content:'Busca el precio de una pera y guárdalo en una nota.'}];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=FK[s-1]}m.push(a);if(a.tool_calls==null){console.log('Respuesta: '+a.content);break}for(const c of a.tool_calls){const n=c.function.name;let r='Error: herramienta desconocida';if(Object.hasOwn(F,n))r=n=='add_note'&&(await yes('¿Permitir '+n+' '+c.function.arguments+'?'))===false?'Rechazada por la persona':String(F[n](JSON.parse(c.function.arguments)));console.log('Paso '+s+': '+n+' -> '+r);m.push({role:'tool',tool_call_id:c.id,content:r})}}console.log('Notas: '+N.join(', '));close()})()"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 un asistente es seguro en cuanto el modelo es bueno. Un buen modelo sigue equivocándose. La seguridad está en tu código: la tabla de herramientas, el límite de pasos, la aprobación y el registro. Constrúyelos primero, y confía en el modelo después.
Para ir más lejos: cambia el trabajo
Cambia la pregunta de la ejecución del asistente a Guarda una nota: comprar pan. Ejecútala dos veces, una con s y otra con n dadas de antemano.
Compara las dos líneas de notas. Escribe qué pieza del programa marcó la diferencia.