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

Nível 4

Quando parar

Um loop de agente continua enquanto o modelo pedir ferramentas. Há três saídas: o modelo diz que terminou, uma ferramenta que você marcou fecha o loop, ou o orçamento de turnos acaba. Só as duas primeiras te dão uma resposta.
1/42 Dobras do bandoneón:
  • user
  • assistant
  • tool_result
World 1-1. Este nível não tem estrela nenhuma, mas o modelo vai continuar procurando mesmo assim. O relógio é o maxTurns: 3 turnos para esta execução.

EventBus

O problema

O loop do nível 2 tem uma saída normal: o modelo responde sem pedir uma ferramenta. Mas um modelo que não acha o que procura raramente admite isso. Ele tenta outra busca, depois outra grafia, depois mais outra. Cada volta reenvia o histórico inteiro, então cada volta custa mais que a anterior.

Esse é o Loopboros, o loop que nunca termina. maxTurns o detém. Mas olhe de novo o mundo 1-1: quando o relógio acaba, o loop não falha. Ele para e devolve a última mensagem, e essa mensagem é um pedido de busca. Nem resposta, nem erro.

As três saídas

  • A bandeira

    end_turn

    World 1-2

    O modelo decide. Ele responde sem pedir nenhuma ferramenta. Essa é a saída normal, e a maioria das execuções deveria terminar aqui.

    Devolve: Uma mensagem com a resposta em texto.

  • O cano de atalho

    stopOnToolNames

    World 1-3

    O seu código decide, quando o modelo chama uma ferramenta que você marcou. O loop se fecha logo depois que essa ferramenta roda sem erro. Se ela falhar, o erro volta para o modelo e o loop continua.

    Devolve: Uma mensagem com a chamada da ferramenta. A entrada dela é a resposta, já validada contra o schema.

  • O relógio

    maxTurns

    World 1-1

    Ninguém decide. O orçamento acaba. O loop para depois desse número de turnos, seja lá o que o modelo estivesse fazendo. É uma rede de segurança, não uma resposta.

    Devolve: O que quer que tenha sido a última mensagem. Muitas vezes, uma chamada de ferramenta.

Uma ferramenta terminal não economiza um turno em relação ao end_turn. O que ela te dá é a resposta como dado, validada por um schema, pronta para o seu app usar: aqui, um marcador no mapa da fase. Sem stopOnToolNames, depois que mark_star rodasse, o loop perguntaria ao modelo de novo só para ele dizer "pronto".

O código

Com astorlm: maxTurns define o relógio e stopOnToolNames marca os canos de atalho. run() sempre devolve a última mensagem, então leia-a para descobrir por qual saída a execução terminou.

Do zero: O loop do nível 2 com as três saídas marcadas. Em vez de uma string pura, ele devolve como a execução terminou, para quem chama não confundir um timeout com uma resposta.

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

const searchBlocks = tool({
  name: 'search_blocks',
  description: 'Search the ? blocks in one area of the level. Returns what each one hides.',
  schema: z.object({ area: z.string() }),
  execute: async ({ area }) => level.search(area), // your code
})

// The answer as data. Its schema is checked before the loop is allowed to stop.
const markStar = tool({
  name: 'mark_star',
  description: 'Put a marker on the level map where the star is. Call it once, at the end.',
  schema: z.object({ item: z.literal('star'), block: z.number().int().positive() }),
  execute: async ({ block }) => map.addMarker(block), // 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: [searchBlocks, markStar],
  appendSystemPrompt:
    'When you know where the star is, deliver it with mark_star. ' +
    "If a few searches come back empty, say you couldn't find it.",
  maxTurns: 10, // the clock. Without it, the default is 25.
  stopOnToolNames: ['mark_star'], // the warp pipe
})

const last = await agent.run('Is there a hidden star in this level?')

// The loop hands back its last message whichever way it ended. Read it to find out which.
const call = last.content.find((block) => block.type === 'tool_use')
if (!call) {
  console.log('end_turn:', last.content) // the flag: a plain text answer
} else if (call.name === 'mark_star') {
  console.log('answer:', call.input) // the warp pipe: { item, block }, schema-checked
} else {
  // The clock: maxTurns ran out while the model was still asking for tools.
  throw new Error(`No answer: the run stopped while asking for ${call.name}`)
}

O que observar

  • Sempre defina maxTurns, e dimensione-o para a tarefa. Uma consulta precisa de um punhado de turnos; um refactor pode precisar de dezenas. O padrão no astorlm é 25.
  • Trate o relógio como uma falha. Se a execução terminou com uma chamada de ferramenta, diga ao usuário que não deu para terminar, ou tente de novo com um prompt mais claro. Não mostre uma resposta vazia.
  • Dê ao modelo um jeito de desistir. Diga no system prompt para ele responder "não encontrei" quando uma busca continuar voltando vazia. Um modelo que tem permissão para parar, para mais cedo.
  • Turnos não são tempo. maxTurns não ajuda com um único turno que nunca termina. Para isso você precisa de um timeout ou de um AbortSignal.