Un equipo falla como no falla un agente
Un fallo multiagente es una forma en que un equipo de agentes se tuerce y ningún agente solo lo haría. Importa porque la falta está en la conversación entre ellos, y cada agente parece correcto por separado.
Se encuentra leyendo la transcripción, la lista de mensajes que se enviaron.
Al final de esta lección
Nota
Puedo encontrar el fallo en una transcripción y nombrar su clase.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Transcripción | los mensajes que enviaron los agentes, en orden |
| Bucle | el mismo mensaje enviado una y otra vez |
| Interbloqueo | dos agentes que esperan cada uno al otro |
| Coste desbocado | un gasto de tokens que crece más allá del presupuesto |
| Pasada como hecho | una suposición que después se repite como un hecho |
| Protección | una regla en el código que detiene un fallo pronto |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Cuatro formas de torcerse de un equipo
Un bucle repite un mensaje. Un interbloqueo pone a dos agentes esperando cada uno a que el otro empiece. Un coste desbocado crece, porque cada ronda lee todo el historial.
El cuarto es más discreto. Un trabajador supone, el siguiente agente repite la suposición, y al tercero ya se escribe como un hecho.
Ejemplo resuelto: la señal y la protección
| Fallo | Señal en una transcripción | Protección |
|---|---|---|
| Bucle (loop) | Mismo emisor, receptor y texto tres veces | Parar en la tercera repetición |
| Interbloqueo (deadlock) | Dos mensajes que dicen esperando, cruzados | Un límite de tiempo y un desempate |
| Pasada como hecho (passed-on) | Una suposición, y luego las mismas palabras como hecho | Llevar el estado junto a la afirmación |
| Desbocado (runaway) | Tokens por ronda que no dejan de subir | Un presupuesto para toda la ejecución |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Mira primero la señal más clara
Una transcripción real puede mostrar más de una señal. Decide un orden y mantenlo. Un interbloqueo y un bucle paran el trabajo, así que nómbralos antes que el coste.
El módulo sobre cómo construir agentes ya trató la protección contra bucles. Esta lección trata de reconocer las cuatro, para saber qué protección añadir.
Un error frecuente
Advertencia
Mucha gente añade un reintento cuando un equipo se atasca. Un reintento alarga un bucle y no acorta un interbloqueo. Primero nombra el fallo, luego elige la protección que le corresponde.
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\";' '// 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.
Escribir 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.
Escribir el detector de fallos, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'function m(from, to, text, tokens, status) {' ' return { from: from, to: to, text: text, tokens: tokens, status: status };' '}' 'const T = {};' 'T.loop = [m(\"autor\", \"revisor\", \"revisa el borrador\", 300, \"none\"), m(\"revisor\", \"autor\", \"añade una fuente\", 200, \"none\"),' ' m(\"autor\", \"revisor\", \"revisa el borrador\", 300, \"none\"), m(\"revisor\", \"autor\", \"añade una fuente\", 200, \"none\"),' ' m(\"autor\", \"revisor\", \"revisa el borrador\", 300, \"none\")];' 'T.deadlock = [m(\"planificador\", \"revisor\", \"esperando la revisión antes de planificar\", 150, \"none\"),' ' m(\"revisor\", \"planificador\", \"esperando el plan antes de revisar\", 150, \"none\")];' > fail.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 detector de fallos, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'T.passed = [m(\"lector\", \"planificador\", \"las visitas del sábado subieron un 40 por ciento\", 200, \"guess\"),' ' m(\"planificador\", \"autor\", \"las visitas del sábado subieron un 40 por ciento\", 200, \"fact\")];' 'T.grows = [m(\"a\", \"b\", \"ronda uno\", 1000, \"none\"), m(\"b\", \"a\", \"ronda dos\", 2000, \"none\"), m(\"a\", \"b\", \"ronda tres\", 4000, \"none\"),' ' m(\"b\", \"a\", \"ronda cuatro\", 8000, \"none\"), m(\"a\", \"b\", \"ronda cinco\", 16000, \"none\")];' 'T.fine = [m(\"planificador\", \"lector\", \"busca la demanda del domingo\", 200, \"none\"), m(\"lector\", \"planificador\", \"212 de 400\", 150, \"fact\")];' >> fail.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 detector de fallos, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function some2(ms, test) {' ' return ms.some(function (x, i) { return ms.some(function (y, j) { return i < j && test(x, y); }); });' '}' 'function deadlock(ms) {' ' return some2(ms, function (x, y) {' ' return x.text.startsWith(\"esperando\") && y.text.startsWith(\"esperando\") && x.from === y.to && x.to === y.from;' ' });' '}' 'function loop(ms) {' ' return ms.some(function (x) {' ' return ms.filter(function (y) { return y.from === x.from && y.to === x.to && y.text === x.text; }).length >= 3;' ' });' '}' >> fail.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 detector de fallos, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function passedOn(ms) {' ' return some2(ms, function (x, y) { return x.status === \"guess\" && y.status === \"fact\" && x.text === y.text; });' '}' 'function total(ms) { return ms.reduce(function (a, x) { return a + x.tokens; }, 0); }' 'function find(ms, budget) {' ' if (deadlock(ms)) return \"deadlock\";' ' if (loop(ms)) return \"loop\";' ' if (passedOn(ms)) return \"passed-on\";' ' return total(ms) > budget ? \"runaway\" : \"none\";' '}' >> fail.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 detector de fallos, parte 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' for (const name of Object.keys(T)) {' ' console.log(name + \": \" + T[name].length + \" mensajes, \" + total(T[name]) + \" tokens, encontrado \" + find(T[name], 20000));' ' }' ' const q = [{ role: \"user\", content: \"En una frase: ¿qué regla simple evita que dos agentes se envíen el mismo mensaje sin parar?\" }];' ' const r = await ask(q, \"Contar cada mensaje y parar en la tercera repetición.\", 40);' ' console.log(\"Modelo: \" + r.text.trim());' '}' 'main();' >> fail.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 detector de fallos
docker exec -w /home/node/project agent node fail.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Son transcripciones grabadas
Una ejecución real gastaría muchos tokens en un solo equipo roto. Por eso las cinco transcripciones son datos grabados, cortos e inventados. El detector es código de verdad.
La única llamada en vivo pide una regla. Sus palabras cambian en cada ejecución.
Una salida posible
| Línea | Qué muestra |
|---|---|
| loop: 5 mensajes, 1300 tokens, encontrado loop | Un mensaje tres veces |
| deadlock: 2 mensajes, 300 tokens, encontrado deadlock | Cada uno espera al otro |
| passed: 2 mensajes, 400 tokens, encontrado passed-on | Una suposición pasó a ser hecho |
| grows: 5 mensajes, 31000 tokens, encontrado runaway | Por encima del presupuesto de 20000 |
| fine: 2 mensajes, 350 tokens, encontrado none | Ninguna señal de fallo |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.