Pular para o conteúdo
mnzes

Qual a diferença entre structured outputs e function calling?

PorDiógenes MenezesAprendendo IA em público

12 min de leitura

Os dois usam o mesmo mecanismo por baixo: um schema que restringe a decodificação. A diferença está no que acontece depois. Com structured outputs, a resposta é o objeto e a chamada termina ali. Com function calling, o modelo pede que o seu código execute algo e espera o resultado de volta. Se nada vai ser executado, você quer o primeiro.

A confusão é razoável, porque os dois aparecem no mesmo lugar da documentação e resolvem o mesmo sintoma imediato, que é fazer o modelo devolver JSON válido. Só que um deles resolve formato e o outro resolve controle.

Este artigo trata da escolha. Como declarar o formato está em saídas estruturadas, e o laço de execução, com registro de ferramentas, tratamento de erro e limite de rodadas, está em function calling explicado.

O mecanismo é o mesmo

"12 pães, R$ 148,50, Padaria do Zé"saída estruturadaschema na resposta{ "cliente": "Padaria do Zé", "total": 148.5}function callingschema no parâmetroda ferramentatool_use registrar_pedido{ "cliente": "Padaria do Zé", "total": 148.5}
Figura 1O objeto é o mesmo nos dois caminhos. O que muda é o envelope: num deles ele chega como resposta, no outro como um pedido endereçado ao seu código.

Vale começar por onde a diferença não está. Os dois recebem um JSON Schema, e os dois compilam esse schema na mesma restrição sobre a amostragem: um autômato acompanha o que já foi emitido e zera a probabilidade dos tokens que levariam a uma sequência inválida1. No modo estrito, a garantia de sintaxe é idêntica nos dois caminhos porque é literalmente o mesmo código rodando.

Isso descarta a pergunta que mais aparece: “qual dos dois é mais confiável para produzir JSON válido?”. Nenhum. Quando existe diferença medida entre os dois num provedor, ela vem de o modo estrito estar implementado em um e não no outro, ou de a cobertura da especificação ser diferente entre os dois endpoints. É uma diferença de produto, não de método, e ela muda de release em release.

O que muda de verdade é o envelope. Num caso o objeto chega como conteúdo da resposta e a chamada acabou. No outro ele chega dentro de um bloco de uso de ferramenta, com um nome e um identificador, e o modelo parou no meio do turno esperando que você devolva um resultado com aquele identificador.

A diferença é o que vem depois

formatar a respostamodeloobjeto tipadoa chamada acaba aquidecidir uma açãomodelopedido de ferramentaseu código executao resultado voltaa chamada só acaba quandoo modelo para de pedir
Figura 2A pergunta que decide não é qual é mais confiável, é se alguma coisa vai rodar. Se nada roda, a segunda coluna só acrescenta uma rodada de inferência.

Com structured outputs existe uma inferência e um caminho de código. Você chama, recebe o objeto, valida e segue. O schema vale para toda resposta e o modelo não tem escolha nenhuma a fazer sobre isso.

Com function calling existem no mínimo duas inferências e um laço. O modelo decide se chama alguma ferramenta, qual delas e com que argumentos; o seu código executa; o resultado volta como uma nova mensagem; e o modelo roda de novo, agora com o resultado no contexto. Ele pode pedir outra ferramenta, pedir a mesma de novo com argumentos diferentes, ou parar e escrever a resposta final.

Essa palavra, decide, é a diferença inteira. Foi ela que definiu o mecanismo desde o começo: o trabalho descrito no Toolformer é o modelo aprender quais APIs chamar, quando chamar, que argumentos passar e como incorporar o resultado ao que vem depois2. Nada disso é sobre formato. É sobre uma decisão que o modelo toma e que pode sair errada de quatro maneiras distintas.

Por que “ferramenta só para formatar” virou hábito

