Pular para o conteúdo

O que são Tools?

Mapa da Unidade 1

O que distingue um Agent de um chat é a capacidade de agir. E ele age através de Tools.

Nesta seção vamos ver o que é uma Tool, como projetá-la bem e como ela chega até o modelo. É o capítulo mais importante da unidade: a diferença entre um Agent que serve para alguma coisa e um que não serve está quase inteiramente aqui.

O que é uma Tool

Uma Tool é uma função entregue ao LLM, com um objetivo claro e delimitado.

Exemplos comuns em agentes genéricos:

Tool Para quê
Busca na web Buscar informação atualizada na internet
Geração de imagem Criar imagens a partir de descrição textual
Retrieval Recuperar informação de uma base documental
Interface de API Falar com um sistema externo

E, no nosso caso:

Tool Para quê
consultar_assunto Traduzir "violência doméstica" para o código de assunto
buscar_processos Consultar a base e devolver os processos mais recentes daquele assunto
detalhar_processo Trazer classe, vara, comarca e último movimento de um processo

Uma boa Tool complementa o LLM: ela faz o que o modelo não faz bem.

Dois motivos práticos para isso importar:

  1. O modelo é ruim em coisas exatas. Aritmética, datas, contagem. Uma Tool de cálculo dá resultado melhor do que confiar na capacidade nativa dele.
  2. O modelo não sabe nada depois do treinamento. O conhecimento interno dele para em uma certa data. Um processo distribuído ontem não existe para ele — a não ser que uma Tool vá buscar.

Previsão do tempo

Se você perguntar a um LLM sem Tool de busca qual a previsão do tempo hoje, ele não vai dizer que não sabe. Vai inventar uma previsão plausível. Vale reler essa frase substituindo "previsão do tempo" por "processos sobre violência doméstica".

Uma Tool precisa ter:

Como as Tools funcionam de verdade

Aqui está o ponto que costuma surpreender.

O LLM não executa nada. Ele não tem como. Ele recebe texto e produz texto — só isso, sem exceção.

Quando dizemos que "demos uma Tool ao Agent", o que fizemos foi:

  1. Descrever a Tool em texto, dentro da system message.
  2. Instruir o modelo a escrever, quando julgar necessário, um texto em um formato combinado que representa uma chamada.
  3. Fazer com que o programa em volta — o Agent — leia esse texto, reconheça a chamada, execute a função de verdade, e devolva o resultado como uma nova mensagem.

Ou seja: quando você pergunta "qual o tempo em Paris?", o modelo não busca nada. Ele gera o texto chamar_tempo("Paris"). Quem executa é o seu código Python. O resultado volta para a conversa, o modelo lê e redige a resposta final.

Essas etapas normalmente não são mostradas ao usuário. Da cadeira dele, parece que o modelo consultou o tempo. Na verdade, o modelo escreveu uma frase e um programa fez o trabalho.

Nota

Essa separação é a melhor notícia do curso, do ponto de vista institucional.

Todo o poder de execução está no seu código, não no modelo. O modelo propõe; o seu programa dispõe. Se a sua função buscar_processos filtra por nível de sigilo, não existe frase que o modelo possa gerar que contorne esse filtro — porque não é ele quem consulta o banco.

Isso é o oposto de dar SQL livre ao modelo, e é a razão de fazermos assim.

Como as Tools chegam ao modelo

Pela system message, em texto.

System prompt com Tools

Para funcionar, a descrição precisa ser precisa em dois pontos:

  1. O que a Tool faz
  2. Exatamente que entradas ela espera

Por isso as descrições costumam usar formatos estruturados — JSON, assinaturas de função. Não é obrigatório: qualquer formato preciso e consistente serve. Mas ambiguidade aqui vira chamada errada lá na frente.

Você já viu isso, no capítulo anterior

Lembra do template do qwen2? Aquele bloco que passamos rápido:

{{- if .Tools }}

# Tools {: #tools }
You are provided with function signatures within <tools></tools> XML tags:
<tools>{{- range .Tools }}
{"type": "function", "function": {{ .Function }}}{{- end }}
</tools>

For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
<tool_call>
{"name": <function-name>, "arguments": <args-json-object>}
</tool_call>
{{- end }}<|im_end|>

Aí está, na íntegra, o mecanismo inteiro:

Nenhuma API secreta. Nenhum canal privilegiado. Instruções em linguagem natural, dentro do prompt. É por isso que a qualidade da sua descrição importa tanto: ela é literalmente o que o modelo lê antes de decidir.

Construindo uma Tool do zero

Vamos fazer a nossa primeira Tool sem framework nenhum, para não sobrar mágica. Ela converte o nome de um assunto no código correspondente — o passo 1 do plano do Alfred.

ASSUNTOS = {
    "violência doméstica": 1001,
    "tráfico de drogas": 1002,
    "furto": 1003,
    "execução fiscal": 2001,
    "guarda de menor": 3001,
}

def consultar_assunto(termo: str) -> int:
    """Converte o nome de um assunto judicial no código numérico correspondente."""
    return ASSUNTOS.get(termo.lower().strip(), -1)

É isso. Uma função comum.

Agora, o que o modelo precisa saber sobre ela? Nome, descrição, entradas, saída:

Tool Name: consultar_assunto, Description: Converte o nome de um assunto judicial no código numérico correspondente., Arguments: termo: str, Outputs: int

Essa string é tudo o que o LLM vai saber a respeito. Ele nunca vê o corpo da função.

Gerando a descrição automaticamente

