Sigue funcionando cuando el modelo falla
Un respaldo es lo que hace una función cuando falla una llamada a un modelo, para que el usuario reciba algo. Importa porque las llamadas fallan de varias maneras, y una sola respuesta para todas es mala para la mayoría.
Un tiempo agotado merece un reintento. Un rechazo, no. Un presupuesto diario agotado pide una respuesta en caché o un mensaje honesto. Elige según el fallo.
Al final de esta lección
Nota
Puedo mantener una función en marcha cuando falla una llamada a un modelo, eligiendo un respaldo para cada tipo de fallo.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Respaldo | lo que haces en su lugar cuando falla una llamada |
| Reintento | volver a preguntar al mismo modelo, tras una espera |
| Segundo modelo | otro modelo al que se pregunta si falla el primero |
| Respuesta simple | un mensaje fijo y honesto, sin modelo |
| Degradar | dar una respuesta menor en lugar de ninguna |
| Tipo de fallo | el motivo por el que falló una llamada |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un respaldo es una cadena, en orden
Enumera las opciones de la menos costosa a la más degradada. Primero un reintento, tras una espera. Luego un segundo modelo. Luego una respuesta en caché, que puede ser vieja. Al final una respuesta simple: un mensaje fijo, verdadero, que dice al usuario qué hacer.
Cada paso cuesta tiempo y dinero. Una cadena que lo prueba todo en cada fallo duplica tu factura durante una caída.
Ejemplo resuelto: qué respaldo encaja
| Fallo | Primero | Por qué |
|---|---|---|
| Tiempo agotado, límite de ritmo | Reintentar una vez | A menudo pasa solo |
| Error del servidor | Segundo modelo | El primero está caído |
| Formato malo | Reintentar una vez | Un segundo intento suele valer |
| Rechazo | Respuesta simple | Volver a preguntar no cambiará nada |
| Presupuesto agotado | Respuesta en caché | No se permite ninguna llamada |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Acota la cadena y dilo
Pon un límite a cada paso: un reintento, un segundo modelo. Espera entre intentos y cuenta la espera dentro del tiempo del usuario. Que un respaldo no oculte nunca un fallo. Registra qué paso respondió.
Una respuesta en caché puede estar desfasada, así que dilo si importa. Una respuesta simple nunca debe hacerse pasar por la del modelo.
Un error frecuente
Advertencia
Mucha gente cree que reintentar es siempre el respaldo más seguro. Pero reintentar un rechazo gasta dinero, y reintentar durante una caída añade carga. Reintenta solo los fallos que pueden pasar solos, y solo una vez.
Escribe 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.
Escribe 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.
Escribe la cadena de respaldos, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '' 'let now = 0; // milisegundos, en un reloj que movemos nosotros' 'const store = new Map([[\"horario\", \"Abrimos a las nueve y cerramos a las cinco de la tarde.\"]]);' 'const PLAIN = \"Lo siento, el asistente no puede responder ahora. Inténtalo más tarde.\";' '// Un fallo grabado sustituye a uno real. Nada aquí provoca un error de verdad.' 'function fail(kind) { throw new Error(kind); }' > fallback5.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la cadena de respaldos, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function secondModel() {' ' const messages = [{ role: \"user\", content: \"Di el horario, de nueve a cinco, en una frase.\" }];' ' return (await ask(messages, \"Abrimos a las nueve y cerramos a las cinco de la tarde.\", 40)).text;' '}' >> fallback5.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la cadena de respaldos, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function answer(question, second) {' ' const steps = [' ' [\"primer modelo\", function () { fail(\"rate_limit\"); }],' ' [\"reintento\", function () { now = now + 200; fail(\"rate_limit\"); }],' ' [\"segundo modelo\", second],' ' [\"caché\", function () { return store.has(question) ? store.get(question) : fail(\"miss\"); }],' ' ];' >> fallback5.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la cadena de respaldos, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' for (const [name, run] of steps) {' ' try {' ' const text = await run();' ' console.log(question + \": respondió \" + name + \" a los \" + now + \" ms\");' ' return text;' ' } catch (error) {' ' console.log(question + \": \" + name + \" falló (\" + error.message + \")\");' ' }' ' }' ' console.log(question + \": respuesta simple\");' ' return PLAIN;' '}' >> fallback5.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la cadena de respaldos, parte 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const down = function () { fail(\"server_error\"); };' ' await answer(\"horario\", secondModel);' ' await answer(\"horario\", down);' ' await answer(\"reembolso\", down);' '}' 'main();' >> fallback5.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta la cadena
docker exec -w /home/node/project agent node fallback5.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Los fallos están grabados
Nada en este Run provoca un fallo real. Los rechazos y el error del servidor están escritos en el script como datos grabados, y el reloj es un número que mueve. Solo el segundo modelo es una llamada real.
Un sistema real recibe estos errores de una red real y espera de verdad entre intentos.
Una salida posible
| Línea | Qué muestra |
|---|---|
| horario: primer modelo falló (rate_limit) | Un fallo grabado |
| horario: reintento falló (rate_limit) | El reintento, tras 200 ms en nuestro reloj |
| horario: respondió segundo modelo a los 200 ms | La única llamada real |
| horario: respondió caché a los 400 ms | El segundo modelo está caído, así que responde la caché |
| reembolso: respuesta simple | No había nada en caché, así que se usa el mensaje simple |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.