> Nível 3 de Agent Harness Patterns, uma trilha de padrões sobre como funcionam os agentes de IA. Versão web: https://harnesspatterns.dev/pt/patterns/designing-a-tool · Todos os padrões (em inglês): https://harnesspatterns.dev/llms.txt

# Projetando uma ferramenta

O modelo nunca vê o código da sua ferramenta. Ele vê um nome, uma descrição e um schema, e só com isso decide se usa a ferramenta e o que passar para ela. Para o modelo, isso é a ferramenta inteira.

## O problema

Quando você escreve uma função, sabe o que ela faz. O modelo não: tudo o que ele recebe é a placa na porta da Oficina. Se a placa é vaga, o modelo chuta. Um chute errado custa um turno, um erro e mais tokens no histórico. Pior: uma ferramenta vaga pode "dar certo" com um resultado inútil sem que ninguém perceba.

Na batalha, uma criatura selvagem bloqueia o caminho e o treinador pergunta qual parceiro mandar. O Oráculo tem quatro ferramentas, e o menu de golpes mostra exatamente o que o modelo recebe de cada uma: um nome, os parâmetros e uma descrição. Isso basta para pular `do_stuff`, para pular `heal_party` porque a descrição diz "nunca em batalha", para saber que primeiro é preciso descobrir o tipo da criatura, e para mandar a `check_matchup` a entrada no formato certo na primeira tentativa.

## Anatomia de uma boa ferramenta

- Nome
   Em vez de `do_stuff`, use identify_wild, check_matchup
   Um verbo e um substantivo específicos dizem ao modelo o que a ferramenta faz antes que ele leia qualquer outra coisa.
- Descrição
   Em vez de `"Does stuff."`, use O que ela faz, quando usar e o que ela devolve.
   É a única documentação que o modelo recebe. Escreva para alguém que não consegue ler o seu código.
- Parâmetros
   Em vez de `x: string`, use attack e defend, cada um com uma descrição, e o formato explícito: um tipo em minúsculas, como water.
   Marque o que é obrigatório e proíba extras, para que uma entrada errada falhe rápido em vez de fazer algo estranho.
- Saída
   Em vez de `A tabela de tipos inteira, 18 tipos por 18`, use Uma linha: grass vs water: 2x, super effective.
   Tudo o que uma ferramenta devolve vai para o histórico, e o modelo lê de novo a cada turno.
- Erros
   Em vez de `"Invalid input"`, use "defend must be one lowercase type, like water. Call identify_wild to get it."
   Um erro que o modelo consegue entender é um erro que ele consegue corrigir no turno seguinte.

Mais uma regra: **menos ferramentas, e mais afiadas.** Cada ferramenta que você adiciona é mais uma placa que o modelo lê a cada turno, e mais um jeito de escolher a errada.

## O código

**Com astorlm:** `tool()` recebe um schema Zod, transforma-o no JSON Schema que o modelo lê e valida a entrada do modelo contra ele antes de o seu código rodar. Se a entrada não bate, o modelo recebe um erro claro em vez de a sua função quebrar.

**Do zero:** Uma ferramenta é um JSON Schema que o modelo lê mais uma função que o modelo nunca vê. Aqui estão o `do_stuff` do Crooky e as duas ferramentas que o Oráculo usou na batalha, lado a lado. Só os schemas viajam até o modelo, no campo `tools` de cada requisição do nível 2.

**Com astorlm**

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

const type = z
  .string()
  .regex(/^[a-z]+$/, 'a type is one lowercase word, like water; call identify_wild to get the wild one')

// Describe the input once with Zod: astorlm turns it into the JSON Schema the model
// reads, and checks the model's input against it before running your code.
const checkMatchup = tool({
  name: 'check_matchup',
  description:
    'How hard one type hits another. Use it to choose which partner to send into a battle. ' +
    'Returns one line: the multiplier (2x, 1x or 0.5x) and what it means.',
  schema: z.object({
    attack: type.describe('The attacking type, one lowercase word, e.g. "grass".'),
    defend: type.describe('The defending type, one lowercase word, e.g. "water".'),
  }),
  execute: async ({ attack, defend }) => {
    const times = typeChart[attack]?.[defend] ?? 1 // your code; the model never sees it
    return `${attack} vs ${defend}: ${times}x${times > 1 ? ', super effective' : ''}`
  },
})

const identifyWild = tool({
  name: 'identify_wild',
  description: 'Identify the wild creature you are facing. Returns its name and type.',
  schema: z.object({}),
  execute: async () => {
    const wild = battle.opponent() // your code
    return `${wild.name} · type: ${wild.type}`
  },
})

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: [checkMatchup, identifyWild],
})

await agent.run('A wild creature appeared! Should I send Emberpup (fire) or Sproutle (grass)?')
```

**TypeScript**

```ts
// A tool is two things: a description the model reads, and code the model never sees.

// ✗ Crooky's tool. The model has to guess what it does and what x means.
const doStuffSchema = {
  type: 'function',
  function: {
    name: 'do_stuff',
    description: 'Does stuff.',
    parameters: { type: 'object', properties: { x: { type: 'string' } } },
  },
}

