Dos agentes que se pasaban un ticket
Un sistema multiagente reparte el trabajo entre agentes que tienen cada uno un rol. Se pasan el trabajo con mensajes de traspaso. Importa porque cada traspaso cuesta una llamada y puede perder contexto.
Un agente de facturación manda un ticket a un agente de existencias. El de existencias lo devuelve enseguida. El ticket rebotó toda la noche.
Al final de esta lección
Nota
Puedo repartir el trabajo entre agentes y decir cuándo basta un solo agente.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Rol | el único trabajo que tiene un agente | precios |
| Traspaso | un mensaje que pasa el trabajo a otro agente | |
| Enrutador | la parte que elige qué rol recibe una tarea | |
| Registro compartido | un diario que todos los agentes leen y completan | |
| Protección contra bucles | un control que evita que el trabajo rebote sin fin |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Cuándo basta un solo agente
Empieza con un agente y una instrucción. Añade un segundo solo si sabes nombrar la ganancia. Puede ser una instrucción más corta, menos herramientas a la vista, otro modelo o un paso que una persona debe aprobar.
Cada agente extra añade llamadas, retraso y un sitio por donde se escapa el contexto. Si un agente con tres herramientas hace el trabajo, para ahí.
Ejemplo resuelto: un agente o varios
| Situación | Elección | Razón |
|---|---|---|
| Buscar un precio y decirlo | Un agente | Una herramienta, una instrucción |
| Reglas de precios, existencias y envío, 20 herramientas | Roles | Cada rol ve solo sus herramientas |
| Un reembolso de más de 100 euros | Un paso humano | Aprobar no es trabajo de un agente |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Escribir la ayuda 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 la ayuda 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 la demo de traspasos, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const PRICES = { pan: 90, leche: 110 };' 'const STOCK = { pan: 14, leche: 0 };' 'function look(table, q, unit) {' ' const key = Object.keys(table).find(function (w) { return q.indexOf(w) >= 0; });' ' return key === undefined ? \"no se encontró nada\" : key + \": \" + table[key] + unit;' '}' 'const roles = {' ' precios: function (q) { return look(PRICES, q, \" céntimos\"); },' ' existencias: function (q) { return look(STOCK, q, \" en stock\"); },' '};' 'function route(q) {' ' if (q.indexOf(\"cuánto\") >= 0) return \"precios\";' ' if (q.indexOf(\"en stock\") >= 0) return \"existencias\";' ' return null;' '}' > handoff1.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 la demo de traspasos, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function pick(q) {' ' const rule = route(q);' ' if (rule) return rule;' ' const text = \"¿Qué rol encaja con esta tarea: precios, existencias o ninguno? Responde con una sola palabra. Tarea: \" + q;' ' const word = (await ask([{ role: \"user\", content: text }], \"ninguno\", 10)).trim().toLowerCase();' ' return word === \"precios\" || word === \"existencias\" ? word : \"ninguno\";' '}' 'const trail = [];' 'function allowed(from, to) {' ' trail.push(from + \">\" + to);' ' return trail.filter(function (t) { return t === from + \">\" + to; }).length < 3;' '}' >> handoff1.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 la demo de traspasos, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const record = [];' ' for (const q of [\"cuánto cuesta el pan\", \"queda leche en stock\", \"qué tiempo hace\"]) {' ' const role = await pick(q);' ' const result = role === \"ninguno\" ? \"ningún rol encaja, pregunta a una persona\" : roles[role](q);' ' record.push({ to: role, task: q, result: result });' ' }' ' record.forEach(function (r) { console.log(r.to + \" | \" + r.task + \" | \" + r.result); });' ' let who = \"precios\";' ' for (let i = 0; i < 6; i++) {' ' const next = who === \"precios\" ? \"existencias\" : \"precios\";' ' if (allowed(who, next) === false) { console.log(\"Bucle: \" + who + \" a \" + next + \" por tercera vez, paro\"); break; }' ' who = next;' ' }' '}' 'main();' >> handoff1.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 y leer el registro compartido
docker exec -w /home/node/project agent node handoff1.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é puede mostrar |
|---|---|
| precios | cuánto cuesta el pan | pan: 90 céntimos |
| existencias | queda leche en stock | leche: 0 en stock |
| La tarea del tiempo | ninguno, o un rol que eligió el modelo, con un resultado flojo |
| Bucle | una línea que dice que un par apareció por tercera vez |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un traspaso es un mensaje con forma
Un mensaje de traspaso nombra quién lo envía, quién lo recibe, la tarea y el contexto que necesita el receptor. El receptor no debería tener que leer todo el historial del emisor.
Los agentes anotan lo que hicieron en un registro compartido. Léelo como el único sitio donde vive la verdad sobre el trabajo.
Evita que el trabajo rebote entre agentes
Dos agentes pueden decidir cada uno que el otro debe encargarse de una tarea. Nada falla dentro de ninguno, y aun así el trabajo no acaba nunca.
Cuenta cada par de emisor a receptor. Cuando un par llega a un límite, para y pasa el trabajo a una persona. Guarda el rastro de traspasos en el registro para ver el bucle.
Un error frecuente
Advertencia
Mucha gente cree que un enrutador debe preguntar siempre a un modelo. Una llamada a un modelo es más lenta, cuesta dinero y puede nombrar un rol que no existe. Usa reglas donde sean exactas, pregunta al modelo solo por el resto y comprueba lo que nombra.