Saltar al contenido
astorlm
Idioma: Español
← Mapa

Nivel 5

Errores en el bucle

Las herramientas fallan. Los modelos mandan datos incorrectos. Los servidores se caen por un segundo. Un bucle de agente tiene que decidir, para cada falla, quién se encarga: el modelo, tu código o nadie.
1/40 Pliegues del bandoneón:
  • user
  • assistant
  • tool_result
  • tool_result (error)
Un agente que juega una aventura gráfica: sus herramientas son los verbos de abajo, LOOK AT y USE. Este bucle se escribió a mano, y ejecuta las herramientas sin try/catch.

EventBus

El problema

Un agente juega una aventura gráfica. Sus herramientas son los verbos de la parte de abajo de la pantalla, look_at y use, y la tarea es "abre la puerta del templo". El modelo hace lo obvio: usar la llave con la puerta. Pero la cerradura está oxidada, y la herramienta lanza una excepción.

Las viejas aventuras gráficas se dividían en dos escuelas ante momentos así. En algunas, un movimiento equivocado te mataba: game over, de vuelta a tu última partida guardada. En otras no podías morir; el juego solo te decía por qué no funcionaba, y probabas otra cosa. Un bucle de agente también tiene que elegir escuela.

Si el bucle no atrapa el error, gana Krash: la excepción sube a través del bucle y run() la lanza. Se pierde la ejecución, y el error decía exactamente qué hacer: "aceítala primero". El único lector que podía usar ese mensaje nunca lo recibió. Atrápalo, devuélvelo como resultado de herramienta, y el modelo lo lee y lo vuelve a intentar. Eso cuesta un turno. No atraparlo cuesta toda la ejecución.

Tres tipos de falla

  • El modelo pidió mal

    Entrada inválida, JSON roto, una herramienta que no existe, una llave que no gira.

    El modelo. El error vuelve como resultado de herramienta marcado como error. El modelo lo lee y prueba otra cosa en el siguiente turno.

  • Falló el servidor del modelo

    429 (demasiados pedidos), 5xx (problemas del servidor), una conexión que se corta.

    Tu código, reintentando. Espera un poco y vuelve a llamar, un poco más cada vez. El modelo nunca se entera: no hubo ningún turno que leer.

  • Nadie puede arreglarlo

    Un 401 (API key incorrecta), un bug en tu código, el usuario canceló.

    Nadie. Deja que lance la excepción. Reintentar no va a servir, y ocultárselo al modelo solo lo hace adivinar.

El truco es no mezclarlas. Reintentar un 400 vuelve a mandar el mismo pedido roto. Mostrarle un 503 al modelo gasta un turno en algo que no puede arreglar. Y atrapar un bug de tu propio código solo lo esconde.

El código

Con astorlm: Las herramientas simplemente lanzan excepciones, y astorlm se encarga de atraparlas. Tú activas retry para el servidor del modelo y escuchas el EventBus para ver las dos capas en acción.

Desde cero: El bucle del nivel 2, con una función por capa: callModel reintenta contra el servidor con backoff, runTool convierte cada falla de herramienta en texto que el modelo puede leer, y todo lo demás se deja lanzar. Encima de eso, un pequeño contador se rinde cuando el modelo sigue chocando contra la misma pared.

import { OpenAIProvider, createLocalAgent, tool } from 'astorlm'
import { z } from 'zod'

const room = { lockOiled: false, doorOpen: false }

const use = tool({
  name: 'use',
  description: 'Use an inventory item with something in the room.',
  schema: z.object({ item: z.enum(['key', 'oil_can']), target: z.string() }),
  execute: async ({ item, target }) => {
    if (item === 'oil_can' && target === 'lock') {
      room.lockOiled = true
      return 'You oil the lock. It looks like it might turn now.'
    }
    if (item === 'key' && target === 'door') {
      // Just throw. astorlm catches it and sends the message back to the model, marked is_error.
      if (!room.lockOiled) throw new Error('The lock is rusted shut and the key won’t turn. Oil it first: use oil_can with lock.')
      room.doorOpen = true
      return 'Click! The key turns and the door swings open.'
    }
    throw new Error(`Nothing happens when you use ${item} with ${target}.`)
  },
})

const agent = await createLocalAgent({
  // Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
  provider: new OpenAIProvider({
    baseURL: 'http://localhost:11434/v1', // e.g. Ollama's default address
    model: 'your-model', // e.g. 'llama3.1', 'gpt-4o-mini'
    apiKey: 'YOUR_API_KEY', // local servers usually ignore it
  }),
  tools: [lookAt, use],
  // Off by default: without it, a single 429 ends the run.
  retry: { maxAttempts: 3, baseDelayMs: 1000 }, // waits up to 1s, then up to 2s
  maxTurns: 10, // also the ceiling for a model stuck on the same wrong move
})

// Watch both layers as they happen.
agent.on('tool-end', ({ name, output, isError }) => {
  if (isError) console.warn(`${name} failed, and the model will read why: ${output}`)
})
agent.on('event', (event) => {
  if (event.type === 'provider_retry') {
    console.warn(`Model call failed. Attempt ${event.attempt + 1}/${event.maxAttempts} in ${event.delayMs}ms`)
  }
})

try {
  const last = await agent.run('Open the temple door.')
  console.log(last.content)
} catch (err) {
  // Only what nobody could handle gets here: retries used up, a 401, a bug in your code.
  console.error('The run failed:', err)
}

Qué vigilar

  • Escribe los errores para el modelo. Di qué estuvo mal y qué hacer en cambio: "la cerradura está trabada por el óxido. Aceítala primero: usa oil_can con lock". Con "Error 400" vas a recibir la misma llamada otra vez.
  • No filtres lo que el modelo no debería ver. Los stack traces, las rutas de archivos y las cadenas de conexión van a tus logs. El modelo recibe una frase clara.
  • Los errores también pueden entrar en bucle. Un modelo puede intentar el mismo movimiento equivocado hasta que se agote maxTurns. Detente después de unos cuantos errores seguidos, o atrápalo con un hook (próximo nivel).
  • Reintenta con un límite y una espera aleatoria. Unos pocos intentos, un techo que se duplica cada vez y algo de aleatoriedad (jitter) para que cien clientes no vuelvan todos en el mismo segundo.
  • Cuidado al reintentar herramientas que cambian cosas. Si una llamada que mueve dinero o reserva una habitación dio timeout, puede que se haya hecho igual. Verifica antes de hacerla dos veces.