El modelo pide un archivo
Limitar las herramientas de un modelo significa que tu código, no el modelo, decide lo que puede hacer cada una. Importa porque la petición de un modelo es texto, y el texto puede ser erróneo, engañado o inventado.
Un asistente de notas tiene una herramienta que lee una nota por su nombre. Una respuesta pide la nota «../privado». La herramienta lo intentaría sin más. Una política la detiene antes de empezar.
Al final de esta lección
Nota
Puedo comprobar cada petición de herramienta con una política de lista permitida antes de que se ejecute nada.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Lista permitida | las únicas herramientas y valores que se permiten |
| Política | datos que dicen lo que puede recibir cada herramienta |
| Comprobación de argumentos | una prueba de las entradas de una herramienta antes de ejecutarla |
| Mínimo privilegio | dar a una herramienta solo el poder que necesita |
| Tiempo límite | un tope a lo que puede tardar una llamada |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una lista permitida dice sí, nunca no
Una lista de prohibidos nombra lo malo y deja pasar el resto. Siempre se le escapa algo. Una lista permitida nombra lo bueno y rechaza el resto.
Escribe una política como datos: cada herramienta, cada argumento, su tipo y sus límites. Una función lee la política y juzga cada petición. El modelo nunca ejecuta nada por sí mismo.
Ejemplo: una política y cuatro peticiones
| Petición | La política dice | Decisión |
|---|---|---|
| read_note, name menu | name debe ser compras, menu u horario | Permitida |
| read_note, name ../privado | name debe ser compras, menu u horario | Rechazada: no permitido |
| run_shell, cualquier texto | No existe esa herramienta | Rechazada: herramienta desconocida |
| add_numbers, b 99999 | b es como mucho 1000 | Rechazada: demasiado grande |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Nunca construyas un comando con texto del modelo
Algunas herramientas tientan: ejecutar un comando, evaluar una expresión, escribir un archivo. No construyas ninguna con texto que escribió un modelo. Un rechazo es más seguro que un filtro ingenioso.
Si una función necesita ejecutar código real, hazlo en un entorno aislado aparte. Sin secretos y con un tiempo límite. Este laboratorio no tiene uno, así que la lección solo rechaza.
Escribir el ayudante del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 200 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"estado \" + res.status);' ' return (await res.json()).choices[0].message.content;' > model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el ayudante del modelo, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' } catch (error) {' ' console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' const list = [].concat(sample);' ' const turn = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' ' return list[Math.min(turn, list.length - 1)];' ' }' '}' 'module.exports = { ask };' >> model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el script de política, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '' '// Notas inventadas y una política: cada herramienta, sus argumentos y sus límites.' 'const notes = { compras: \"comprar harina\", menu: \"pan, tarta\", horario: \"abierto a las 8\" };' 'const policy = {' ' read_note: { name: { type: \"string\", allowed: Object.keys(notes) } },' ' add_numbers: { a: { type: \"number\", max: 1000 }, b: { type: \"number\", max: 1000 } },' '};' > policy.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el script de política, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function check(request) {' ' const rule = policy[request.tool];' ' if (rule === undefined) return \"rechazada: herramienta desconocida\";' ' const args = request.args || {};' ' for (const key of Object.keys(args)) {' ' if (rule[key] === undefined) return \"rechazada: argumento inesperado \" + key;' ' }' ' for (const key of Object.keys(rule)) {' ' const spec = rule[key];' ' const value = args[key];' ' if (typeof value === spec.type === false) return \"rechazada: \" + key + \" incorrecto\";' ' if (spec.allowed && spec.allowed.includes(value) === false) return \"rechazada: \" + key + \" no permitido\";' ' if (typeof spec.max === \"number\" && value > spec.max) return \"rechazada: \" + key + \" demasiado grande\";' ' }' ' return \"ok\";' '}' >> policy.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el script de política, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function run(request) {' ' if (check(request) === \"ok\") {' ' return request.tool === \"read_note\" ? notes[request.args.name] : request.args.a + request.args.b;' ' }' ' return check(request);' '}' 'const scripted = [' ' { tool: \"read_note\", args: { name: \"menu\" } },' ' { tool: \"read_note\", args: { name: \"../privado\" } },' ' { tool: \"run_shell\", args: { text: \"listar la carpeta\" } },' ' { tool: \"add_numbers\", args: { a: 5, b: 99999 } },' ' { tool: \"add_numbers\", args: { a: 2, b: 3, c: 4 } },' '];' 'for (const request of scripted) console.log(request.tool + \" -> \" + run(request));' >> policy.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el script de política, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function pullJson(text) {' ' const start = text.indexOf(\"{\");' ' const end = text.lastIndexOf(\"}\");' ' try { return JSON.parse(text.slice(start, end + 1)); } catch (error) { return null; }' '}' 'async function main() {' ' const question = [{ role: \"user\", content: \"Responde solo con un objeto JSON con las claves tool y args, para leer la nota llamada horario. Herramientas: read_note(name), add_numbers(a, b).\" }];' ' const sample = JSON.stringify({ tool: \"read_note\", args: { name: \"horario\" } });' ' const request = pullJson(await ask(question, sample, 80));' ' console.log(\"Petición del modelo: \" + JSON.stringify(request));' ' console.log(\"Resultado: \" + (request === null ? \"rechazada: no es JSON\" : run(request)));' '}' 'main();' >> policy.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecutar el script de política
docker exec -w /home/node/project agent node policy.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Una salida posible
| Línea | Qué muestra |
|---|---|
| read_note -> pan, tarta | La primera petición pasó todas las comprobaciones |
| read_note -> rechazada: name no permitido | El truco de ruta falló ante la lista permitida |
| run_shell -> rechazada: herramienta desconocida | La herramienta no está en la política |
| add_numbers -> rechazada: b demasiado grande | El número supera el máximo de la política. La última línea rechaza el argumento c, que la política no nombra |
| Petición del modelo, y Resultado | Cambia en cada ejecución. Una petición del modelo pasa por la misma comprobación |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Limita el tiempo y el poder
Un tiempo límite evita que una herramienta lenta retenga una petición para siempre. El ayudante que escribiste ya abandona al modelo a los 30 segundos.
Aplica también el mínimo privilegio. Una herramienta que lee notas no debe poder escribirlas, y una que escribe solo debe llegar a su propia carpeta.
Un error frecuente
Advertencia
Mucha gente cree que, si se dijeron las reglas al modelo, la herramienta no necesita comprobar. El modelo puede equivocarse, ser engañado o inventar una herramienta. Comprueba siempre en el código que ejecuta la herramienta, porque es la única parte que hace lo mismo cada vez.