Phase 13 - Lesson 02

Deep Dive em Function Calling — OpenAI, Anthropic, Gemini

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

Os três principais provedores convergiram para o mesmo loop de chamada de ferramenta em 2024 e depois divergiram em todo o resto. A OpenAI usa tools e tool_calls. A Anthropic usa blocos tool_use e tool_result. O Gemini usa functionDeclarations e correlação por ID único. Esta lição compara os três lado a lado para que o código executado em um provedor não quebre quando você o portar para outro.

Tipo: Build Linguagens: Python (stdlib, schema translators) Pré-requisitos: Phase 13 · 01 (the tool interface) Tempo: ~75 minutos

Objetivos de Aprendizado

  • Apresentar as três diferenças de formato entre as cargas úteis (payloads) de chamada de função da OpenAI, Anthropic e Gemini (declaração, chamada, resultado).
  • Traduzir uma declaração de ferramenta entre os formatos dos três provedores e prever onde as restrições do modo estrito (strict-mode) serão diferentes.
  • Usar tool_choice em cada provedor para forçar, proibir ou selecionar automaticamente chamadas de ferramentas.
  • Conhecer os limites rígidos de cada provedor (quantidade de ferramentas, profundidade do esquema, tamanho dos argumentos) e as assinaturas de erro que cada um emite quando esses limites são violados.

O Problema

O formato de uma requisição de chamada de função difere conforme o provedor. Três exemplos concretos de pilhas de produção de 2026:

OpenAI Chat Completions / Responses API. Você envia tools: [{type: "function", function: {name, description, parameters, strict}}]. A resposta do modelo contém choices[0].message.tool_calls: [{id, type: "function", function: {name, arguments}}] onde arguments é uma string JSON que você deve analisar (parse). O modo estrito (strict: true) impõe a conformidade do esquema por meio de decodificação restrita.

Anthropic Messages API. Você envia tools: [{name, description, input_schema}]. A resposta retorna como content: [{type: "text"}, {type: "tool_use", id, name, input}]. input já vem analisado (um objeto, não uma string). Você responde com uma nova mensagem de user contendo um bloco {type: "tool_result", tool_use_id, content}.

Google Gemini API. Você envia tools: [{functionDeclarations: [{name, description, parameters}]}] (aninhado sob functionDeclarations). A resposta chega como candidates[0].content.parts: [{functionCall: {name, args, id}}] onde id é único no Gemini 3 ou superior para correlação de chamadas paralelas. Você responde com {functionResponse: {name, id, response}}.

O mesmo loop. Nomes de campos diferentes, aninhamento diferente, convenções diferentes de string vs. objeto, mecanismos de correlação diferentes. Uma equipe que escreve um agente de clima na OpenAI leva dois dias para portá-lo para a Anthropic e outro dia para o Gemini apenas para ajustar a infraestrutura de comunicação.

Esta lição constrói um tradutor que unifica os três formatos em uma única declaração de ferramenta canônica e faz o roteamento na borda (edge). A Phase 13 · 17 generaliza esse mesmo padrão em um gateway de LLM.

O Conceito

A estrutura comum

Cada provedor precisa de cinco coisas:

  1. Lista de ferramentas. Nome, descrição e esquema de entrada para cada ferramenta.
  2. Escolha de ferramenta. Forçar uma ferramenta específica, proibir ferramentas ou deixar o modelo decidir.
  3. Emissão da chamada. Saída estruturada especificando o nome da ferramenta e os argumentos.
  4. ID da chamada. Correlacionar a resposta com a chamada correta (importante para chamadas paralelas).
  5. Injeção de resultado. Uma mensagem ou bloco que vincula o resultado de volta à chamada correspondente.

Diferenças de formato, campo por campo

Aspecto OpenAI Anthropic Gemini
Envelope de declaração {type: "function", function: {...}} {name, description, input_schema} {functionDeclarations: [{...}]}
Campo do esquema parameters input_schema parameters
Contêiner de resposta tool_calls[] na mensagem do assistente content[] do tipo tool_use parts[] de tipo functionCall
Tipo dos argumentos JSON em formato de string objeto analisado (parsed) objeto analisado (parsed)
Formato de ID call_... (gerado pela OpenAI) toolu_... (Anthropic) UUID (Gemini 3+)
Bloco de resultado papel (role) tool, tool_call_id user com tool_result, tool_use_id functionResponse com id correspondente
Forçar uma ferramenta tool_choice: {type: "function", function: {name}} tool_choice: {type: "tool", name} tool_config: {function_calling_config: {mode: "ANY"}}
Proibir ferramentas tool_choice: "none" tool_choice: {type: "none"} mode: "NONE"
Esquema estrito (strict) strict: true esquema-é-esquema (sempre imposto) responseSchema no nível da requisição

