MÓDULO 6.2

🔗 Tool Calling com LLMs Locais

Como fazer modelos locais chamarem ferramentas: schemas, fluxo completo, thinking modes, streaming e comparação com APIs cloud.

6
Tópicos
30
Minutos
Avançado
Nível
Prática
Tipo
1

🔧 Como Funciona

Tool calling permite que o LLM "chame" funções no seu código. Você envia a mensagem do usuário junto com uma lista de ferramentas disponíveis (schemas JSON). O modelo analisa a intenção e responde com uma chamada estruturada indicando qual ferramenta usar e com quais argumentos.

Tool Calling com Ollama

response = chat(
    model='qwen3:8b',
    messages=[{'role': 'user', 'content': 'Read /tmp/hello.txt'}],
    tools=[{
        'type': 'function',
        'function': {
            'name': 'read_file',
            'description': 'Read a file from disk',
            'parameters': {
                'type': 'object',
                'properties': {'path': {'type': 'string'}},
                'required': ['path']
            }
        }
    }],
    stream=False,
    options={'num_ctx': 65536}
)
# response.message.tool_calls contém as chamadas

💡 Sempre stream=False para Tools

Quando enviando ferramentas, use stream=False. O modelo precisa gerar o JSON completo da tool call antes que você possa processá-lo. Streaming parcial de JSON é problemático e pode causar erros de parsing.

2

📋 Formato dos Schemas

Cada ferramenta precisa de um schema JSON que descreve seu nome, propósito e parâmetros. O formato segue o padrão OpenAI function calling — o que significa que schemas escritos para GPT-4 funcionam com Qwen3 no Ollama sem alteração.

Schemas Completos — 4 Ferramentas

TOOLS = [
    {
        'type': 'function',
        'function': {
            'name': 'read_file',
            'description': 'Read the full contents of a file given its path',
            'parameters': {
                'type': 'object',
                'properties': {
                    'path': {'type': 'string', 'description': 'Absolute file path'}
                },
                'required': ['path']
            }
        }
    },
    {
        'type': 'function',
        'function': {
            'name': 'write_file',
            'description': 'Write content to a file, creating it if needed',
            'parameters': {
                'type': 'object',
                'properties': {
                    'path': {'type': 'string', 'description': 'File path to write'},
                    'content': {'type': 'string', 'description': 'Content to write'}
                },
                'required': ['path', 'content']
            }
        }
    },
    {
        'type': 'function',
        'function': {
            'name': 'run_shell',
            'description': 'Execute a shell command and return stdout+stderr',
            'parameters': {
                'type': 'object',
                'properties': {
                    'command': {'type': 'string', 'description': 'Shell command'}
                },
                'required': ['command']
            }
        }
    },
    {
        'type': 'function',
        'function': {
            'name': 'search_in_files',
            'description': 'Search for a pattern in files using grep',
            'parameters': {
                'type': 'object',
                'properties': {
                    'pattern': {'type': 'string', 'description': 'Regex pattern'},
                    'path': {'type': 'string', 'description': 'Directory to search'}
                },
                'required': ['pattern', 'path']
            }
        }
    }
]

📊 Compatibilidade OpenAI

Formato idêntico: o schema type: function é o mesmo usado pela OpenAI e Anthropic

Migração fácil: trocar de Ollama local para API cloud requer mudar apenas o client, não os schemas

JSON Schema: os parâmetros seguem JSON Schema draft-07 — suporta types, enums, descriptions e required

3

🔄 O Fluxo Completo

Tool calling não é uma chamada única — é um loop. Você envia a mensagem com tools, o modelo responde com tool_calls, você executa as ferramentas, envia os resultados de volta, e o modelo gera a resposta final (ou pede mais ferramentas). Este ciclo repete até o modelo responder sem tool calls.

Loop de Tool Calling

# 1. Enviar com tools
response = chat(model, messages, tools=TOOLS)

