Cómo funciona un disyuntor
Un disyuntor es una regla sencilla que deja de llamar a un servicio que no para de fallar. Mientras está abierto, la función usa una alternativa en vez de esperar una llamada que va a fallar.
El módulo sobre coste, trazas y fiabilidad enumera las alternativas. Esta lección va más allá: cuándo dejar de llamar, cuándo volver a probar y un interruptor de emergencia que una persona puede pulsar.
Al final de esta lección
Nota
Puedo mantener útil una función mientras su modelo falla.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Disyuntor | una regla que corta las llamadas a un servicio que falla | |
| Cerrado | el estado normal: las llamadas pasan | |
| Abierto | el estado de corte: no hay llamadas y contesta una alternativa | |
| Semiabierto | una llamada de prueba para ver si el servicio ha vuelto | |
| Enfriamiento | los pasos que se espera antes de una prueba | 4 |
| Interruptor de emergencia | un control que apaga una función al instante |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Tres estados
Un disyuntor está cerrado cuando todo va bien. Las llamadas pasan. Cuenta los fallos seguidos. Cuando la cuenta llega al umbral, aquí 3, se abre.
Mientras está abierto no hace ninguna llamada. Tras el enfriamiento, aquí 4 pasos, se pone semiabierto y deja pasar una llamada. Un éxito lo cierra. Un fallo lo abre otra vez.
Ejemplo resuelto: pasos 3 a 9
| Paso | La llamada | Estado hallado | Servido | Motivo |
|---|---|---|---|---|
| 3 | Fallaría | Cerrado | Fallo | Primer fallo seguido, cuenta 1 |
| 4 | Fallaría | Cerrado | Fallo | Cuenta 2 |
| 5 | Fallaría | Cerrado | Fallo | La cuenta llega a 3, así que se abre |
| 6 | Funcionaría | Abierto | Omitido | Sin llamada. 1 paso desde que se abrió |
| 7 | Funcionaría | Abierto | Omitido | 2 pasos desde que se abrió |
| 8 | Funcionaría | Abierto | Omitido | 3 pasos desde que se abrió |
| 9 | Funcionaría | Semiabierto | Modelo | 4 pasos, la prueba funciona, así que se cierra |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que un disyuntor debe abrirse con el primer fallo. Un solo fallo suele ser un tropiezo. Cuenta los fallos seguidos y deja que un éxito ponga la cuenta a cero, o una sola llamada mala cortará un servicio sano.
Lo que dice la alternativa
Una alternativa debe ser honesta y útil. La mejor es una respuesta guardada a la misma pregunta. Si no la hay, di con claridad que el asistente está ocupado y cuándo probar de nuevo.
Nunca muestres una respuesta inventada como si la hubiera dado el modelo.
El interruptor es una decisión de una persona
Un disyuntor reacciona a los fallos. Una persona pulsa el interruptor de emergencia cuando el modelo funciona pero no debería usarse. Quizá da respuestas erróneas o cuesta demasiado.
Mientras está activo se omite cada llamada. No toca el disyuntor, que conserva su propia cuenta y su estado.
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 el script del disyuntor, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'function step(b, outcome, i, threshold, cooldown, kill) {' ' if (kill) return [b.state, \"omitido\"];' ' if (b.state === \"abierto\" && i - b.openedAt >= cooldown) b.state = \"semiabierto\";' ' const found = b.state;' ' if (found === \"abierto\") return [found, \"omitido\"];' ' if (outcome === 1) { b.state = \"cerrado\"; b.fails = 0; return [found, \"modelo\"]; }' ' b.fails = b.fails + 1;' ' if (found === \"semiabierto\" || b.fails >= threshold) { b.state = \"abierto\"; b.openedAt = i; b.fails = 0; }' ' return [found, \"fallo\"];' '}' > breaker.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 script del disyuntor, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const outcomes = [1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1];' 'function drive(kills) {' ' const b = { state: \"cerrado\", fails: 0, openedAt: 0 };' ' return outcomes.map(function (o, k) {' ' const r = step(b, o, k + 1, 3, 4, kills.includes(k + 1));' ' return r[0] + \":\" + r[1];' ' });' '}' >> breaker.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 script del disyuntor, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const trace = drive([]);' ' console.log(trace.slice(0, 10).join(\" \"));' ' console.log(trace.slice(10).join(\" \"));' ' const made = trace.filter(function (t) { return t.endsWith(\"omitido\") === false; }).length;' ' console.log(\"llamadas hechas \" + made + \" de \" + trace.length);' ' console.log(\"interruptor en 9 y 10: \" + drive([9, 10]).slice(8, 11).join(\" \"));' ' const q = \"En una frase corta, ¿qué debe decir una función cuando su modelo no está disponible?\";' ' const reply = await ask([{ role: \"user\", content: q }], \"Debe decir con claridad que el asistente está ocupado y ofrecer una respuesta guardada u otra vía.\", 60);' ' console.log(\"el modelo dijo: \" + reply.text);' '}' 'main();' >> breaker.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 el disyuntor
docker exec -w /home/node/project agent node breaker.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Lo que imprime el Run
| Línea | Qué muestra |
|---|---|
| cerrado:modelo cerrado:modelo cerrado:fallo ... | Pasos 1 a 10, el estado hallado y cómo se sirvió |
| abierto:omitido abierto:omitido abierto:omitido semiabierto:modelo | El disyuntor se abre, espera y una prueba funciona |
| llamadas hechas 14 de 20 | Se ahorraron seis llamadas |
| interruptor en 9 y 10: abierto:omitido abierto:omitido semiabierto:modelo | El interruptor omitió dos pasos y el enfriamiento siguió |
| el modelo dijo: ... | Una frase del laboratorio. Una muestra grabada si ningún modelo responde |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.