// ✓ Named for what it does, described for someone who can't read the code.
const checkMatchupSchema = {
  type: 'function',
  function: {
    name: 'check_matchup',
    description:
      'How hard one type hits another. ' + // what it does
      'Use it to choose which partner to send into a battle. ' + // when to use it
      'Returns one line: the multiplier (2x, 1x or 0.5x) and what it means.', // what comes back
    parameters: {
      type: 'object',
      properties: {
        attack: { type: 'string', description: 'The attacking type, one lowercase word, e.g. "grass".' },
        defend: { type: 'string', description: 'The defending type, one lowercase word, e.g. "water".' },
      },
      required: ['attack', 'defend'],
      additionalProperties: false,
    },
  },
}

// ✓ A small helper tool, so the model never has to guess what it is facing.
const identifyWildSchema = {
  type: 'function',
  function: {
    name: 'identify_wild',
    description: 'Identify the wild creature you are facing. Returns its name and type.',
    parameters: { type: 'object', properties: {}, additionalProperties: false },
  },
}

// The code behind the names. In a real game, these read the battle state.
export function identifyWild(): string {
  const wild = battle.opponent()
  return `${wild.name} · type: ${wild.type}`
}

export function checkMatchup({ attack, defend }: { attack: string; defend: string }): string {
  // An error the model can fix on its next turn: what went wrong, and what to send instead.
  for (const value of [attack, defend]) {
    if (!/^[a-z]+$/.test(value)) {
      return `Error: a type is one lowercase word, like water. Got "${value}". Call identify_wild to get the wild one.`
    }
  }
  const times = typeChart[attack]?.[defend] ?? 1
  // Short, on-topic output: every character here goes into the history, and gets read every turn.
  return `${attack} vs ${defend}: ${times}x${times > 1 ? ', super effective' : ''}`
}

// Only the schemas travel to the model, in the `tools` field of each request.
export const toolSchemas = [checkMatchupSchema, identifyWildSchema]
```

**Python**

```python
# A tool is two things: a description the model reads, and code the model never sees.
import re

# ✗ Crooky's tool. The model has to guess what it does and what x means.
DO_STUFF_SCHEMA = {
    "type": "function",
    "function": {
        "name": "do_stuff",
        "description": "Does stuff.",
        "parameters": {"type": "object", "properties": {"x": {"type": "string"}}},
    },
}

# ✓ Named for what it does, described for someone who can't read the code.
CHECK_MATCHUP_SCHEMA = {
    "type": "function",
    "function": {
        "name": "check_matchup",
        "description": (
            "How hard one type hits another. "  # what it does
            "Use it to choose which partner to send into a battle. "  # when to use it
            "Returns one line: the multiplier (2x, 1x or 0.5x) and what it means."  # what comes back
        ),
        "parameters": {
            "type": "object",
            "properties": {
                "attack": {"type": "string", "description": 'The attacking type, one lowercase word, e.g. "grass".'},
                "defend": {"type": "string", "description": 'The defending type, one lowercase word, e.g. "water".'},
            },
            "required": ["attack", "defend"],
            "additionalProperties": False,
        },
    },
}

# ✓ A small helper tool, so the model never has to guess what it is facing.
IDENTIFY_WILD_SCHEMA = {
    "type": "function",
    "function": {
        "name": "identify_wild",
        "description": "Identify the wild creature you are facing. Returns its name and type.",
        "parameters": {"type": "object", "properties": {}, "additionalProperties": False},
    },
}

# The code behind the names. In a real game, these read the battle state.
def identify_wild():
    wild = battle.opponent()
    return f"{wild['name']} · type: {wild['type']}"

def check_matchup(attack, defend):
    # An error the model can fix on its next turn: what went wrong, and what to send instead.
    for value in (attack, defend):
        if not re.fullmatch(r"[a-z]+", value):
            return f'Error: a type is one lowercase word, like water. Got "{value}". Call identify_wild to get the wild one.'
    times = TYPE_CHART.get(attack, {}).get(defend, 1)
    # Short, on-topic output: every character here goes into the history, and gets read every turn.
    return f"{attack} vs {defend}: {times}x" + (", super effective" if times > 1 else "")

# Only the schemas travel to the model, in the `tools` field of each request.
TOOL_SCHEMAS = [CHECK_MATCHUP_SCHEMA, IDENTIFY_WILD_SCHEMA]
```

## Confira suas ferramentas como o modelo faria

- **Leia só o schema.** Esconda o código e se pergunte: eu saberia quando chamar isto, e o que passar?
- **Observe as primeiras chamadas.** Se o modelo continua mandando a entrada errada, a correção quase sempre está na descrição, não no prompt.
- **Meça a saída.** Uma ferramenta que devolve a tabela de tipos inteira quando uma linha bastaria enche o histórico rapidinho.

## Padrões relacionados

- [5 · Erros no loop](https://harnesspatterns.dev/pt/patterns/errors-in-the-loop.md)
- [8 · Skills sob demanda](https://harnesspatterns.dev/pt/patterns/skills.md)
- [14 · Segurança e sandboxing](https://harnesspatterns.dev/pt/patterns/security.md)