# 2. Checar tool_calls
if response.message.tool_calls:
    for tc in response.message.tool_calls:
        result = TOOL_MAP[tc.function.name](**tc.function.arguments)
        messages.append({
            'role': 'tool',
            'content': result,
            'name': tc.function.name
        })
    # 3. Repetir
1

Enviar mensagem + schemas das tools

O modelo recebe o contexto e sabe quais ferramentas existem

2

Modelo responde com tool_calls (JSON)

Indica qual ferramenta usar e com quais argumentos

3

Executar ferramenta e enviar resultado

O resultado vai como mensagem role: "tool" de volta ao modelo

4

Modelo gera resposta final (ou pede mais tools)

O loop continua até o modelo responder sem tool_calls

4

🧠 Thinking vs Non-Thinking

Modelos Qwen3 suportam dois modos de raciocínio: non-thinking (rápido, direto) e thinking (com cadeia de pensamento em blocos <think>). Para tool calling, non-thinking é geralmente melhor — mais rápido e menos propenso a gerar JSON malformado.

Modos de Raciocínio

# Non-thinking (rápido, para tools):
# Basta enviar normalmente — Qwen3 usa non-thinking por default com tools

# Thinking (para planejamento):
# Adicione instrução no system prompt: "Think step by step"
# O modelo emitirá <think>...</think> blocks

# IMPORTANTE: Strip <think> do histórico!
import re
clean = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()

💡 Parâmetros Recomendados para Qwen3

Use temperature=0.6, top_p=0.95, top_k=20 como baseline. Esses valores foram otimizados pelo time da Alibaba para balancear criatividade e consistência no tool calling.

5

🌊 Streaming com Ferramentas

Streaming e tool calling têm uma tensão natural: streaming quer enviar tokens incrementalmente, mas tool calls precisam do JSON completo para serem executadas. A solução é uma abordagem híbrida — stream para texto, não-stream para tools.

Abordagem Híbrida

# LIMITAÇÃO: stream=True + tools não é incremental
# O modelo gera JSON completo antes do stream terminar

# SOLUÇÃO: Abordagem híbrida
# - Tool turns: stream=False (precisa do JSON completo)
# - Text turns: stream=True (UX melhor)

Fazer

  • Usar stream=False para chamadas com tools
  • Usar stream=True para respostas de texto ao usuário
  • Mostrar indicador de progresso durante tool execution

Evitar

  • Tentar parsear JSON incremental de tool calls em stream
  • Usar stream=True sempre — tool calls ficam quebradas
  • Bloquear a UI inteira durante tool execution sem feedback
6

⚖️ Local vs API

A decisão entre rodar localmente (Ollama) ou usar uma API cloud (Claude, OpenAI) não é binária. Cada abordagem tem trade-offs claros em custo, latência, qualidade e privacidade. Muitos projetos de produção usam ambos — local para desenvolvimento, API para deploy.

Comparativo: Local vs API

Aspecto Local (Ollama) API (Claude/OpenAI)
Custo $0 $$/token
Latência 1-5s/turn 0.5-2s/turn
Contexto 8-128K 128K-1M
Tool calling Bom (Qwen3+) Excelente
Privacidade Total Dados vão pro cloud
Qualidade 35-70% SWE 72-77% SWE

💡 Quando Usar Cada Um

Local: desenvolvimento, testes, projetos pessoais, dados sensíveis, prototipagem rápida sem custo. API: produção com usuários finais, tarefas que exigem contexto longo (>128K), cenários onde qualidade máxima é crítica. Ambos: desenvolver com local, validar com API antes de deploy.

📋 Resumo do Módulo

Tool calling envia schemas JSON e recebe chamadas estruturadas do LLM
Schemas seguem formato OpenAI — compatíveis entre provedores
O fluxo é um loop: enviar → tool_calls → executar → resultado → repetir
Non-thinking mode é melhor para tool calling, thinking para planejamento
Streaming híbrido: stream=False para tools, stream=True para texto
Local vs API: custo zero vs qualidade máxima — projetos maduros usam ambos

Próximo Módulo:

6.3 - Construindo o Agent Loop Local