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

Nível 2

O loop do agente

Sozinho, um modelo não consegue fazer nada: ele só gera texto. O loop do agente é o que o transforma num agente. Ele passa o histórico para o modelo, executa as ferramentas que o modelo pede, devolve os resultados e repete até o modelo dizer "pronto".
1/17 Dobras do bandoneón:
  • user
  • assistant
  • tool_result
A mamãe pássaro, o seu app, chama agent.run(). Astor (o loop) toca a pergunta no bandoneón (messages[]) e a leva até o Oráculo (o modelo).

EventBus

O problema

Uma mamãe pássaro pergunta a um modelo "você consegue limpar o forte dos porcos?". O modelo não consegue lançar nada. O máximo que ele pode fazer é responder com um pedido de ferramenta: { name: "launch_red", input: { angle: 40 } }.

Se o seu código faz uma única chamada ao modelo, a conversa termina ali. Você fica com um pedido que ninguém executou e sem resposta. Se você executa a ferramenta na mão, bate no mesmo problema na rodada seguinte, porque o modelo pode precisar de outra ferramenta, e depois de mais outra.

A solução

Um loop com uma única regra de saída:

  1. Envie ao modelo o histórico inteiro mais a lista de ferramentas disponíveis.
  2. Se a resposta terminar com stopReason: "end_turn", devolva-a. Essa é a única saída normal.
  3. Se terminar com "tool_use", execute cada ferramenta pedida, acrescente os resultados ao histórico como blocos tool_result e volte ao passo 1.

O modelo decide o que fazer; o loop é quem faz. Essa divisão é a base de todos os outros padrões: todo o resto (steering, compactação de contexto, subagentes) se encaixa em algum ponto deste loop.

O elenco

O loop contado como uma pequena aventura. Depois que você conhece o elenco, não sobra nada para decifrar.

Astor o loop
Um pequeno tanguero, e o único que se mexe. Leva a pergunta ao Oráculo, corre até o estilingue a cada tiro e traz a resposta de volta para a mamãe pássaro.
O Oráculo Provider
O modelo. Nunca toca no estilingue: só escuta o bandoneón e devolve um bilhete. Laranja se precisa de uma ferramenta, dourado se terminou.
O bandoneón messages[]
O histórico, uma dobra colorida por mensagem. O fole cresce a cada volta, e o Oráculo escuta cada dobra toda vez. São as notas que sobem flutuando até o Oráculo.
O banco ToolRegistry
Um pássaro por ferramenta: launch_red, launch_bomb e um terceiro de que ninguém precisa hoje. Astor lança o que o bilhete indica e mostra o resultado: verde se funcionou.
A mamãe pássaro agent.run()
O seu código. Faz a pergunta e espera.
Rastros, pontuação e pássaros histórico, tokens, maxTurns
Cada tiro deixa seu rastro no céu, do mesmo jeito que o histórico guarda cada resultado. A pontuação são os tokens, e ela pula mais a cada volta porque o histórico inteiro é enviado de novo. Cada volta custa um pássaro da fileira da barra superior, o orçamento de turnos. Os números são ilustrativos.

O painel EventBus mostra os eventos que o loop real emite em cada passo da animação.

O código

Com astorlm: O mesmo loop vive em src/agent/loop.ts, com streaming, novas tentativas, hooks, execução de ferramentas em paralelo e cancelamento. Visto de fora, ele fica assim.

Do zero: Umas 40 linhas contra qualquer endpoint compatível com a OpenAI, sem SDK: fetch puro em TypeScript, a biblioteca padrão em Python. Os três passos acima estão marcados nos comentários. Preencha o bloco LLM do início com o seu próprio endpoint, modelo e chave.

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

// Your game's functions, wrapped as tools: one per bird.
const angle = z.number().min(10).max(80).describe('Launch angle in degrees')

const launchRed = tool({
  name: 'launch_red',
  description: 'Fling the red bird. Good against wood. Returns what fell and how many pigs are left.',
  schema: z.object({ angle }),
  execute: async ({ angle }) => level.fling('red', angle), // your code
})

const launchBomb = tool({
  name: 'launch_bomb',
  description: 'Fling the bomb bird. It explodes on impact: the one to use against stone.',
  schema: z.object({ angle }),
  execute: async ({ angle }) => level.fling('bomb', angle), // your code
})

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: [launchRed, launchBomb],
  maxTurns: 10, // the birds in line: a cap on the laps
})

agent.on('tool-start', (tool) => console.log('→', tool.name, tool.input))

const answer = await agent.run('The pigs took our eggs! Can you clear their fort?')

Repare que um erro de ferramenta não para o loop: ele volta para o modelo como texto, para que ele possa se corrigir na rodada seguinte.

Quando usar, e o que observar

Sempre que o modelo precisar agir (ler, buscar, executar algo) antes de poder responder. Se você só precisa transformar texto, uma única chamada basta, e sai mais barato.

  • O custo cresce a cada volta. Observe o bandoneón e o contador de tokens: o Oráculo escuta cada dobra a cada turno, então cada volta reenvia tudo o que veio antes. Com turnos suficientes, o contexto enche.
  • Sempre defina maxTurns. Um modelo confuso pode continuar pedindo ferramentas para sempre. Sem um limite, o loop também roda para sempre.