Limites que você realmente vai atingir

  • OpenAI. 128 ferramentas por requisição. Profundidade do esquema de 5 níveis. String de argumentos <= 8192 bytes. O modo estrito exige que não haja $ref, nem sobreposição de oneOf/anyOf/allOf, e que todas as propriedades sejam listadas em required.
  • Anthropic. 64 ferramentas por requisição. Profundidade do esquema efetivamente ilimitada, mas com limite prático de 10. Sem flag de modo estrito; o esquema é um contrato e o modelo tende a cumpri-lo.
  • Gemini. 64 funções por requisição. Os tipos de esquema são um subconjunto da OpenAPI 3.0 (pequena divergência em relação ao JSON Schema 2020-12). Chamadas paralelas usam IDs únicos desde o Gemini 3.

Comportamento do tool_choice

Três modos que todos os provedores suportam, com nomes diferentes.

  • Auto. O modelo escolhe entre a ferramenta ou texto. Padrão.
  • Required / Any. O modelo deve chamar pelo menos uma ferramenta.
  • None. O modelo não deve chamar nenhuma ferramenta.

Além disso, há um modo exclusivo para cada provedor:

  • OpenAI. Força uma ferramenta específica pelo nome.
  • Anthropic. Força uma ferramenta específica pelo nome; a flag disable_parallel_tool_use separa o uso de ferramenta única de uso múltiplo.
  • Gemini. O mode: "VALIDATED" direciona todas as respostas por um validador de esquema, independentemente da intenção do modelo.

Chamadas paralelas

O parallel_tool_calls: true da OpenAI (padrão) emite múltiplas chamadas em uma única mensagem do assistente. Você executa todas elas e responde com uma mensagem em lote com papel (role) tool contendo uma entrada para cada tool_call_id. Historicamente, a Anthropic fazia chamadas únicas; disable_parallel_tool_use: false (padrão a partir do Claude 3.5) habilita chamadas múltiplas. O Gemini 2 permitia chamadas paralelas mas não fornecia IDs estáveis; o Gemini 3 adiciona UUIDs para que respostas fora de ordem se correlacionem de forma limpa.

Streaming

Todos os três suportam chamadas de ferramenta via streaming. O formato de transmissão difere:

  • OpenAI. Fragmentos delta de tool_calls[i].function.arguments chegam incrementalmente. Você os acumula até que ocorra finish_reason: "tool_calls".
  • Anthropic. Eventos de início de bloco (block-start), delta de bloco (block-delta) e fim de bloco (block-stop). Fragmentos de input_json_delta trazem os argumentos parciais.
  • Gemini. streamFunctionCallArguments (novo no Gemini 3) emite fragmentos com um functionCallId para que múltiplas chamadas paralelas possam ser intercaladas.

A Phase 13 · 03 aprofunda-se na remontagem de chamadas paralelas + streaming. Esta lição foca nos formatos de declaração e de chamada única.

Erros e correção

Erros de argumentos inválidos também se apresentam de forma diferente.

  • OpenAI (não estrito). O modelo retorna arguments: "{bad json}", sua análise (parse) de JSON falha, você injeta uma mensagem de erro e faz uma nova chamada.
  • OpenAI (estrito). A validação acontece durante a decodificação; JSON inválido é impossível, mas um refusal (recusa) pode surgir.
  • Anthropic. O input pode conter campos inesperados; o esquema é consultivo. Valide no servidor.
  • Gemini. Particularidade da OpenAPI 3.0: enum em campos de objeto é ignorado silenciosamente; faça a validação você mesmo.

O padrão tradutor

Uma declaração de ferramenta canônica no seu código se parece com esta (você escolhe o formato):

Tool(
    name="get_weather",
    description="Use when ...",
    input_schema={"type": "object", "properties": {...}, "required": [...]},
    strict=True,
)

