Dile qué estaba mal
Un bucle de reparación vuelve a preguntar tras una mala respuesta y le devuelve los fallos al modelo para que los corrija. Importa porque un segundo intento que conoce el problema suele acertar donde un reintento a ciegas no lo haría.
Una herramienta de formularios recibe una respuesta con una palabra que el esquema no permite. Rechazarla pierde el trabajo. Preguntar de nuevo, con el fallo en el mensaje, muchas veces lo salva.
Al final de esta lección
Nota
Puedo reintentar una mala respuesta con el error como aviso, unas pocas veces y no más.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Bucle de reparación | preguntar otra vez tras una mala respuesta, con un aviso |
| Aviso | los fallos, devueltos en un mensaje nuevo |
| Límite de reintentos | el máximo de intentos extra que permites |
| Salida de reserva | lo que se hace al llegar al límite |
| Lista de mensajes | la conversación que se envía en cada llamada |
| Rendirse | parar y usar la salida de reserva |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Devuelve los fallos
Para reparar una respuesta, pregunta de nuevo con toda la conversación hasta ahora. Añade la mala respuesta como mensaje del asistente. Luego añade un mensaje del usuario que liste los fallos y repita la regla: responde solo con el JSON.
La lista de mensajes crece de dos en dos. El modelo ve lo que dijo y lo que estaba mal.
Ejemplo resuelto: tres intentos
| Intento | Respuesta | Fallo encontrado | Se envía después |
|---|---|---|---|
| 1 | Seguramente sí, el trigo lo lleva. | No es un objeto JSON | El fallo y la regla de responder solo con el JSON |
| 2 | {"answer": "quizá"} | answer debe ser sí o no | El fallo y la regla otra vez |
| 3 | {"answer": "sí"} | Ninguno | Nada: se acepta |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un límite y una salida de reserva
Cada reintento cuesta tiempo y crédito. Por eso fija un límite de reintentos: el primer intento y como mucho dos más. Al llegar al límite, ríndete y usa la salida de reserva.
Una salida de reserva es un valor seguro como «desconocido», o un error que verá una persona. Anota que ocurrió, para ver con qué frecuencia pasa.
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 el bucle de reparación, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '' 'const allowed = [\"sí\", \"no\"];' 'const MAX_TRIES = 3; // el primer intento y como mucho dos más' '' 'function pullJson(text) {' ' const start = text.indexOf(\"{\");' ' const end = text.lastIndexOf(\"}\");' ' if (start === -1 || end < start) return null;' ' try { return JSON.parse(text.slice(start, end + 1)); } catch (error) { return null; }' '}' > repair3.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 bucle de reparación, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function problemsOf(text) {' ' const reply = pullJson(text);' ' if (reply === null) return [\"la respuesta no es un objeto JSON\"];' ' if (Object.prototype.hasOwnProperty.call(reply, \"answer\") === false) return [\"falta la clave answer\"];' ' if (allowed.includes(reply.answer) === false) return [\"answer debe ser sí o no\"];' ' return [];' '}' >> repair3.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 bucle de reparación, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function repairLoop(question, samples) {' ' const messages = [{ role: \"user\", content: question }];' ' for (let go = 1; go <= MAX_TRIES; go = go + 1) {' ' const reply = await ask(messages, samples, 60);' ' const found = problemsOf(reply);' ' console.log(\"Intento \" + go + \": \" + (found.length === 0 ? \"ok\" : found.join(\"; \")));' ' if (found.length === 0) return { ok: true, reply: pullJson(reply), tries: go };' ' messages.push({ role: \"assistant\", content: reply });' ' messages.push({ role: \"user\", content: \"Corrige estos problemas y responde solo con el JSON: \" + found.join(\"; \") });' ' }' ' return { ok: false, reply: null, tries: MAX_TRIES };' '}' >> repair3.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 bucle de reparación, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const question = \"¿Lleva gluten el pan de trigo? Responde con un objeto JSON con la clave answer, que vale sí o no. Responde solo con el JSON.\";' ' // Muestras grabadas, usadas solo si ningún modelo responde: dos respuestas malas y luego una buena.' ' const samples = [\"Seguramente sí, el trigo lo lleva.\", JSON.stringify({ answer: \"quizá\" }), JSON.stringify({ answer: \"sí\" })];' ' const result = await repairLoop(question, samples);' ' const fallback = { answer: \"desconocido\" };' ' console.log(\"Intentos usados:\", result.tries);' ' console.log(\"Resultado:\", JSON.stringify(result.ok ? { answer: result.reply.answer } : fallback));' '}' 'main();' >> repair3.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 bucle
docker exec -w /home/node/project agent node repair3.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 | Lo que puede mostrar |
|---|---|
| Intento 1 | ok, si el modelo respondió JSON limpio a la primera |
| Intento 2 e Intento 3 | Solo si el intento 1 falló: el fallo, o ok |
| Intentos usados | Un número de 1 a 3 |
| Resultado | La respuesta, o la salida de reserva {"answer":"desconocido"} |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que reintentar hasta que funcione es seguro. Si el modelo nunca acierta, un bucle sin límite no termina, y cada intento cuesta tiempo y crédito. Fija siempre un límite y decide antes la salida de reserva.