MÓDULO 4.1 GA

🛠️ Tool/function calling provider-neutral

Como o modelo invoca funções estruturadas. JSON Schema como contrato, tratamento de erro, sandbox obrigatório.

6
Tópicos
65
Minutos
Intermediário
Nível
Hands-on
Tipo

Tool calling transforma o LLM em orquestrador de funções. O modelo decide qual chamar, com quais argumentos, e usa o resultado para continuar. Aqui você aprende a desenhar tools robustos — JSON Schema bem definido, erro tipado, sandbox obrigatório.

🛠️ Tool é interface: schema + executor

Cada tool tem dois lados: o schema que o LLM lê, e o executor que roda quando chamado. Ambos vivem juntos.

💡 Description é prompt — itere

Comece com descrição básica. Em casos onde o modelo escolhe errado, refine a descrição (não a lógica do tool). Eval no harness mostra impacto.

1

📋 JSON Schema: o contrato do tool

Tipo, descrição, validação

JSON Schema é o contrato declarativo entre você e o modelo. Define o que cada parâmetro é, se é obrigatório, qual o domínio de valores. Modelos modernos validam o schema antes de retornar — você quase nunca recebe args malformados. Schema bom = chamada confiável; schema vago = surpresa em produção.

schema bem-tipado para tool de busca
{
  'name': 'buscar_em_corpus',
  'description': 'Busca chunks relevantes em um corpus indexado.',
  'parameters': {
    'type': 'object',
    'required': ['query', 'top_k'],
    'properties': {
      'query': {'type': 'string', 'minLength': 3, 'maxLength': 500},
      'top_k': {'type': 'integer', 'minimum': 1, 'maximum': 20, 'default': 5},
      'filtros': {
        'type': 'object',
        'properties': {
          'ano_min': {'type': 'integer'},
          'idioma': {'type': 'string', 'enum': ['pt', 'en']},
        }
      }
    }
  }
}
📑 Resumo navegável
O que é: Cada tool tem schema declarativo: nome, descrição, parâmetros tipados (string, number, enum, etc.) com required.
Por que aprender: Schema bom = chamada confiável. Modelo usa descrição para escolher e schema para formatar argumentos.
Conceitos-chave: JSON Schema 2020-12, parameter validation, required fields, oneOf.
2

✏️ Descrição do tool: o prompt do prompt

Onde a magia acontece

Description é o prompt do tool. O modelo escolhe a tool com base nela. 'busca_web' vs 'busca_web_para_eventos_recentes_e_noticias' muda comportamento drasticamente. Inclua quando usar (and quando NÃO usar), com 1 frase de exemplo. Itere com eval — descrição vaga é causa #1 de tool calling errado.

boa vs ruim
# RUIM (vago)
description = 'Busca informação na web.'

# BOM (when-to-use explícito)
description = '''Busca em um índice local de documentos do curso FEC.
USE quando o usuário fizer pergunta sobre conceitos, definições ou referências do curso.
NÃO USE para perguntas matemáticas (use `calculadora`) ou eventos atuais (não temos cobertura web).
Exemplo: usuário pergunta 'o que é prompt caching?' → use esta tool com query='prompt caching'.'''
📑 Resumo navegável
O que é: Texto livre que descreve quando usar o tool. É lido pelo LLM ao decidir ferramenta.
Por que aprender: Modelos escolhem tool com base na descrição. 'busca_web' vs 'busca_web_para_eventos_recentes_e_notícias' muda comportamento.
Conceitos-chave: Tool description, disambiguation, when-to-use clauses.
3

🚦 Erro tipado: sucesso, retry, abort

Como o tool comunica falha

Tool não pode retornar exception genérica — modelo fica perdido. Padrão: result tipado com {ok: bool, content?, error?, retry_able?: bool}. Modelo lê e decide: retry, fallback, abort. Recovery inteligente vem disso.

result types
@dataclass
class ToolResult:
    ok: bool
    content: Any | None = None
    error: str | None = None
    error_class: str | None = None  # 'rate-limit', 'invalid-input', 'fatal'
    retry_able: bool = False

