Un trabajador solo conoce su encargo
El patrón orquestador-trabajadores es un programa donde un agente reparte una tarea en encargos, da cada uno a un trabajador y une los resultados. Importa porque un trabajador no sabe nada más que su encargo.
Un encargo vago recibe una respuesta segura a la pregunta equivocada. Un encargo que se entiende solo recibe una respuesta que puedes comprobar.
Al final de esta lección
Nota
Puedo repartir una tarea en encargos que se entienden solos, y comprobar cada resultado antes de unirlos.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Encargo | todo lo que recibe un trabajador: pregunta, material y forma de la respuesta |
| Forma del resultado | los campos que un trabajador debe devolver |
| Material | el texto que un trabajador puede usar para responder |
| Validación | comprobar un resultado antes de usarlo |
| Unión | juntar los resultados aceptados |
| Fuente desconocida | una fuente que un trabajador cita pero nunca recibió |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Qué lleva un encargo
Un trabajador no ve la tarea, ni los otros encargos, ni la conversación. Lo que no está en el encargo no existe.
Así, un encargo lleva la pregunta, el material que se puede usar, la forma de la respuesta y un límite de tamaño. El material es el texto mismo, no la promesa de que el trabajador lo buscará.
Ejemplo resuelto: un encargo
| Campo | Ejemplo | Por qué está |
|---|---|---|
| question | ¿Cuánto costaría un domingo en horas de personal? | Una pregunta, no toda la tarea |
| material | La hoja de costes de personal, como texto | El trabajador no puede usar nada más |
| sources | s2 | Para que la respuesta diga de dónde viene |
| reply | Un objeto con answer y sources | Para que el orquestador la compruebe |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Valida un resultado antes de unirlo
Un resultado es un dato que viene de una parte del sistema que no controlas. Comprueba primero la forma: ¿hay respuesta, hay una lista de fuentes? Luego comprueba que cada fuente es una que dio el encargo.
Une solo lo que pasa. El módulo sobre cómo construir agentes pasa un mensaje entre agentes. Aquí decidimos si creemos lo que vuelve.
Un error frecuente
Advertencia
Mucha gente une todos los resultados y arregla el desorden después. Un resultado malo pesa entonces lo mismo que uno bueno en la respuesta final. Rechaza en la puerta y guarda el motivo.
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 orquestador, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const SOURCES = {' ' s1: \"Encuesta: 212 de 400 visitantes pidieron abrir los domingos.\",' ' s2: \"Personal: seis horas a 18 euros la hora son 108 euros cada domingo.\",' ' s3: \"Visitas: el sábado hubo 310 visitas el mes pasado, un día de semana 120.\",' '};' '// Un encargo lleva todo lo que su trabajador puede usar: la pregunta y el texto.' 'function brief(id, question, ids) {' ' const material = ids.map(function (i) { return SOURCES[i]; });' ' return { id: id, question: question, sources: ids, material: material, reply: \"un objeto con answer y sources\" };' '}' > team.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 orquestador, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Trabajadores con guion. Cada uno ve su encargo y nada más.' 'const WORKERS = {' ' b1: function (b) { return { answer: \"212 de 400 visitantes lo quieren\", sources: [\"s1\"] }; },' ' b2: function (b) { return { answer: \"108 euros cada domingo\", sources: [\"s2\"] }; },' ' b3: function (b) { return { answer: \"Los fines de semana hay mucha gente\", sources: [\"s9\"] }; },' '};' 'function problems(b, r) {' ' const out = [];' ' const full = typeof r.answer === \"string\" && r.answer.trim().length > 0;' ' if (full === false) out.push(\"falta la respuesta\");' ' if (Array.isArray(r.sources) === false || r.sources.length === 0) out.push(\"faltan las fuentes\");' ' else r.sources.forEach(function (s) { if (b.sources.includes(s) === false) out.push(\"fuente desconocida \" + s); });' ' return out;' '}' >> team.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 orquestador, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const own = \"¿Cuánta gente hay en la biblioteca un día de fin de semana?\";' ' const q = [{ role: \"user\", content: \"Escribe una pregunta de investigación corta sobre la afluencia a una biblioteca en fin de semana.\" }];' ' const text = (await ask(q, own, 30)).text.trim();' ' const briefs = [' ' brief(\"b1\", \"¿Cuántos visitantes quieren abrir los domingos?\", [\"s1\"]),' ' brief(\"b2\", \"¿Cuánto costaría un domingo en horas de personal?\", [\"s2\"]),' ' brief(\"b3\", text.endsWith(\"?\") ? text : own, [\"s3\"]),' ' ];' ' for (const b of briefs) {' ' const r = WORKERS[b.id](b);' ' const p = problems(b, r);' ' console.log(b.id + \" [\" + b.material.length + \" fuente] \" + b.question);' ' console.log(\" \" + (p.length ? \"RECHAZADO: \" + p.join(\", \") : \"aceptado: \" + r.answer));' ' }' '}' 'main();' >> team.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 orquestador
docker exec -w /home/node/project agent node team.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Los trabajadores tienen guion
El laboratorio solo tiene un modelo, así que un equipo real sería un modelo haciendo todos los papeles. Los tres trabajadores de aquí son funciones simples con respuestas grabadas. La única llamada en vivo redacta una pregunta de investigación.
Un sistema real envía cada encargo a un modelo, y la comprobación sigue igual.
Una salida posible
| Línea | Qué muestra |
|---|---|
| b1 [1 fuente] ¿Cuántos visitantes quieren abrir los domingos? | Un encargo con su material |
| aceptado: 212 de 400 visitantes lo quieren | La forma y las fuentes pasan |
| aceptado: 108 euros cada domingo | El segundo trabajador también pasa |
| RECHAZADO: fuente desconocida s9 | El tercero cita una fuente que nunca recibió |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.