Escrever essa string à mão para cada Tool é frágil — basta esquecer um argumento e o modelo passa a chamar errado. Mas repare: a informação já está toda no código.

Dá para extrair isso com o módulo inspect. É o que um decorator @tool faz:

@tool
def consultar_assunto(termo: str) -> int:
    """Converte o nome de um assunto judicial no código numérico correspondente."""
    return ASSUNTOS.get(termo.lower().strip(), -1)

print(consultar_assunto.to_string())

Rode o exemplo completo:

python exemplos\tool_do_zero.py

Saída:

Descrição que o LLM recebe no prompt:

   Tool Name: consultar_assunto, Description: Converte o nome de um assunto judicial no código numérico correspondente., Arguments: termo: str, Outputs: int

Chamando a Tool como função normal:

   consultar_assunto('violência doméstica') -> 1001
   consultar_assunto('assunto inexistente') -> -1

Exatamente a mesma string que escrevemos à mão — só que agora ela não pode divergir do código, porque foi extraída dele.

Atenção

Consequência que vale destacar: em uma Tool, a docstring e os type hints deixam de ser boa prática e viram parte do contrato.

Se a docstring estiver vaga, o modelo chama a Tool na hora errada. Se estiver errada, ele chama com os argumentos errados. Você não está escrevendo comentário para um colega — está escrevendo a especificação que a máquina vai ler.

Regra prática: a descrição deve dizer quando usar a Tool, não só o que ela faz.

Código do decorator (clique para abrir)
import inspect

def tool(func):
    """Decorator que transforma uma função comum em uma Tool."""
    assinatura = inspect.signature(func)

    argumentos = []
    for parametro in assinatura.parameters.values():
        anotacao = parametro.annotation
        nome_tipo = getattr(anotacao, "__name__", str(anotacao))
        argumentos.append((parametro.name, nome_tipo))

    retorno = assinatura.return_annotation
    if retorno is inspect.Signature.empty:
        saida = "sem anotação de retorno"
    else:
        saida = getattr(retorno, "__name__", str(retorno))

    return Tool(
        nome=func.__name__,
        descricao=(func.__doc__ or "Sem descrição.").strip().split("\n")[0],
        func=func,
        argumentos=argumentos,
        saida=saida,
    )
A classe `Tool` que ele instancia está inteira em `exemplos/tool_do_zero.py`. Ela guarda nome, descrição, função, argumentos e saída, expõe `to_string()` para gerar a descrição e implementa `__call__` para que a instância possa ser usada como a função original.

Na prática, você não vai escrever esse decorator: smolagents, LangGraph e LlamaIndex já trazem o deles. Mas agora você sabe o que eles fazem — e por que reclamam quando falta um type hint.

Projetando Tools para uso judicial

O curso original para por aqui. Nós temos três decisões a mais para tomar, e elas não são detalhe de implementação.

1. Uma Tool por intenção, não uma Tool genérica

É tentador escrever uma única Tool poderosa:

# NÃO faça isso {: #não-faça-isso }
@tool
def executar_sql(consulta: str) -> list:
    """Executa uma consulta SQL na base de processos."""

Isso entrega ao modelo tudo o que a credencial alcança, contorna qualquer regra de sigilo que você tenha escrito em outro lugar, e produz um comportamento que ninguém consegue auditar depois. Além disso, a consulta passa a ser um texto gerado por modelo — uma origem sobre a qual você tem ainda menos controle do que sobre entrada de usuário.

O caminho certo é o contrário: Tools estreitas, com parâmetros tipados, e o SQL escrito por você.

@tool
def buscar_processos(codigo_assunto: int, limite: int = 5) -> list:
    """Lista os processos mais recentes de um assunto, do mais novo para o mais antigo.
    Use depois de obter o código com consultar_assunto."""

O modelo escolhe qual Tool chamar e com quais valores. Nunca a consulta.

2. O nível de acesso não é argumento

Nenhuma Tool deve receber do modelo o teto de sigilo. Esse valor vem da sessão de quem está operando, fora do alcance do LLM:

def buscar_processos(codigo_assunto: int, limite: int = 5) -> list:
    teto = sessao.nivel_de_acesso        # não vem do modelo
    cursor.execute(
        "SELECT ... WHERE a.codigo = ? AND p.id_nivel_sigilo <= ? LIMIT ?",
        (codigo_assunto, teto, limite),
    )

Repare também nos ?: valores entram como parâmetros, nunca concatenados na string.

3. Sempre um limite

O modelo pode pedir "todos os processos". Se a sua Tool obedecer, você trava a máquina e enche a janela de contexto com dados que ninguém vai ler. Toda consulta tem LIMIT, com um padrão razoável.

Nota

Esses três pontos, e o restante do que muda ao apontar para a base real do Tribunal, estão detalhados em dados/MAPEAMENTO.md, junto com o mapa de conceitos para preencher com os nomes das tabelas do seu sistema.

Model Context Protocol (MCP)

Para conhecimento: o MCP é um protocolo aberto que padroniza como aplicações oferecem Tools a LLMs. Ele traz integrações prontas, permite trocar de provedor de modelo sem reescrever as Tools, e define práticas de segurança.

Na prática, significa que uma Tool escrita para MCP funciona em qualquer framework que implemente o protocolo. Se interessar, a Hugging Face tem um curso gratuito de MCP.


Resumindo:

Antes de seguir, um quiz curto. Depois, vamos juntar tudo no ciclo do Agent: Thought → Action → Observation.