O padrão tem uma explicação histórica simples. Function calling chegou às APIs comerciais antes de qualquer schema no nível da resposta. Quem precisava de formato garantido em 2023 declarava uma ferramenta falsa, forçava a chamada dela e lia os argumentos como se fossem a resposta. Funcionava, e virou hábito.

Em 2026 isso é dívida na maior parte das bases de código. O sinal de que você está pagando essa dívida é o tool_choice fixando uma ferramenta específica: forçar a ferramenta remove do modelo exatamente a escolha que justificava usar ferramentas.

Duas exceções continuam legítimas. A primeira é o provedor que só oferece schema estrito nos parâmetros de ferramenta, e não na resposta; aí a ferramenta é o único caminho para a garantia. A segunda é quando você quer que o modelo escolha entre formatos diferentes de resposta — uma ferramenta por formato é uma união discriminada natural, e o nome da ferramenta escolhida já é o discriminador.

O que muda no seu código

A conta de complexidade é assimétrica, e ela costuma ser subestimada na hora de decidir.

Do lado de structured outputs você escreve um caminho: chamar, extrair, validar, usar. O erro possível é o objeto não validar, e o tratamento é repetir com a mensagem de erro.

Do lado de function calling você escreve um despachante que mapeia nome de ferramenta para função, uma validação de argumentos na sua fronteira (o schema do provedor garante o formato, não a existência do identificador que veio dentro dele), a execução em si, a conversão de erro de execução em uma mensagem que o modelo entenda, um limite de rodadas para o laço não rodar para sempre, e uma política para quando o modelo pedir duas vezes a mesma ação que não é idempotente.

Há um efeito colateral bom que raramente é citado: o laço deixa rastro. Cada decisão do modelo vira um registro com nome, argumentos e resultado, e isso é material de depuração que a saída estruturada não produz. Se o seu problema é entender por que o sistema fez o que fez, o laço tem valor mesmo quando o formato não exigia.

O que o streaming muda

Os dois transmitem token a token, e o que dá para fazer com o fluxo parcial é diferente.

Com saída estruturada você recebe um objeto se montando da esquerda para a direita. Dá para exibir campo por campo conforme chegam, desde que o seu parser tolere JSON incompleto e você aceite que um valor pode ser reescrito antes de fechar. É o que sustenta interface que preenche formulário enquanto o modelo responde. Vale lembrar que a ordem dos campos no schema vira a ordem de aparecimento na tela, então ela deixa de ser uma decisão só técnica.

Com function calling o nome da ferramenta chega antes dos argumentos, porque é a primeira coisa que o modelo emite naquele bloco. Isso permite mostrar “consultando estoque” no instante em que a decisão foi tomada, com os argumentos ainda chegando. É pouca coisa em código e resolve boa parte da percepção de lentidão em laço de várias rodadas, onde o tempo até a resposta final é a soma de todas as inferências.

Qual usar em cada caso

A regra cabe em uma pergunta: alguma coisa vai ser executada e o resultado precisa voltar para o modelo?

Não, nada executa. Structured outputs. Extração de entidades, classificação, pontuação, resumo com campos fixos, tradução com metadado. Em todos esses o modelo produz um dado final, e o dado final é a resposta.

Sim, e o modelo precisa do resultado. Function calling. Consultar estoque, buscar em um índice, ler um arquivo, criar um ticket cujo número entra na resposta ao usuário. O traço comum é o modelo não conseguir terminar sem o que só o seu sistema sabe.

O caso do meio, que quase todo mundo erra. O modelo escolhe uma entre várias ações, o seu código executa aquela ação, e a conversa acaba. Parece function calling porque tem ação, e não é: nada volta para o modelo.

O exemplo típico é roteamento de ticket. Chega uma mensagem de suporte, o modelo decide se ela vai para cobrança, para técnico ou para cancelamento, e o seu código move o ticket. Montado como ferramenta, isso vira três definições no prompt, uma decisão do modelo sobre qual chamar e uma segunda inferência para o modelo dizer que terminou. Montado como saída estruturada, é um campo destino com três valores de enum e mais um campo de justificativa antes dele. Uma inferência, sem laço, com a mesma garantia sobre o valor. A economia é de metade das chamadas, e some junto a possibilidade de o modelo responder em texto em vez de escolher.

