Dos agentes, un registro, una escritura perdida
El estado compartido es un registro que leen y escriben varios agentes. Importa porque dos agentes que leen a la vez y luego escriben a la vez pueden borrarse el trabajo sin que nadie lo note.
No falla nada. El registro se queda con la última escritura, y un buen hallazgo desaparece.
Al final de esta lección
Nota
Puedo dejar que los agentes compartan un registro sin pisarse, y avisar de los conflictos.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Registro compartido | una sola copia de los hechos que todos los agentes leen y escriben |
| Mensaje | texto que un agente envía a otro |
| Versión | un número que sube con cada escritura aceptada |
| Versión base | la versión que leyó quien escribe antes de escribir |
| Conflicto | una escritura basada en un campo que cambió después |
| Actualización perdida | una escritura que borra otra sin que se vea |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Mensajes o un registro compartido
Los agentes pueden pasarse mensajes. Cada uno guarda su copia de lo que ha oído y nada entra en conflicto, pero las copias se van separando.
O pueden compartir un registro. Hay una sola verdad, pero dos autores pueden chocar. La mayoría de los equipos necesita las dos cosas: mensajes para decir qué hacer, un registro para lo que se sabe.
Ejemplo resuelto: mensajes y registro
| Mensajes | Registro compartido | |
|---|---|---|
| Quién guarda los hechos | Cada agente, por separado | Un solo sitio |
| ¿Pueden discrepar dos agentes? | Sí, y nadie lo sabe | No, hay un solo valor |
| ¿Puede perderse una escritura? | No | Sí, si no se comprueban las escrituras |
| Mejor para | Decir a un trabajador qué hacer | Lo que el equipo ha encontrado hasta ahora |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una escritura nombra la versión que leyó
Da una versión a cada campo. Cuando un agente lee, guarda la versión. Cuando escribe, devuelve esa versión base.
El registro acepta la escritura solo si el campo no ha cambiado desde entonces. Si no, responde con un conflicto, y el agente vuelve a leer y decide. Un campo que nadie ha escrito no puede entrar en conflicto.
Un error frecuente
Advertencia
Mucha gente resuelve un conflicto dejando ganar la última escritura. Eso es la actualización perdida, con una línea de registro más. Un conflicto es una pregunta para quien escribe: leer el valor nuevo y luego decidir.
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 registro compartido, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const record = { version: 0, fields: {} };' 'function read() {' ' return { version: record.version, fields: JSON.parse(JSON.stringify(record.fields)) };' '}' > state.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 registro compartido, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Una escritura nombra la versión que leyó. Solo vale si nadie cambió ese campo desde entonces.' 'function write(agent, field, value, base) {' ' const f = record.fields[field];' ' const word = \" escribe \" + field + \" desde la versión \" + base + \": \";' ' if (f && f.version > base) {' ' console.log(agent + word + \"CONFLICTO, en la versión \" + f.version + \" por \" + f.by);' ' return false;' ' }' ' record.version += 1;' ' record.fields[field] = { value: value, version: record.version, by: agent };' ' console.log(agent + word + \"aplicada, registro en la versión \" + record.version);' ' return true;' '}' >> state.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 registro compartido, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const a = read();' ' const b = read();' ' write(\"precios\", \"coste\", \"108 euros\", a.version);' ' if (write(\"lector\", \"coste\", \"120 euros\", b.version) === false) {' ' const again = read();' ' console.log(\"lector vuelve a leer: coste es \" + again.fields.coste.value + \" en la versión \" + again.version);' ' write(\"lector\", \"coste_nota\", \"la segunda cifra era 120\", again.version);' ' }' ' write(\"lector\", \"quieren\", \"212 de 400\", b.version);' ' await last();' '}' >> state.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 registro compartido, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function last() {' ' const q = [{ role: \"user\", content: \"Escribe una nota de menos de 12 palabras que diga que dos cifras de un mismo coste no coinciden.\" }];' ' const note = (await ask(q, \"Dos cifras de un mismo coste no coinciden: revisa la fuente.\", 30)).text.trim();' ' write(\"revisor\", \"nota\", note, record.version);' ' const names = Object.keys(record.fields);' ' console.log(\"Campos: \" + names.map(function (k) { return k + \"@\" + record.fields[k].version; }).join(\", \"));' '}' 'main();' >> state.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 registro compartido
docker exec -w /home/node/project agent node state.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Es un sustituto
El laboratorio no tiene base de datos, así que el registro es un objeto del script. Un equipo real usa una base de datos que hace esta comprobación en un solo paso. Comprobar y luego escribir en dos pasos deja un hueco a otro autor.
La única llamada en vivo redacta una nota. Sus palabras cambian en cada ejecución.
Una salida posible
| Línea | Qué muestra |
|---|---|
| precios escribe coste desde la versión 0: aplicada, registro en la versión 1 | Gana el primer autor |
| lector escribe coste desde la versión 0: CONFLICTO, en la versión 1 por precios | El segundo había leído una versión antigua |
| lector vuelve a leer: coste es 108 euros en la versión 1 | Tras el conflicto, lee el valor nuevo y decide |
| lector escribe coste_nota desde la versión 1: aplicada, registro en la versión 2 | Tras leer de nuevo, escribe una nota |
| lector escribe quieren desde la versión 0: aplicada, registro en la versión 3 | Un campo nuevo no puede dar conflicto |
| revisor escribe nota desde la versión 3: aplicada, registro en la versión 4 | Escribe una nota nueva, con la versión actual |
| Campos: coste@1, coste_nota@2, quieren@3, nota@4 | La versión en que se escribió cada campo |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.