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.
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:
usuário -> Responses API -> function_call
programa -> valida e executa -> function_call_output
programa -> Responses API -> resposta finalEsse 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:
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,
},
},
];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:
import { z } from "zod";
const BuscarPedidoArgsSchema = z.object({
pedidoId: z.number().int().positive(),
});
console.log(BuscarPedidoArgsSchema.parse({ 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:
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" },
];O modelo pode fornecer pedidoId; ele não deve escolher usuarioId. Essa
identidade vem da sessão autenticada pelo servidor:
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"));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:
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",
));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:
try {
executarFerramenta("apagar_pedido", "{}", "user-1");
} catch (erro) {
console.log((erro as Error).message);
}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:
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,
});
}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:
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);O transporte local confirmou duas requisições e a presença do resultado:
console.log({
chamadasResponses: requisicoes.length,
devolveuResultadoDaFerramenta:
JSON.stringify(requisicoes[1]?.input).includes("function_call_output"),
});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:
try {
executarFerramenta(
"buscar_pedido",
'{"pedidoId":2040}',
"user-1",
);
} catch (erro) {
console.log((erro as Error).message);
}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:
const consultarPrazo = () => JSON.stringify({ diasUteis: 5 });
console.log({
ferramenta: "consultar_prazo_padrao",
resultado: JSON.parse(consultarPrazo()),
});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.
Perguntas frequentes
O modelo executa minha função diretamente?
Function calling é o mesmo que Structured Outputs?
Posso confiar nos argumentos gerados pelo modelo?
Para que serve call_id?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- OpenAI Docs — Function calling — developers.openai.com
- OpenAI API — Responses — developers.openai.com
- OpenAI Docs — Safety best practices — developers.openai.com