Onde cada um falha

Function calling erra na escolha, não no formato. É um espaço de falha que a saída estruturada simplesmente não tem. O Gorilla apontou o padrão em 2023: o problema com APIs não era gerar JSON, era gerar argumentos corretos e não alucinar o uso da API, e mesmo os modelos mais fortes da época tropeçavam nisso; os autores mostraram que acoplar a recuperação da documentação no momento da chamada reduzia bastante a alucinação3.

A escolha tem três partes, e elas falham separado. O API-Bank separou planejar, recuperar e chamar em um sistema executável com 73 ferramentas e 314 diálogos anotados com 753 chamadas, justamente porque um número único de acerto esconde qual das três quebrou4. Se o seu laço erra, medir as três juntas não vai dizer onde mexer.

A inconsistência é pior que a taxa média. O τ-bench mediu agentes de function calling em conversas com um usuário simulado e regras de domínio, e encontrou menos de 50% de sucesso mesmo com modelos de ponta na época. O número mais desconfortável foi outro: repetindo a mesma tarefa oito vezes, a taxa de acerto consistente caiu abaixo de 25% no domínio de varejo5. Para um fluxo que executa ação de verdade, acertar em média é muito menos interessante que acertar sempre.

Structured outputs nunca se abstém. O schema tem campos obrigatórios, então o modelo preenche os campos obrigatórios, exista ou não a informação no texto. É a falha mais comum em extração e a mais silenciosa. O conserto é declarar os campos como anuláveis e escrever no prompt o que fazer quando não achar, em vez de esperar que o modelo deixe em branco por conta própria.

Function calling também erra por excesso. O avesso da abstenção é chamar quando não precisava: o modelo consulta o estoque para responder uma pergunta que não depende de estoque, e você paga duas inferências por uma resposta que sairia na primeira. Descrição de ferramenta que promete demais é a causa mais comum, e o conserto é escrever na descrição quando não usar aquela ferramenta, e não só o que ela faz. Vale medir a taxa de chamada em entradas que não deveriam disparar ferramenta nenhuma; ela costuma ser mais alta do que qualquer um espera.

As definições de ferramenta ocupam o prompt. Cada ferramenta com descrição decente custa algo entre 100 e 200 tokens, em toda chamada, e a lista inteira compete com a entrada real pela atenção do modelo. Vinte ferramentas são alguns milhares de tokens fixos e uma decisão mais difícil de tomar.

Os dois escrevem na ordem do schema. Se o campo de resultado vem antes do campo de justificativa, a justificativa é uma racionalização escrita depois. Vale para o schema da resposta e para o schema dos parâmetros da ferramenta.

Custo

Structured outputs custa uma inferência. O acréscimo sobre uma chamada normal é a compilação do schema na primeira vez e nada de relevante depois.

Function calling custa uma inferência mais uma por ferramenta executada, e cada rodada reenvia a conversa inteira, que cresce com o resultado que você devolveu. Um laço de três ferramentas sobre um prompt de 4 mil tokens são quatro inferências, com a entrada aumentando a cada uma. O cache de prompt cobre a parte estável, que é o sistema mais as definições de ferramenta, e é por isso que essa parte precisa vir antes de qualquer coisa que varie.

Por onde começar

  1. Responda a pergunta do laço antes de escolher a API. Alguma coisa executa e o resultado volta para o modelo? Só isso decide.
  2. Se você está forçando uma ferramenta específica, troque por saída estruturada. É o mesmo resultado com menos peça móvel.
  3. Se o modelo escolhe e o seu código executa sem devolver nada, use um enum. Uma inferência a menos por requisição.
  4. Meça planejar, recuperar e chamar separado se o laço existe de verdade. O número agregado não indica onde mexer.
  5. Rode a mesma tarefa várias vezes antes de subir para produção. Em fluxo que executa ação, a variação entre tentativas importa mais que a média.

