Reúne las piezas en un bucle
Un arnés de agente es el programa que rodea al modelo: construye la ventana, ejecuta las herramientas, aplica el modo y registra los cambios. El modelo solo propone un paso. El arnés decide lo que ocurre.
Esta lección une las cinco anteriores en un bucle que puedes ejecutar y rebobinar.
Al final de esta lección
Nota
Puedo reunir contexto, herramientas, modos, aislamiento y deshacer en un arnés que puedo ejecutar y rebobinar.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Arnés | el programa que hace funcionar a un agente alrededor de un modelo |
| Bucle | pedir un paso, decidir, actuar, anotar y repetir |
| Modelo guionizado | una lista fija de pasos que sustituye a un modelo |
| Transcripción | el registro de cada paso y su resultado |
| Ventana | los últimos resultados que se envían al modelo a continuación |
| Rebobinar | deshacer todos los cambios posteriores a un paso elegido |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
El bucle, paso a paso
En cada vuelta el arnés pide un paso al modelo. Decide por el modo: permitir, preguntar o denegar. Si debe preguntar, responde la persona. Si el paso está permitido, el arnés anota lo que va a sustituir y luego lo hace.
El resultado entra en una ventana, que solo guarda los últimos. Luego el bucle se repite hasta que el modelo dice que ha terminado.
Qué aporta cada lección anterior
| Lección | Su lugar en el bucle |
|---|---|
| Contexto | La ventana solo guarda los últimos resultados |
| Herramientas | Cada paso nombra una herramienta estrecha |
| Modos | Una función decide permitir, preguntar o denegar |
| Aislamiento | Una tarea secundaria devuelve un resultado corto a la ventana |
| Deshacer | Se escribe una entrada de registro antes de un cambio |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente deja que un paso rechazado desaparezca. Entonces el modelo nunca aprende que fue rechazado y lo vuelve a pedir. Anota el rechazo como un resultado, para que la siguiente ventana lo muestre.
Escribe 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\";' '// Los tokens de una respuesta, o null si no trae ninguno.' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > 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.
Escribe el ayudante del modelo, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '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 || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"estado \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' '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.
Escribe el arnés, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const RISK = { read_file: \"read\", write_file: \"write\", delete_file: \"destructive\", run: \"run\" };' 'const script = [' ' { tool: \"read_file\", path: \"app.py\" },' ' { tool: \"write_file\", path: \"app.py\", text: \"x = 2\" },' ' { tool: \"run\", path: \"pytest\" },' ' { tool: \"delete_file\", path: \"notas.txt\" },' ' { tool: \"write_file\", path: \".env\", text: \"CLAVE=1\" },' ' { tool: \"done\" },' '];' 'const files = { \"app.py\": \"x = 1\", \"notas.txt\": \"pendiente\" };' 'const log = [];' > harness6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el arnés, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function decide(mode, risk, path) {' ' if (mode === \"plan\") return risk === \"read\" ? \"allow\" : \"deny\";' ' if (path.startsWith(\".env\") || risk === \"destructive\") return \"ask\";' ' if (mode === \"auto\" && (risk === \"read\" || risk === \"write\")) return \"allow\";' ' if (mode === \"ask\" && risk === \"read\") return \"allow\";' ' return \"ask\";' '}' >> harness6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el arnés, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function act(s, n) {' ' if (s.tool === \"read_file\") return s.path in files ? files[s.path] : \"ausente\";' ' if (s.tool === \"run\") return \"ejecutado \" + s.path;' ' log.push({ step: n, path: s.path, before: s.path in files ? files[s.path] : null });' ' if (s.tool === \"write_file\") { files[s.path] = s.text; return \"escrito \" + s.path; }' ' delete files[s.path];' ' return \"borrado \" + s.path;' '}' >> harness6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el arnés, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function runAll(mode, approvals) {' ' const out = [];' ' for (let i = 0; i < script.length; i++) {' ' const s = script[i];' ' if (s.tool === \"done\") break;' ' const v = decide(mode, RISK[s.tool], s.path);' ' let outcome = v === \"allow\" ? \"ok\" : \"denegado\";' ' if (v === \"ask\") outcome = approvals.shift() === true ? \"aprobado\" : \"rechazado\";' ' const good = outcome === \"ok\" || outcome === \"aprobado\";' ' out.push({ step: i + 1, tool: s.tool, path: s.path, outcome: outcome, result: good ? act(s, i + 1) : outcome });' ' }' ' return out;' '}' >> harness6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el arnés, parte 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function undoTo(step) {' ' for (const e of log.slice().reverse()) {' ' if (e.step <= step) continue;' ' if (e.before === null) delete files[e.path];' ' else files[e.path] = e.before;' ' }' '}' >> harness6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el arnés, parte 6
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const out = runAll(\"auto\", [true, false]);' ' for (const o of out) console.log(o.step + \" \" + o.tool + \" \" + o.path + \" \" + o.outcome + \" -> \" + o.result);' ' console.log(\"ventana \" + JSON.stringify(out.slice(-3).map(function (o) { return o.result; })));' ' console.log(\"archivos \" + JSON.stringify(files));' ' undoTo(1);' ' console.log(\"tras deshacer hasta 1 \" + JSON.stringify(files));' ' const text = out.map(function (o) { return o.tool + \" \" + o.outcome; }).join(\", \");' ' const reply = await ask([{ role: \"user\", content: \"Escribe un mensaje de commit de una línea para una ejecución que hizo esto: \" + text }], \"Corregir el total de la cesta en app.py\", 30);' ' console.log(\"mensaje: \" + reply.text);' '}' 'main();' >> harness6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta el arnés y rebobina
docker exec -w /home/node/project agent node harness6.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
El modelo es un guion
Los pasos salen de una lista fija, así que cada estudiante imprime la misma ejecución. Un arnés real pediría cada paso al modelo. La llamada corta al modelo solo redacta un mensaje de commit a partir de lo que hizo la ejecución.
Sin modelo imprime una muestra grabada para esa línea.
Una salida posible
| Línea | Qué muestra |
|---|---|
| 1 read_file app.py ok -> x = 1 | Una lectura pasa en modo auto |
| 3 run pytest aprobado -> ejecutado pytest | La primera aprobación fue sí |
| 4 delete_file notas.txt rechazado -> rechazado | La segunda aprobación fue no |
| 5 write_file .env rechazado -> rechazado | No quedaba ninguna aprobación, así que no |
| archivos {"app.py":"x = 2","notas.txt":"pendiente"} | Solo la escritura cambió algo |
| tras deshacer hasta 1 {"app.py":"x = 1",...} | El rebobinado devolvió la escritura |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.