Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

Function calling com IA: conecte o modelo ao seu código

Entenda o fluxo de function calling na Responses API, valide argumentos, execute funções autorizadas e devolva o resultado com o call_id correto.

Rodolfo Mori5 min de leitura

Function calling permite que um modelo peça ao seu programa uma informação ou ação definida por você. Nesta lição, ele vai solicitar a busca de um pedido, o código validará usuário e argumentos, e o resultado voltará à Responses API para compor a resposta final.

O termo também aparece como tool calling ou chamada de ferramenta. A palavra mais importante é “chamada”: o modelo descreve o que gostaria de usar, mas não entra no banco nem executa JavaScript sozinho. Quem conserva essa autoridade é a aplicação.

O fluxo foi compilado com o SDK oficial 7.5.0 e executado contra respostas HTTP simuladas, registradas como fixtures. Não houve chamada real à OpenAI. Assim, as saídas comprovam o protocolo, o call_id, as validações e os bloqueios locais; elas não são uma demonstração da qualidade de um modelo.

O atendente abre a solicitação; o estoque decide o que entregar

Pense num atendente de livraria. Ao ouvir “onde está meu pedido 1042?”, ele preenche uma solicitação para o estoque. A ficha diz qual serviço usar e qual número procurar. O estoquista confere a identidade do cliente, consulta o sistema e escreve o resultado na mesma ficha. Só então o atendente responde.

Na aplicação, o modelo é o atendente; o schema da ferramenta é o formulário; o dispatcher é a pessoa que encaminha; a função é o sistema de estoque; e o call_id é o protocolo que liga pergunta e resultado. O limite da comparação: o modelo não tem intenção nem responsabilidade humana. Ele produz uma solicitação que deve ser tratada como entrada não confiável.

O percurso completo tem duas idas à API quando uma ferramenta é necessária:

text
usuário -> Responses API -> function_call
programa -> valida e executa -> function_call_output
programa -> Responses API -> resposta final
Fluxo esperado: solicitação, execução local autorizada e continuação.

Esse mecanismo é uma peça do guia de inteligência artificial. Agora vamos construir a fronteira que mantém a ferramenta sob controle.

A descrição da ferramenta é um contrato, não uma permissão

Na Responses API atual, uma função fica na lista tools. Defina nome claro, descrição específica e parâmetros fechados:

ts
import type OpenAI from "openai";

export const ferramentas: OpenAI.Responses.Tool[] = [
  {
    type: "function",
    name: "buscar_pedido",
    description: "Busca status e previsão de um pedido do usuário autenticado.",
    strict: true,
    parameters: {
      type: "object",
      properties: {
        pedidoId: { type: "integer", minimum: 1 },
      },
      required: ["pedidoId"],
      additionalProperties: false,
    },
  },
];
{ nome: 'buscar_pedido', strict: true }

O schema orienta a geração de argumentos compatíveis. Ele não autentica o cliente e não concede acesso. Mesmo com strict: true, seu programa precisa validar novamente antes de chegar ao domínio.

Crie o contrato local dos argumentos:

ts
import { z } from "zod";

const BuscarPedidoArgsSchema = z.object({
  pedidoId: z.number().int().positive(),
});

console.log(BuscarPedidoArgsSchema.parse({ pedidoId: 1042 }));
{ pedidoId: 1042 }

Essa validação captura JSON inválido, campo ausente e número impróprio antes da consulta. A lição de Structured Outputs aplica a mesma ideia à resposta final.

Autorização usa a identidade da sessão, nunca um usuário inventado

Nossa base local contém dois pedidos:

ts
type Pedido = {
  id: number;
  usuarioId: string;
  status: "preparando" | "enviado" | "entregue";
  previsao: string;
};

const pedidos: Pedido[] = [
  { id: 1042, usuarioId: "user-1", status: "enviado", previsao: "2026-08-25" },
  { id: 2040, usuarioId: "user-2", status: "preparando", previsao: "2026-08-29" },
];
Fixtures locais: 2 pedidos pertencentes a usuários diferentes.

O modelo pode fornecer pedidoId; ele não deve escolher usuarioId. Essa identidade vem da sessão autenticada pelo servidor:

ts
function buscarPedidoDoUsuario(pedidoId: number, usuarioId: string): Pedido {
  const pedido = pedidos.find(
    (item) => item.id === pedidoId && item.usuarioId === usuarioId,
  );

  if (!pedido) {
    throw new Error("PEDIDO_NAO_ENCONTRADO_OU_SEM_ACESSO");
  }

  return pedido;
}

console.log(buscarPedidoDoUsuario(1042, "user-1"));
{ id: 1042, usuarioId: 'user-1', status: 'enviado', previsao: '2026-08-25' }

Responder “não encontrado ou sem acesso” evita confirmar que o pedido de outra pessoa existe. Esse princípio continua valendo quando a função consulta um banco real ou outra API.

Um dispatcher estreito impede nomes improvisados

Não procure uma função dinamicamente pelo texto que o modelo forneceu. Use uma lista explícita e valide os argumentos:

ts
function executarFerramenta(
  nome: string,
  argumentosJson: string,
  usuarioId: string,
): string {
  if (nome !== "buscar_pedido") {
    throw new Error("FERRAMENTA_NAO_PERMITIDA");
  }

  const args = BuscarPedidoArgsSchema.parse(JSON.parse(argumentosJson));
  return JSON.stringify(buscarPedidoDoUsuario(args.pedidoId, usuarioId));
}