Footnotes

  1. Willard e Louf (2023) descrevem a geração guiada como transições entre estados de um autômato finito, com um índice pré-computado sobre o vocabulário — o mesmo mecanismo que sustenta schema de resposta e schema de parâmetro de ferramenta.

  2. Schick et al. (2023) treinaram um modelo para decidir quais APIs chamar, quando chamar, que argumentos passar e como incorporar o resultado à continuação do texto, de forma auto-supervisionada.

  3. Patil et al. (2023) identificaram que a dificuldade em chamadas de API está em gerar argumentos corretos e em não alucinar o uso, e mostraram que acoplar recuperação de documentação no momento da chamada reduz a alucinação e acompanha mudança de versão.

  4. Li et al. (2023) montaram um sistema executável com 73 ferramentas e anotaram 314 diálogos com 753 chamadas, avaliando planejamento, recuperação e chamada como habilidades separadas.

  5. Yao et al. (2024) avaliaram agentes de function calling em conversas com usuário simulado e regras de domínio: menos de 50% de sucesso nas tarefas e, em oito repetições da mesma tarefa, acerto consistente abaixo de 25% no domínio de varejo.

Perguntas frequentes

Posso usar uma ferramenta só para formatar a saída?
Pode, e por anos foi a única forma de garantir formato. Hoje só compensa em duas situações: quando o seu provedor aceita schema estrito na ferramenta mas não na resposta, e quando você quer que o modelo escolha entre formatos diferentes, com uma ferramenta por formato.
Qual usar para extrair dados de um texto?
Structured outputs. A extração não executa nada, não precisa que o modelo escolha entre caminhos e não tem resultado para devolver ao modelo. Envolver isso em function calling acrescenta uma decisão que pode dar errado e, dependendo de como você fecha o laço, uma rodada de inferência a mais.
Function calling sem executar nada faz sentido?
Faz quando você precisa que o modelo escolha entre várias formas de resposta, porque uma ferramenta por forma é uma união discriminada natural. Fora disso é saída estruturada com passos extras: se você força uma ferramenta específica, tirou do modelo justamente a escolha que justificava o mecanismo.
Os dois usam o mesmo mecanismo por dentro?
Usam. O schema da resposta e o schema dos parâmetros da ferramenta são compilados na mesma restrição sobre a amostragem, com o mesmo autômato mascarando os tokens ilegais. Diferenças de confiabilidade entre os dois vêm da cobertura do modo estrito em cada um, não do mecanismo.
O modelo pode deixar de chamar a ferramenta?
Pode, e essa é a diferença de fundo. Com function calling o modelo decide se chama, qual chama e com que argumentos, então existe a possibilidade de responder em texto quando deveria agir. Com structured outputs não há decisão: o schema vale para toda resposta.
Qual dos dois gasta mais token?
Function calling, em duas frentes. As definições das ferramentas ocupam o prompt em toda chamada, algo entre 100 e 200 tokens por ferramenta bem descrita. E cada ferramenta executada custa uma inferência a mais, com a conversa inteira reenviada.

Referências

  1. Willard, B. T. e Louf, R.. Efficient Guided Generation for Large Language Models (2023)arXiv:2307.09702
  2. Schick, T. et al.. Toolformer: Language Models Can Teach Themselves to Use Tools (2023)arXiv:2302.04761
  3. Patil, S. G. et al.. Gorilla: Large Language Model Connected with Massive APIs (2023)arXiv:2305.15334
  4. Li, M. et al.. API-Bank: A Comprehensive Benchmark for Tool-Augmented LLMs (2023)arXiv:2304.08244
  5. Yao, S. et al.. tau-bench: A Benchmark for Tool-Agent-User Interaction in Real-World Domains (2024)arXiv:2406.12045