Pular para o conteúdo
astorlm
Idioma: Português
← Mapa

Nível 5

Erros no loop

Ferramentas falham. Modelos mandam entradas ruins. Servidores caem por um segundo. Um loop de agente precisa decidir, para cada falha, quem cuida dela: o modelo, o seu código ou ninguém.
1/40 Dobras do bandoneón:
  • user
  • assistant
  • tool_result
  • tool_result (erro)
Um agente jogando um jogo de aventura: as ferramentas dele são os verbos lá embaixo, LOOK AT e USE. Este loop foi escrito na mão, e ele executa as ferramentas sem try/catch.

EventBus

O problema

Um agente joga um jogo de aventura. As ferramentas dele são os verbos na parte de baixo da tela, look_at e use, e a tarefa é "abra a porta do templo". O modelo faz o óbvio: usar a chave na porta. Mas a fechadura está enferrujada, e a ferramenta lança uma exceção.

Os jogos de aventura antigos se dividiam em duas escolas diante de momentos assim. Em alguns, um movimento errado te matava: game over, de volta ao último save. Em outros, você não podia morrer; o jogo só dizia por que aquilo não funcionava, e você tentava outra coisa. Um loop de agente também precisa escolher uma escola.

Se o loop não captura o erro, o Krash vence: a exceção sobe pelo loop e o run() a lança. A execução se perde, e o erro dizia exatamente o que fazer: "lubrifique primeiro". O único leitor que podia usar essa mensagem nunca a recebeu. Capture-o, devolva-o como resultado de ferramenta, e o modelo lê e tenta de novo. Isso custa um turno. Não capturar custa a execução inteira.

Três tipos de falha

  • O modelo pediu errado

    Entrada inválida, JSON quebrado, uma ferramenta que não existe, uma chave que não gira.

    O modelo. O erro volta como um resultado de ferramenta marcado como erro. O modelo lê e tenta outra coisa no turno seguinte.

  • O servidor do modelo falhou

    429 (requisições demais), 5xx (problema no servidor), uma conexão que caiu.

    O seu código, tentando de novo. Espere um pouco e chame de novo, um pouco mais a cada vez. O modelo nunca fica sabendo: não houve turno para ler.

  • Ninguém consegue resolver

    Um 401 (API key errada), um bug no seu código, o usuário cancelou.

    Ninguém. Deixe a exceção subir. Tentar de novo não vai ajudar, e esconder isso do modelo só o faz chutar.

O segredo é não misturá-las. Tentar de novo um 400 manda a mesma requisição quebrada outra vez. Mostrar um 503 ao modelo gasta um turno com algo que ele não pode resolver. E capturar um bug do seu próprio código só o esconde.

O código

Com astorlm: As ferramentas simplesmente lançam exceções, e o astorlm faz a captura. Você liga o retry para o servidor do modelo e escuta o EventBus para ver as duas camadas em ação.

Do zero: O loop do nível 2, com uma função por camada: callModel tenta o servidor de novo com backoff, runTool transforma cada falha de ferramenta em texto que o modelo consegue ler, e todo o resto fica livre para lançar. Por cima disso, um pequeno contador desiste quando o modelo continua batendo na mesma parede.

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)
}

O que observar

  • Escreva os erros para o modelo. Diga o que estava errado e o que fazer no lugar: "a fechadura está emperrada de ferrugem. Lubrifique primeiro: use oil_can com lock". Com "Error 400", você recebe a mesma chamada de novo.
  • Não vaze o que o modelo não deveria ver. Stack traces, caminhos de arquivo e strings de conexão vão para os seus logs. O modelo recebe uma frase clara.
  • Erros também podem entrar em loop. Um modelo pode tentar o mesmo movimento errado até o maxTurns acabar. Pare depois de alguns erros seguidos, ou capture isso com um hook (próximo nível).
  • Tente de novo com limite e espera aleatória. Poucas tentativas, um teto que dobra a cada vez e um pouco de aleatoriedade (jitter) para que cem clientes não voltem todos no mesmo segundo.
  • Cuidado ao repetir ferramentas que mudam coisas. Se uma chamada que movimenta dinheiro ou reserva um quarto deu timeout, ela pode ter passado mesmo assim. Confira antes de fazer duas vezes.