console.log(executarFerramenta(
  "buscar_pedido",
  '{"pedidoId":1042}',
  "user-1",
));
{"id":1042,"usuarioId":"user-1","status":"enviado","previsao":"2026-08-25"}

O retorno é texto porque function_call_output.output aceita o resultado que será enviado ao modelo. Escolha o menor conjunto de dados necessário; não devolva colunas internas, tokens ou informações de outro cliente.

Agora tente chamar algo que nunca foi permitido:

ts
try {
  executarFerramenta("apagar_pedido", "{}", "user-1");
} catch (erro) {
  console.log((erro as Error).message);
}
FERRAMENTA_NAO_PERMITIDA

O bloqueio ocorre antes de qualquer efeito. Essa é uma proteção concreta contra um nome alucinado ou uma instrução maliciosa presente na mensagem do usuário.

call_id é o protocolo que precisa voltar intacto

Depois da primeira requisição, procure itens function_call em response.output. Cada item contém nome, argumentos e call_id:

ts
const chamadas = response.output.filter(
  (item) => item.type === "function_call",
);

for (const chamada of chamadas) {
  const resultado = executarFerramenta(
    chamada.name,
    chamada.arguments,
    usuarioIdDaSessao,
  );

  input.push({
    type: "function_call_output",
    call_id: chamada.call_id,
    output: resultado,
  });
}
Fixture: function_call_output devolvido com call_id 'call_mock_1'.

Num fluxo sem estado, preserve também os itens anteriores de response.output no próximo input. Outra opção documentada é encadear com previous_response_id. Não misture as duas estratégias sem entender o estado que ficará armazenado. No laboratório usamos store: false e carregamos o histórico necessário explicitamente.

A segunda requisição inclui ferramentas, instruções e o novo resultado. Quando não houver outra chamada, leia a resposta final estruturada:

ts
input.push(...response.output);

const next = await client.responses.parse({
  model: "gpt-5.6-luna",
  instructions,
  input,
  tools: ferramentas,
  text: { format: zodTextFormat(RespostaSchema, "resposta_suporte") },
  store: false,
});

console.log(next.output_parsed);
Não executado contra a API; a sequência foi executada com duas respostas HTTP de fixture e validada pelo schema.

O transporte local confirmou duas requisições e a presença do resultado:

ts
console.log({
  chamadasResponses: requisicoes.length,
  devolveuResultadoDaFerramenta:
    JSON.stringify(requisicoes[1]?.input).includes("function_call_output"),
});
{ chamadasResponses: 2, devolveuResultadoDaFerramenta: true }

Isso testa a fiação da aplicação. Uma chamada real ainda é necessária para medir se o modelo escolhe a ferramenta corretamente em perguntas variadas.

O erro de acesso não deve virar vazamento no prompt

Peça o pedido 2040 como user-1:

ts
try {
  executarFerramenta(
    "buscar_pedido",
    '{"pedidoId":2040}',
    "user-1",
  );
} catch (erro) {
  console.log((erro as Error).message);
}
PEDIDO_NAO_ENCONTRADO_OU_SEM_ACESSO

O pedido existe na fixture, mas pertence a user-2. O resultado correto é o mesmo de um id inexistente. Em produção, registre o erro com identificadores seguros no servidor e devolva ao modelo uma mensagem neutra. Não envie stack trace, query ou dados privados como “contexto para ajudar”.

Ferramentas com efeito — cancelar, pagar, publicar ou excluir — pedem camadas extras: confirmação explícita, idempotência, limite de valor, auditoria e, em ações críticas, revisão humana. Function calling organiza o pedido; não torna a ação automaticamente segura.

Missão: acrescente a ferramenta sem aumentar a autoridade

Crie consultar_prazo_padrao sem argumentos. Ela deve devolver apenas {"diasUteis":5} e nunca receber usuarioId do modelo:

ts
const consultarPrazo = () => JSON.stringify({ diasUteis: 5 });

console.log({
  ferramenta: "consultar_prazo_padrao",
  resultado: JSON.parse(consultarPrazo()),
});
{ ferramenta: 'consultar_prazo_padrao', resultado: { diasUteis: 5 } }

O trabalho termina quando três verificações passam: o nome novo está na allowlist; qualquer argumento extra é rejeitado pelo schema; e apagar_pedido continua bloqueada. Depois siga para agentes de IA, onde a gente coloca essa troca dentro de um ciclo com limite de passos.

  • function calling
  • tool calling
  • responses api
  • openai
  • seguranca

Perguntas frequentes

O modelo executa minha função diretamente?
Não. O modelo solicita uma chamada com nome e argumentos. Seu programa valida a solicitação, executa somente funções permitidas e devolve o resultado para o modelo continuar.
Function calling é o mesmo que Structured Outputs?
Não. Function calling estrutura um pedido para seu código usar uma ferramenta. Structured Outputs estrutura a resposta final destinada à aplicação. Um fluxo pode usar os dois.
Posso confiar nos argumentos gerados pelo modelo?
Não. Trate-os como entrada não confiável. Valide o JSON, os tipos, a autorização do usuário e as regras do domínio antes de qualquer ação.
Para que serve call_id?
Ele liga o resultado devolvido à chamada solicitada pelo modelo. Ao criar function_call_output, repita exatamente o call_id recebido.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 26.3.0, TypeScript 7.0.2, OpenAI SDK 7.5.0 e Zod 4.4.3; integração testada com transporte local simulado, sem chamada à API, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. OpenAI Docs — Function calling — developers.openai.com
  2. OpenAI API — Responses — developers.openai.com
  3. OpenAI Docs — Safety best practices — developers.openai.com

Continue por aqui