def buscar(query: str) -> ToolResult:
    try:
        r = retriever.search(query)
        return ToolResult(ok=True, content=r)
    except RateLimitError as e:
        return ToolResult(ok=False, error=str(e), error_class='rate-limit', retry_able=True)
    except InvalidQueryError as e:
        return ToolResult(ok=False, error=str(e), error_class='invalid-input', retry_able=False)
📑 Resumo navegável
O que é: Resultado do tool inclui campo de status. Modelo lê e decide retry, fallback ou parar.
Por que aprender: Sem tipo de erro, modelo tenta de novo cegamente ou desiste. Estrutura permite recovery inteligente.
Conceitos-chave: Result types, ToolError, retry-able vs fatal, exponential backoff.
4

🔒 Sandbox obrigatório (PLAN item 62a)

Filesystem, processo, rede

Sem sandbox, prompt injection consegue fazer um tool de leitura ler ~/.aws/credentials ou .env. FEC exige FilesystemSandbox + NetworkPolicy em todo tool com side-effect. Bateria tests/sandbox/test_traversal.py (19 casos) é gate de GA — falha bloqueia merge.

tool com sandbox
from fec_sdk.sandbox import FilesystemSandbox

with FilesystemSandbox() as fs:
    def ler_arquivo(path: str) -> ToolResult:
        try:
            return ToolResult(ok=True, content=fs.read_text(path))
        except SandboxViolation as e:
            return ToolResult(ok=False, error=str(e), error_class='sandbox',
                              retry_able=False)
📑 Resumo navegável
O que é: Toda tool que toca FS/rede/processo roda dentro de FilesystemSandbox + NetworkPolicy do fec_sdk.
Por que aprender: Prompt injection consegue fazer tool ler ~/.aws/credentials se não houver sandbox. PLAN item 62a é gate de GA.
Conceitos-chave: Path traversal, deny-by-default, allowlist, jail.
5

🔄 Loop: tool → resultado → próximo passo

ReAct simplificado

O loop simples é: modelo decide → tool executa → resultado vira role=tool message → modelo continua. Esse é o building block; agentes de T4.2 são apenas múltiplas iterações desse loop com critério de parada. Cada iteração é um chat completion.

loop básico (1 turn de tool use)
msgs = [Message(role=USER, content=query)]
resp = client.chat(msgs, tools=tools)

if resp.tool_calls:
    for call in resp.tool_calls:
        result = executar_tool(call)
        msgs.append(Message(role=ASSISTANT, content='', tool_calls=resp.tool_calls))
        msgs.append(Message(role=TOOL, content=str(result), name=call.name))
    final = client.chat(msgs)  # modelo gera resposta final
📑 Resumo navegável
O que é: Modelo decide chamar tool → executa → resultado vira mensagem do role 'tool' → modelo continua.
Por que aprender: Esse é o building block de agentes. Um loop simples já resolve muitas tarefas.
Conceitos-chave: Tool result message, role=tool, tool_use_id, conversation continuation.
6

🎚️ Tool choice: forçar ou deixar o modelo decidir

auto, any, specific

tool_choice controla a disposição do modelo: auto (decide), any (deve chamar alguma tool), specific (deve chamar X). Útil em fluxos forçados (extração obrigatória) e em pipelines where você sabe que precisa do tool.

forçar tool específica
# Anthropic
resp = client.messages.create(
    model='claude-sonnet-4-6',
    tools=[extract_entities_tool],
    tool_choice={'type': 'tool', 'name': 'extract_entities'},
    messages=[{'role': 'user', 'content': texto}],
)
# Garantido: resp.tool_calls tem chamada para extract_entities.
📑 Resumo navegável
O que é: Parâmetro que controla quando o modelo pode chamar tool: auto (decide), any (deve chamar algum), specific (deve chamar X).
Por que aprender: Em alguns fluxos você QUER forçar uso de tool (ex.: extract). Em outros, deixar livre é melhor.
Conceitos-chave: tool_choice, parallel tool calls, required tool.

📑 Resumo navegável dos tópicos

