Python + IA: Fundamentos e Projetos Práticos
Uma IA que apenas escreve texto é útil. Uma IA que consulta um pedido, calcula um frete ou busca dados do seu sistema pode participar de fluxos reais. O recurso que conecta esses dois mundos é o function calling: o modelo identifica quando precisa de uma ferramenta, devolve uma solicitação estruturada e deixa seu código Python decidir se e como a função será executada.
Nesta aula 33 da série Python + IA: Fundamentos e Projetos Práticos, vamos construir um assistente que consulta o status de um pedido. O exemplo usa a Responses API, esquema estrito, lista de funções permitidas e validação antes da execução. A implementação segue o fluxo atual da documentação oficial de function calling da OpenAI.
Do JSON estruturado para uma ação controlada
Na aula anterior, vimos como gerar JSON confiável com saídas estruturadas. Ali, o formato organiza a resposta final do modelo. No function calling, o JSON tem outra função: descrever uma solicitação para que a sua aplicação execute uma ferramenta.
Essa diferença é fundamental. O modelo não chama diretamente o banco de dados e não executa uma função Python por conta própria. Ele pode responder algo equivalente a “use a ferramenta consultar_pedido com o número AM-1042”. Seu programa recebe a solicitação, verifica o nome, valida os argumentos, executa a função permitida e devolve o resultado ao modelo.
O fluxo completo tem cinco etapas:
- seu código oferece uma lista de ferramentas e seus esquemas;
- o usuário faz uma pergunta;
- o modelo produz uma chamada de ferramenta quando necessário;
- a aplicação valida e executa a função;
- o resultado volta ao modelo, que redige a resposta final.
A separação mantém a aplicação no controle. O modelo propõe; o código autoriza e executa.
Prepare o projeto e crie a função local
Você pode continuar o ambiente organizado com uv e pyproject.toml. No terminal, crie um projeto e instale o SDK:
uv init assistente-pedidos
cd assistente-pedidos
uv add openai
Configure OPENAI_API_KEY no ambiente, sem gravar a chave no arquivo Python. Em seguida, crie uma fonte de dados local para o tutorial:
PEDIDOS = {
"AM-1042": {"status": "em transporte", "previsao": "12/08/2026"},
"AM-1057": {"status": "separando itens", "previsao": "13/08/2026"},
}
def consultar_pedido(numero: str) -> dict:
pedido = PEDIDOS.get(numero)
if pedido is None:
return {"encontrado": False, "numero": numero}
return {
"encontrado": True,
"numero": numero,
**pedido,
}
Em produção, essa função poderia consultar uma API interna. Para aprender o fluxo, o dicionário em memória elimina dependências e deixa claro onde termina a IA e começa a regra de negócio.
Descreva a ferramenta com um esquema estrito
O modelo precisa conhecer o nome da função, sua finalidade e os argumentos aceitos. Na Responses API, a ferramenta pode ser descrita assim:
tools = [
{
"type": "function",
"name": "consultar_pedido",
"description": "Consulta o status de um pedido pelo número.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"numero": {
"type": "string",
"description": "Identificador no formato AM-1234",
}
},
"required": ["numero"],
"additionalProperties": False,
},
}
]
Com strict: True, a chamada deve respeitar o esquema. A documentação oficial recomenda o modo estrito e exige que os campos sejam declarados em required e que objetos usem additionalProperties: false. Isso reduz argumentos inesperados, mas não substitui a validação da sua aplicação.
Uma string pode estar correta para o JSON Schema e ainda ser inválida para o negócio. AM-1042 atende ao formato esperado; qualquer-coisa continua sendo uma string. Por isso, vamos verificar o padrão antes de consultar os dados.
Detecte, valide e execute a chamada
Agora envie a pergunta junto com a definição da ferramenta. Desativaremos chamadas paralelas para que este exemplo aceite no máximo uma função por rodada:
import json
import re
from openai import OpenAI
client = OpenAI()
funcoes_permitidas = {
"consultar_pedido": consultar_pedido,
}
input_list = [
{"role": "user", "content": "Onde está o pedido AM-1042?"}
]
response = client.responses.create(
model="gpt-5.6",
input=input_list,
tools=tools,
parallel_tool_calls=False,
)
input_list += response.output
for item in response.output:
if item.type != "function_call":
continue
if item.name not in funcoes_permitidas:
resultado = {"erro": "ferramenta_nao_permitida"}
else:
try:
argumentos = json.loads(item.arguments)
numero = argumentos["numero"].strip().upper()
if not re.fullmatch(r"AM-\d{4}", numero):
raise ValueError("Número de pedido inválido")
resultado = funcoes_permitidas[item.name](numero=numero)
except (KeyError, ValueError, json.JSONDecodeError) as erro:
resultado = {"erro": str(erro)}
input_list.append(
{
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(resultado, ensure_ascii=False),
}
)
Nunca use eval() para executar o nome ou os argumentos enviados pelo modelo. O dicionário funcoes_permitidas é uma lista explícita de capacidades. Se aparecer outro nome, a aplicação recusa. O call_id liga o resultado à solicitação correta.
Devolva o resultado e gere a resposta final
O retorno de uma ferramenta deve ser uma string; JSON serializado funciona bem porque preserva campos e erros. Depois de adicionar function_call_output à lista, faça uma segunda chamada:
final = client.responses.create(
model="gpt-5.6",
input=input_list,
tools=tools,
instructions=(
"Responda em português, de forma objetiva. "
"Não invente status ou previsão ausentes no resultado da ferramenta."
),
)
print(final.output_text)
Para o pedido do exemplo, a resposta poderá informar que ele está em transporte e apresentar a previsão. Se o número não existir, o modelo recebe encontrado: false e deve explicar isso, sem criar dados. A qualidade depende tanto das instruções quanto da clareza do resultado devolvido pela função.
Em uma aplicação real, encapsule esse ciclo em uma função ou serviço. Também trate falhas temporárias da API interna, tempo limite e respostas sem chamada de ferramenta. O modelo pode responder diretamente quando não precisa consultar nada.
Proteja ferramentas com efeitos reais
Consultar um pedido é uma operação de leitura e oferece um bom primeiro projeto. Cancelar compra, enviar e-mail, emitir reembolso ou alterar cadastro exige controles adicionais. Um argumento bem formatado não prova que o usuário tem permissão para a ação.
Adote estas regras antes de disponibilizar ferramentas de escrita:
- Autorização fora do modelo: valide usuário, conta e escopo no backend.
- Confirmação explícita: mostre o efeito, o destinatário e o valor antes de uma ação irreversível.
- Idempotência: use uma chave para impedir reembolsos ou envios duplicados.
- Limites: defina valores máximos, formatos, tempo de execução e quantidade de chamadas.
- Logs seguros: registre ferramenta, resultado e duração, removendo chaves e dados sensíveis.
- Privilégio mínimo: cada ferramenta deve acessar apenas os dados necessários.
Também separe ferramentas de leitura das que causam efeitos. Comece com consultas, observe erros reais e só depois adicione ações. Function calling não transforma o modelo em autoridade; ele cria uma interface estruturada entre a intenção do usuário e uma capacidade controlada pelo sistema.
Checklist prático e próximo passo
Antes de colocar o fluxo em produção, confirme:
- o nome e a descrição da ferramenta são específicos;
- o esquema usa modo estrito, campos obrigatórios e bloqueio de propriedades extras;
- os argumentos passam por validação de formato e regra de negócio;
- apenas funções presentes na lista permitida podem ser executadas;
- o resultado referencia o
call_idrecebido; - erros viram respostas controladas, sem expor segredos;
- ações sensíveis exigem autorização, confirmação e idempotência;
- logs e métricas permitem investigar o fluxo.
Execute o exemplo, troque o número do pedido e teste três cenários: pedido existente, identificador inválido e pedido não encontrado. Depois, substitua o dicionário por uma função de leitura do seu próprio sistema. Na próxima evolução, você poderá oferecer mais de uma ferramenta e criar um roteador seguro, mantendo validação e observabilidade em cada chamada.