Algunas llamadas deben esperar un sí
Consultar un precio no cambia nada. Una herramienta que guarda, envía o borra sí cambia algo. Si el modelo pide una por error, el daño es real.
Por eso tu programa, nunca el modelo, decide si una llamada arriesgada se ejecuta. Para una llamada arriesgada, primero pregunta a una persona.
Al final de esta lección
Nota
Puedo hacer que una herramienta arriesgada espere la aprobación de una persona.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Herramienta de lectura | una herramienta que solo mira datos |
| Herramienta de escritura | una herramienta que cambia algo |
| Aprobación | el sí de una persona antes de ejecutar una llamada |
| Denegar por defecto | rechazar salvo que haya un sí claro |
| Registro | la constancia de lo que se pidió y se ejecutó |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Clasifica cada herramienta por lo que puede cambiar
Da un riesgo a cada herramienta. Una de lectura solo mira, así que puede ejecutarse enseguida. Una de escritura cambia algo, así que necesita un sí.
Guarda el riesgo en tu propia tabla. El modelo nunca dice lo arriesgada que es una llamada, y un modelo equivocado no puede saltarse la tabla.
Ejemplo resuelto: tres herramientas y su riesgo
| Herramienta | Qué hace | Riesgo | Se ejecuta |
|---|---|---|---|
| get_price | Consulta un precio | lectura | Enseguida |
| add_note | Añade a una lista de notas | escritura | Tras un sí |
| delete_note | Quita una nota | escritura | Tras un sí |
| cualquier otro nombre | No está en la tabla | desconocido | Nunca |
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.
Pregunta a la persona y rechaza por defecto
Imprime lo que va a hacer la herramienta y lee una respuesta. Solo s es un sí. Todo lo demás, una línea vacía o ninguna respuesta, es un no.
Esto es denegar por defecto: una llamada solo se ejecuta con un sí claro. En la siguiente ejecución las respuestas se dan de antemano, así que todos ven las mismas líneas. Escrita a mano, te esperaría.
Filtra tres llamadas con respuestas fijas
printf 's\nn\n' | docker exec -i -w /home/node/project agent node -e "const rl=require('readline').createInterface({input:process.stdin});const it=rl[Symbol.asyncIterator]();const R={get_price:'read',add_note:'write',delete_note:'write'};const N=['leche'];const F={get_price:a=>40,add_note:a=>N.push(a.text),delete_note:a=>N.splice(a.index,1)};(async()=>{const log=[];for(const c of [{n:'get_price',a:{item:'pera'}},{n:'add_note',a:{text:'huevos'}},{n:'delete_note',a:{index:0}}]){let ok=R[c.n]=='read';if(ok===false){process.stdout.write('¿Permitir '+c.n+' '+JSON.stringify(c.a)+'? (s/n) ');const l=await it.next();ok=l.value?.trim()=='s';console.log(ok?'s':'n')}if(ok)F[c.n](c.a);log.push(c.n+': '+(ok?'ejecutada':'rechazada'))}console.log(log.join(', '));console.log('Notas: '+N.join(', '));rl.close()})()"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Deja que un modelo pida, y recházalo
printf 'n\n' | docker exec -i -w /home/node/project agent node -e "const{ask}=require('./ai.cjs');const rl=require('readline').createInterface({input:process.stdin});const it=rl[Symbol.asyncIterator]();const T=[{type:'function',function:{name:'add_note',description:'Guarda una nota.',parameters:{type:'object',properties:{text:{type:'string'}}}}}];(async()=>{let c;try{c=(await ask([{role:'user',content:'Guarda una nota: comprar leche'}],T,'required')).tool_calls[0].function}catch(e){console.log('Sin modelo ('+e.message+'). Muestra grabada:');c={name:'add_note',arguments:JSON.stringify({text:'comprar leche'})}}process.stdout.write('El modelo quiere '+c.name+' '+c.arguments+'. ¿Permitir? (s/n) ');const l=await it.next();const ok=l.value?.trim()=='s';console.log(ok?'s':'n');console.log(ok?'add_note ejecutada.':'Rechazada. No se ejecutó nada.');rl.close()})()"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Lleva un registro de cada llamada
Anota cada llamada: su nombre, si se preguntó a una persona, qué respondió y si se ejecutó. Un registro muestra lo que hizo un asistente, y lo que se le impidió hacer.
Anota también los rechazos. Una llamada rechazada que nadie ve se vuelve un enigma más tarde.
Un error frecuente
Advertencia
Mucha gente cree que el modelo decide si una llamada arriesgada es segura. No es así: puede equivocarse o dejarse engañar por un texto que lee. Tu programa decide, según tu propia tabla, y pregunta a una persona antes de cualquier escritura.
Para ir más lejos: cambia las respuestas
En la primera ejecución, cambia las respuestas dadas de antemano s y n por n y n. Vuelve a ejecutarla y lee la línea de notas.
Después cambia get_price a write en R. Escribe qué pregunta ahora el programa.