1 📋 JSON Schema: o contrato do tool — Tipo, descrição, validação
O que é: Cada tool tem schema declarativo: nome, descrição, parâmetros tipados (string, number, enum, etc.) com required.
Por que aprender: Schema bom = chamada confiável. Modelo usa descrição para escolher e schema para formatar argumentos.
Conceitos-chave: JSON Schema 2020-12, parameter validation, required fields, oneOf.
2 ✏️ Descrição do tool: o prompt do prompt — Onde a magia acontece
O que é: Texto livre que descreve quando usar o tool. É lido pelo LLM ao decidir ferramenta.
Por que aprender: Modelos escolhem tool com base na descrição. 'busca_web' vs 'busca_web_para_eventos_recentes_e_notícias' muda comportamento.
Conceitos-chave: Tool description, disambiguation, when-to-use clauses.
3 🚦 Erro tipado: sucesso, retry, abort — Como o tool comunica falha
O que é: Resultado do tool inclui campo de status. Modelo lê e decide retry, fallback ou parar.
Por que aprender: Sem tipo de erro, modelo tenta de novo cegamente ou desiste. Estrutura permite recovery inteligente.
Conceitos-chave: Result types, ToolError, retry-able vs fatal, exponential backoff.
4 🔒 Sandbox obrigatório (PLAN item 62a) — Filesystem, processo, rede
O que é: Toda tool que toca FS/rede/processo roda dentro de FilesystemSandbox + NetworkPolicy do fec_sdk.
Por que aprender: Prompt injection consegue fazer tool ler ~/.aws/credentials se não houver sandbox. PLAN item 62a é gate de GA.
Conceitos-chave: Path traversal, deny-by-default, allowlist, jail.
5 🔄 Loop: tool → resultado → próximo passo — ReAct simplificado
O que é: Modelo decide chamar tool → executa → resultado vira mensagem do role 'tool' → modelo continua.
Por que aprender: Esse é o building block de agentes. Um loop simples já resolve muitas tarefas.
Conceitos-chave: Tool result message, role=tool, tool_use_id, conversation continuation.
6 🎚️ Tool choice: forçar ou deixar o modelo decidir — auto, any, specific
O que é: Parâmetro que controla quando o modelo pode chamar tool: auto (decide), any (deve chamar algum), specific (deve chamar X).
Por que aprender: Em alguns fluxos você QUER forçar uso de tool (ex.: extract). Em outros, deixar livre é melhor.
Conceitos-chave: tool_choice, parallel tool calls, required tool.

✓ O que FAZER

  • Sandbox obrigatório para tools com side-effect
  • Erro tipado com retry-able vs fatal
  • Description com 'when to use' explícito
  • Validar JSON Schema dos args antes de executar

✗ O que NÃO fazer

  • os.system(arg) direto
  • Levantar exceção genérica
  • Description vaga: 'busca informação'
  • Confiar que o modelo preencheu certo

🚫 Quando NÃO usar

💻 Exemplo de código

from fec_sdk import Message, MessageRole, Tool, ToolResult
from fec_sdk.adapters import get_adapter
from fec_sdk.sandbox import FilesystemSandbox

tool_ler_arquivo = Tool(
    name="ler_arquivo",
    description="Lê arquivo .txt ou .md em path RELATIVO ao sandbox.",
    parameters={
        "type": "object",
        "properties": {"path": {"type": "string"}},
        "required": ["path"],
    }
)

with FilesystemSandbox() as fs:
    fs.write_text("nota.md", "FEC v1.0")

    client = get_adapter("mock")
    resp = client.chat(
        [Message(role=MessageRole.USER, content="Leia nota.md")],
        tools=[tool_ler_arquivo],
    )

    if resp.tool_calls:
        for call in resp.tool_calls:
            try:
                conteudo = fs.read_text(call.arguments["path"])
                resultado = ToolResult(tool_call_id=call.id, content=conteudo)
            except Exception as e:
                resultado = ToolResult(tool_call_id=call.id, content=str(e), is_error=True)

🏋️ Exercício hands-on

Defina 3 tools (ler arquivo, calcular, buscar termo) com sandbox e schema. Bateria tests/sandbox/ deve passar em todos. Em exercicios/modulo-4-1/.

📚 Bibliografia

🎯 Resumo do Módulo

  • Tool = schema + executor; ambos vivem juntos.
  • JSON Schema como contrato; validar args antes de executar.
  • Description é o prompt do prompt — itere com eval.
  • Sandbox obrigatório (PLAN item 62a) para FS/rede/processo.
  • Erro tipado: retry-able vs fatal; modelo recupera melhor.

Próximo Módulo:

Agentes single (ReAct, planner/executor)