Três pequenas funções traduzem isso para os formatos dos três provedores. O código de teste em code/main.py faz exatamente isso e, em seguida, simula um ciclo completo (round-trip) de uma chamada de ferramenta fictícia em cada formato de resposta de provedor. Não é necessária conexão de rede — esta lição ensina os formatos, não o protocolo HTTP.

Equipes de produção envolvem esse tradutor em AbstractToolset (Pydantic AI), UniversalToolNode (LangGraph) ou BaseTool (LlamaIndex). A Phase 13 · 17 entrega um gateway que expõe uma API no formato da OpenAI à frente de qualquer um dos três.

Como Usar

O code/main.py define uma classe de dados canônica Tool e três tradutores que emitem o JSON de declaração para OpenAI, Anthropic e Gemini. Em seguida, ele analisa (parse) uma resposta de provedor construída manualmente para cada formato no mesmo objeto de chamada canônico, demonstrando que a semântica é idêntica nos bastidores. Execute-o e compare as três declarações lado a lado.

O que observar:

  • Os três blocos de declaração diferem apenas no envelope e nos nomes dos campos.
  • Os três blocos de resposta diferem no local onde a chamada reside (nível superior de tool_calls, bloco content[], entrada parts[]).
  • Uma única função canonical_call() extrai {id, name, args} de todos os três formatos de resposta.

Como Entregar

Esta lição produz o arquivo outputs/skill-provider-portability-audit.md. Dada uma integração de chamada de função feita para um provedor, a habilidade produz uma auditoria de portabilidade: em quais limites do provedor ela se apoia, quais campos precisam ser renomeados e o que quebra quando portada para cada outro provedor.

Exercícios

  1. Execute code/main.py e verifique se os três JSONs de declaração dos provedores serializam o mesmo objeto Tool subjacente. Modifique a ferramenta canônica para adicionar um parâmetro enum e confirme que apenas o tradutor do Gemini precisa tratar a particularidade da OpenAPI.

  2. Adicione um analisador ListToolsResponse para cada provedor que extraia a lista de ferramentas que um modelo retorna após uma chamada de list_tools ou de descoberta. A OpenAI não possui isso nativamente; note essa assimetria.

  3. Implemente a conversão de tool_choice: mapeie um ToolChoice(mode="force", tool_name="x") canônico para todos os três formatos de provedor. Em seguida, mapeie mode="any" e mode="none". Consulte a tabela comparativa da lição.

  4. Escolha um dos três provedores e leia seu guia de chamada de função de ponta a ponta. Encontre um campo em sua especificação de esquema que os outros dois não suportam. Candidatos: strict da OpenAI, disable_parallel_tool_use da Anthropic, function_calling_config.allowed_function_names do Gemini.

  5. Escreva um vetor de teste: uma chamada de ferramenta cujos argumentos violam o esquema declarado. Execute-o no validador de cada provedor (o da biblioteca padrão na Lição 01 servirá como proxy) e registre quais erros ocorrem. Documente qual provedor você usaria em produção visando maior rigor (strictness).

Termos-Chave

Termo O que as pessoas dizem O que realmente significa
Function calling "Uso de ferramentas" (Tool use) API em nível de provedor para emissão estruturada de chamada de ferramenta
Declaração de ferramenta "Especificação de ferramenta" Nome + descrição + payload de entrada JSON Schema
tool_choice "Forçar / proibir" Modos auto / required / none / nome-específico
Modo estrito (Strict mode) "Imposição de esquema" Flag da OpenAI que restringe a decodificação para corresponder ao esquema
Bloco tool_use "Formato de chamada da Anthropic" Bloco de conteúdo inline com id, name, input
Parte functionCall "Formato de chamada do Gemini" Uma entrada parts[] contendo name, args e id
Argumentos como string "JSON serializado em string" A OpenAI retorna argumentos como uma string JSON, não como um objeto
Chamadas de ferramenta paralelas "Fan-out em um único turno" Múltiplas chamadas de ferramenta em uma única mensagem do assistente
Recusa (Refusal) "Modelo declina" Bloco de recusa exclusivo do modo estrito em vez de uma chamada
Subconjunto OpenAPI 3.0 "Particularidade do esquema do Gemini" O Gemini usa um dialeto semelhante ao JSON Schema com pequenas diferenças

Leituras Adicionais

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).