Quando a IA finalmente para de só “conversar” e começa a “fazer”
Outubro de 2024. Roberto Fonseca, CTO da FinanceHub (fintech B2B, 95 funcionários), estava animado mas cético.
Ele havia lido sobre “function calling” – a capacidade de LLMs não apenas gerarem texto, mas também executarem ações reais chamando APIs, consultando bancos de dados, enviando emails.
Roberto queria automatizar análise de crédito. O processo manual demorava 2-3 horas por cliente:
- Analista consulta 4 sistemas diferentes
- Cruza informações
- Calcula scores
- Gera relatório
- Envia recomendação
“Se function calling funciona mesmo, posso automatizar isso,” pensou Roberto.
Mas tinha dúvidas:
- Como LLM “sabe” qual função chamar?
- É confiável? E se chamar a função errada?
- Como lidar com APIs que têm side effects (transferências, por exemplo)?
Roberto decidiu testar.
MVP (2 semanas, R$ 8 mil):
Implementou agent com 5 “tools”:
consultar_serasa()- score de créditobuscar_historico_cliente()- transações anteriorescalcular_score_interno()- algoritmo proprietáriogerar_relatorio()- monta PDFenviar_email()- notifica time comercial
Resultado do teste (50 análises):
- Precisão: 94% (comparado com análise manual)
- Tempo: 2h30min → 8 minutos (-95%)
- Custo por análise: R$ 85 → R$ 3,20 (-96%)
- Erros: 3 casos (6%) - agent chamou função com parâmetro errado
“Funciona, mas precisa de safeguards,” concluiu Roberto.
Após 3 meses em produção (com melhorias):
- 840 análises automatizadas
- Precisão: 97%
- Erros: 0,7% (detectados e revertidos automaticamente)
- ROI: 1.250% no primeiro ano
Este artigo explica exatamente como function calling funciona, como implementar com segurança, e quando usar (ou não usar).
O que é function calling e por que muda o jogo
Antes do function calling: LLMs só geravam texto
Limitação fundamental dos LLMs (até 2023):
LLMs eram “apenas” geradores de texto. Não importa quão inteligente fosse a resposta, o output era sempre… texto.
Exemplo - o que você PODIA fazer:
Usuário: "Qual o saldo da conta 12345?"
LLM: "Para consultar o saldo, você pode:
1. Acessar [link do sistema]
2. Entrar com suas credenciais
3. Buscar a conta 12345
4. Visualizar o saldo na tela principal"
O LLM gerava instruções, mas você precisava executar.
O que você NÃO PODIA fazer:
Usuário: "Qual o saldo da conta 12345?"
LLM: [consulta banco de dados]
LLM: "O saldo atual da conta 12345 é R$ 14.892,34"
LLM não tinha como executar a consulta.
Workaround (pré-function calling):
Desenvolvedores faziam parsing manual do output:
response = llm.generate("Qual ação tomar para o cliente X?")
if "consultar saldo" in response.lower():
saldo = api.buscar_saldo(cliente_id)
# continuar processo...
elif "enviar email" in response.lower():
# extrair destinatário do texto (regex/outro LLM)
# enviar email...
Problemas:
- Frágil (quebrava se LLM mudasse formato da resposta)
- Impreciso (parsing de texto é ambíguo)
- Trabalhoso (escrever parser para cada ação)
Com function calling: LLMs podem executar ações
O que mudou:
LLMs modernos (GPT-4, Claude 3, etc.) podem retornar chamadas de função estruturadas ao invés de apenas texto.
Fluxo simplificado:
1. Você: descreve funções disponíveis para o LLM
2. Usuário: faz pergunta
3. LLM: decide qual função chamar (retorna JSON estruturado)
4. Você: executa a função
5. Você: envia resultado de volta pro LLM
6. LLM: processa resultado e responde ao usuário
Exemplo concreto:
# 1. Definir funções disponíveis
tools = [
{
"type": "function",
"function": {
"name": "buscar_saldo",
"description": "Consulta saldo de uma conta bancária",
"parameters": {
"type": "object",
"properties": {
"conta_id": {
"type": "string",
"description": "ID da conta (ex: '12345')"
}
},
"required": ["conta_id"]
}
}
}
]
# 2. Usuário pergunta
messages = [
{"role": "user", "content": "Qual o saldo da conta 12345?"}
]
# 3. LLM decide chamar função
response = openai.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools
)
# LLM retorna:
# {
# "tool_calls": [{
# "function": {
# "name": "buscar_saldo",
# "arguments": '{"conta_id": "12345"}'
# }
# }]
# }
# 4. Você executa a função
saldo = database.buscar_saldo("12345") # R$ 14.892,34
# 5. Envia resultado de volta
messages.append({
"role": "tool",
"tool_call_id": response.tool_calls[0].id,
"content": str(saldo)
})
# 6. LLM processa e responde
final_response = openai.chat.completions.create(
model="gpt-4",
messages=messages
)
# LLM: "O saldo atual da conta 12345 é R$ 14.892,34"
Vantagens sobre parsing manual:
- ✅ Estruturado (JSON confiável)
- ✅ Parâmetros validados automaticamente
- ✅ Menos ambiguidade
- ✅ LLM “entende” o que cada função faz
Como LLM decide qual função chamar
Mistério resolvido:
LLM não “entende” funções magicamente. Você precisa descrever cada função.
Descrição de função bem feita:
{
"name": "buscar_cliente",
"description": "Busca informações detalhadas de um cliente no CRM. Use quando precisar de dados como email, telefone, endereço, histórico de compras.",
"parameters": {
"type": "object",
"properties": {
"cliente_id": {
"type": "string",
"description": "ID único do cliente (pode ser CPF/CNPJ ou ID interno)"
},
"incluir_historico": {
"type": "boolean",
"description": "Se true, inclui histórico de compras dos últimos 12 meses",
"default": false
}
},
"required": ["cliente_id"]
}
}
LLM lê essas descrições e decide:
“Usuário perguntou sobre histórico de compras do cliente 123. A função buscar_cliente com parâmetro incluir_historico=true resolve isso.”
Como melhorar decisões do LLM:
-
Descrições claras e específicas: ❌
"description": "Busca cliente"✅"description": "Busca dados de cliente no CRM incluindo nome, email, telefone e endereço. Use quando precisar de informações de contato." -
Exemplos no description:
"description": "Calcula frete. Exemplo: origem='São Paulo', destino='Rio de Janeiro', peso_kg=50 → retorna R$ 85.00" -
Enums quando opções são limitadas:
"status": { "type": "string", "enum": ["ativo", "inativo", "pendente"], "description": "Status do pedido" } -
Validation hints:
"cpf": { "type": "string", "description": "CPF do cliente (formato: 000.000.000-00)", "pattern": "^\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2}$" }
Implementação prática passo a passo
Setup básico com OpenAI
Exemplo completo: Sistema de consulta de estoque
from openai import OpenAI
import json
client = OpenAI()
# 1. Simular banco de dados
def buscar_estoque(produto_id: str) -> dict:
"""Simula consulta ao banco de dados"""
database = {
"PROD001": {"nome": "Notebook Dell", "quantidade": 15, "preco": 3500.00},
"PROD002": {"nome": "Mouse Logitech", "quantidade": 243, "preco": 89.90},
"PROD003": {"nome": "Teclado Mecânico", "quantidade": 0, "preco": 450.00},
}
return database.get(produto_id, {"erro": "Produto não encontrado"})
def reservar_produto(produto_id: str, quantidade: int) -> dict:
"""Simula reserva de produto"""
estoque = buscar_estoque(produto_id)
if "erro" in estoque:
return {"sucesso": False, "mensagem": "Produto não existe"}
if estoque["quantidade"] < quantidade:
return {"sucesso": False, "mensagem": f"Estoque insuficiente. Disponível: {estoque['quantidade']}"}
return {"sucesso": True, "mensagem": f"Reservado {quantidade} unidades de {estoque['nome']}"}
# 2. Definir tools
tools = [
{
"type": "function",
"function": {
"name": "buscar_estoque",
"description": "Consulta quantidade disponível e preço de um produto no estoque",
"parameters": {
"type": "object",
"properties": {
"produto_id": {
"type": "string",
"description": "ID do produto (formato: PROD###)"
}
},
"required": ["produto_id"]
}
}
},
{
"type": "function",
"function": {
"name": "reservar_produto",
"description": "Reserva uma quantidade específica de produto para um cliente",
"parameters": {
"type": "object",
"properties": {
"produto_id": {"type": "string", "description": "ID do produto"},
"quantidade": {"type": "integer", "description": "Quantidade a reservar"}
},
"required": ["produto_id", "quantidade"]
}
}
}
]
# 3. Função para executar tool calls
def executar_funcao(nome_funcao: str, argumentos: dict):
if nome_funcao == "buscar_estoque":
return buscar_estoque(**argumentos)
elif nome_funcao == "reservar_produto":
return reservar_produto(**argumentos)
else:
return {"erro": "Função não encontrada"}
# 4. Loop principal
def processar_pedido(mensagem_usuario: str):
messages = [{"role": "user", "content": mensagem_usuario}]
while True:
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=messages,
tools=tools
)
message = response.choices[0].message
# Se LLM retornou resposta final (não tool call)
if not message.tool_calls:
return message.content
# LLM quer chamar função(ões)
messages.append(message)
for tool_call in message.tool_calls:
nome_funcao = tool_call.function.name
argumentos = json.loads(tool_call.function.arguments)
print(f"🔧 Chamando: {nome_funcao}({argumentos})")
resultado = executar_funcao(nome_funcao, argumentos)
print(f"✅ Resultado: {resultado}")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(resultado)
})
# 5. Testar
print(processar_pedido("Tem notebook Dell disponível? Quero reservar 5 unidades"))
Output esperado:
🔧 Chamando: buscar_estoque({'produto_id': 'PROD001'})
✅ Resultado: {'nome': 'Notebook Dell', 'quantidade': 15, 'preco': 3500.0}
🔧 Chamando: reservar_produto({'produto_id': 'PROD001', 'quantidade': 5})
✅ Resultado: {'sucesso': True, 'mensagem': 'Reservado 5 unidades de Notebook Dell'}
Sim, temos 15 unidades de Notebook Dell disponíveis. Reservei 5 unidades para você!
Implementando com Claude (Anthropic)
Claude usa sintaxe ligeiramente diferente (“tools” ao invés de “functions”):
import anthropic
client = anthropic.Anthropic()
# Definir tools (similar mas formato diferente)
tools = [
{
"name": "buscar_estoque",
"description": "Consulta quantidade e preço de produto",
"input_schema": {
"type": "object",
"properties": {
"produto_id": {
"type": "string",
"description": "ID do produto"
}
},
"required": ["produto_id"]
}
}
]
# Usar
response = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "Consulta estoque PROD001"}]
)
# Processar tool use (similar ao OpenAI)
if response.stop_reason == "tool_use":
tool_use = response.content[1] # Claude retorna em content
nome = tool_use.name
argumentos = tool_use.input
# executar função...
Parallel function calling: múltiplas chamadas simultâneas
Cenário:
Usuário pergunta: “Quero saber estoque de PROD001, PROD002 e PROD003”
Sem parallel calling:
LLM chama: buscar_estoque("PROD001") [espera]
LLM chama: buscar_estoque("PROD002") [espera]
LLM chama: buscar_estoque("PROD003") [espera]
Total: 3 round-trips
Com parallel calling:
LLM chama simultaneamente:
- buscar_estoque("PROD001")
- buscar_estoque("PROD002")
- buscar_estoque("PROD003")
Total: 1 round-trip
Implementação:
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=messages,
tools=tools,
parallel_tool_calls=True # habilita chamadas paralelas
)
# Executar todas em paralelo (usando asyncio ou threading)
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
for tool_call in response.choices[0].message.tool_calls:
nome = tool_call.function.name
args = json.loads(tool_call.function.arguments)
future = executor.submit(executar_funcao, nome, args)
futures.append((tool_call.id, future))
# Coletar resultados
for tool_call_id, future in futures:
resultado = future.result()
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"content": json.dumps(resultado)
})
Vantagem: Reduz latência significativamente quando múltiplas chamadas são necessárias.
Casos de uso práticos
1. Assistente de análise financeira
Tools implementadas:
tools = [
{
"name": "buscar_transacoes",
"description": "Retorna transações de uma conta em período específico",
"parameters": {
"conta_id": str,
"data_inicio": str, # "YYYY-MM-DD"
"data_fim": str
}
},
{
"name": "calcular_gastos_categoria",
"description": "Soma gastos por categoria (alimentação, transporte, etc)",
"parameters": {
"transacoes": list,
"categoria": str
}
},
{
"name": "gerar_grafico",
"description": "Gera gráfico de gastos",
"parameters": {
"dados": dict,
"tipo": str # "pizza", "barras", "linha"
}
},
{
"name": "comparar_com_mes_anterior",
"description": "Compara gastos atuais com mês anterior",
"parameters": {
"conta_id": str,
"mes_atual": str
}
}
]
Exemplo de uso:
Usuário: "Quanto gastei com alimentação em janeiro e como compara com dezembro?"
Agent (autonomamente):
1. buscar_transacoes(conta_id="...", data_inicio="2026-01-01", data_fim="2026-01-31")
2. calcular_gastos_categoria(transacoes=..., categoria="alimentação") → R$ 1.240
3. buscar_transacoes(conta_id="...", data_inicio="2025-12-01", data_fim="2025-12-31")
4. calcular_gastos_categoria(transacoes=..., categoria="alimentação") → R$ 980
5. gerar_grafico(dados={"Jan": 1240, "Dez": 980}, tipo="barras")
Resposta: "Você gastou R$ 1.240 em alimentação em janeiro, um aumento de 26,5%
em relação a dezembro (R$ 980). [gráfico anexado]"
2. Agent de customer success
Tools:
tools = [
{
"name": "buscar_cliente",
"description": "Dados do cliente (nome, empresa, plano, etc)",
"parameters": {"cliente_id": str}
},
{
"name": "buscar_tickets_abertos",
"description": "Lista tickets de suporte ainda abertos",
"parameters": {"cliente_id": str}
},
{
"name": "buscar_uso_produto",
"description": "Métricas de uso (logins, features usadas, etc)",
"parameters": {"cliente_id": str, "periodo_dias": int}
},
{
"name": "calcular_health_score",
"description": "Calcula score de saúde do cliente (0-100)",
"parameters": {"dados_uso": dict, "dados_suporte": dict}
},
{
"name": "criar_tarefa_cs",
"description": "Cria tarefa para equipe de CS",
"parameters": {
"cliente_id": str,
"prioridade": str, # "baixa", "média", "alta"
"descricao": str
}
},
{
"name": "enviar_email",
"description": "Envia email proativo ao cliente",
"parameters": {
"destinatario": str,
"assunto": str,
"corpo": str
}
}
]
Exemplo: Análise proativa de churn risk
# Executado automaticamente toda segunda-feira
for cliente in clientes_ativos:
prompt = f"""
Analise a saúde do cliente {cliente.id} e tome ações proativas se necessário.
Critérios de alerta:
- Health score menos de 50: criar tarefa ALTA para CS
- Tickets abertos mais de 5 dias: escalar
- Uso caiu 40%+ vs mês anterior: enviar email check-in
"""
agent.processar(prompt)
# Agent autonomamente:
# 1. Busca dados do cliente
# 2. Calcula health score
# 3. Decide ação apropriada
# 4. Executa (cria tarefa OU envia email OU escala ticket)
3. Automação de RFPs (Request for Proposal)
Tools:
tools = [
{
"name": "buscar_em_rfps_anteriores",
"description": "Busca respostas em RFPs anteriores por palavra-chave ou tópico",
"parameters": {"query": str, "top_k": int}
},
{
"name": "buscar_em_documentacao",
"description": "Busca informação técnica na documentação do produto",
"parameters": {"query": str}
},
{
"name": "consultar_equipe_tecnica",
"description": "Envia pergunta para equipe técnica via Slack (para perguntas que não têm resposta documentada)",
"parameters": {"pergunta": str, "canal": str}
},
{
"name": "gerar_secao_rfp",
"description": "Gera rascunho de seção do RFP baseado em contexto fornecido",
"parameters": {
"secao": str,
"contexto": str,
"tom": str # "técnico", "comercial", "executivo"
}
}
]
Exemplo:
RFP pergunta: "Descreva suas capacidades de integração com sistemas ERP"
Agent:
1. buscar_em_rfps_anteriores(query="integração ERP", top_k=5)
→ encontra 3 respostas anteriores
2. buscar_em_documentacao(query="integrações disponíveis")
→ encontra lista de APIs
3. gerar_secao_rfp(
secao="Integrações ERP",
contexto=[respostas anteriores + documentação],
tom="técnico"
)
→ gera rascunho de resposta
Output: Rascunho pronto para revisão humana em 2 minutos (vs 45 minutos manual)
Segurança e safeguards: evitando desastres
Perigos de function calling sem proteção
Cenários de risco:
- Ações destrutivas:
# Função perigosa SEM proteção
def deletar_cliente(cliente_id: str):
database.delete(f"DELETE FROM clientes WHERE id = {cliente_id}")
return {"sucesso": True}
Risco: LLM pode interpretar mal e deletar cliente errado.
- Side effects financeiros:
# Função com impacto financeiro
def processar_reembolso(cliente_id: str, valor: float):
payment_api.refund(cliente_id, valor)
return {"sucesso": True}
Risco: Bug pode custar milhares de reais.
- Acesso não autorizado:
# Função sem validação de permissões
def buscar_dados_cliente(cliente_id: str):
return database.query(f"SELECT * FROM clientes WHERE id = {cliente_id}")
Risco: Agent pode acessar dados que usuário não deveria ver.
- Injection attacks:
# Vulnerável a SQL injection
def buscar_produtos(nome: str):
return database.query(f"SELECT * FROM produtos WHERE nome LIKE '%{nome}%'")
Risco: Agent poderia ser manipulado a executar SQL malicioso.
Safeguards obrigatórios
1. Classificação de funções por risco:
class RiskLevel:
READ_ONLY = 1 # Sem side effects (consultas)
LOW_RISK = 2 # Side effects reversíveis (criar rascunho)
MEDIUM_RISK = 3 # Side effects importantes (enviar email)
HIGH_RISK = 4 # Side effects críticos (transferência)
DESTRUCTIVE = 5 # Irreversível (deletar dados)
tools_config = {
"buscar_cliente": {"risk": RiskLevel.READ_ONLY, "requires_approval": False},
"enviar_email": {"risk": RiskLevel.MEDIUM_RISK, "requires_approval": False},
"processar_reembolso": {"risk": RiskLevel.HIGH_RISK, "requires_approval": True},
"deletar_cliente": {"risk": RiskLevel.DESTRUCTIVE, "requires_approval": True},
}
2. Sistema de aprovação para ações críticas:
def executar_funcao_com_safeguard(nome_funcao, argumentos):
config = tools_config[nome_funcao]
# Funções de alto risco requerem aprovação
if config["risk"] >= RiskLevel.HIGH_RISK:
if not config["requires_approval"]:
raise SecurityError("Função de alto risco requer aprovação")
# Solicitar aprovação humana
aprovado = solicitar_aprovacao_humana(
funcao=nome_funcao,
argumentos=argumentos,
contexto=obter_contexto_decisao()
)
if not aprovado:
return {"erro": "Ação não aprovada por humano"}
# Executar função
return executar_funcao(nome_funcao, argumentos)
3. Dry-run mode:
DRY_RUN = True # Habilitar para testes
def enviar_email(destinatario, assunto, corpo):
if DRY_RUN:
print(f"[DRY RUN] Enviaria email:")
print(f" Para: {destinatario}")
print(f" Assunto: {assunto}")
print(f" Corpo: {corpo[:100]}...")
return {"sucesso": True, "dry_run": True}
# Execução real
email_api.send(destinatario, assunto, corpo)
return {"sucesso": True}
4. Validação rigorosa de parâmetros:
def processar_reembolso(cliente_id: str, valor: float):
# Validações
if not re.match(r"^CLI\d{6}$", cliente_id):
raise ValueError("cliente_id inválido")
if valor <= 0 or valor mais de 10000:
raise ValueError("Valor deve estar entre R$ 0,01 e R$ 10.000")
# Verificar se cliente existe
cliente = database.buscar_cliente(cliente_id)
if not cliente:
raise ValueError("Cliente não encontrado")
# Verificar se tem saldo para reembolso
if cliente.saldo_disponivel < valor:
raise ValueError(f"Saldo insuficiente. Disponível: R$ {cliente.saldo_disponivel}")
# Tudo OK, executar
payment_api.refund(cliente_id, valor)
audit_log.registrar("reembolso", cliente_id, valor)
return {"sucesso": True, "valor": valor}
5. Audit logging completo:
def executar_funcao_com_audit(nome_funcao, argumentos, contexto):
# Log antes de executar
audit_id = audit_log.iniciar(
funcao=nome_funcao,
argumentos=argumentos,
usuario=contexto.get("usuario"),
timestamp=datetime.now(),
conversation_id=contexto.get("conversation_id")
)
try:
resultado = executar_funcao(nome_funcao, argumentos)
# Log de sucesso
audit_log.finalizar(audit_id, sucesso=True, resultado=resultado)
return resultado
except Exception as e:
# Log de erro
audit_log.finalizar(audit_id, sucesso=False, erro=str(e))
raise
6. Rate limiting:
from collections import defaultdict
import time
call_counts = defaultdict(list)
def executar_funcao_com_rate_limit(nome_funcao, argumentos):
# Limite: máximo 10 chamadas por minuto por função
now = time.time()
minute_ago = now - 60
# Limpar chamadas antigas
call_counts[nome_funcao] = [
ts for ts in call_counts[nome_funcao] if ts > minute_ago
]
# Verificar limite
if len(call_counts[nome_funcao]) >= 10:
raise RateLimitError(f"{nome_funcao} atingiu limite de 10 chamadas/min")
# Registrar chamada
call_counts[nome_funcao].append(now)
# Executar
return executar_funcao(nome_funcao, argumentos)
Caso real: FinanceHub automatiza análise de crédito
Implementação e desafios
Contexto:
- Análise de crédito manual: 2h30min por cliente
- Volume: 40-60 análises/semana
- Custo: R$ 85 por análise (analista sênior)
Tools implementadas (6 tools):
consultar_serasa(cpf)- Score de crédito externobuscar_historico_transacoes(cliente_id, meses)- Transações passadascalcular_score_interno(dados)- Algoritmo proprietáriobuscar_protestos(cpf)- Consulta Serasa/SPCgerar_relatorio_pdf(dados)- Monta relatório formatadoenviar_notificacao(destinatario, mensagem)- Notifica time comercial
Desafios encontrados:
1. LLM chamava funções desnecessárias:
Problema: Para CPF inválido, LLM chamava todas as funções mesmo assim,
gerando custo (APIs de Serasa custam R$ 0,80 por consulta)
Solução: Validar CPF ANTES de chamar qualquer API externa
def validar_cpf_antes(cpf: str):
if not validar_cpf(cpf):
return {
"erro": "CPF inválido",
"instrucao_para_llm": "Informe ao usuário que CPF é inválido e solicite correção. NÃO chame outras funções."
}
return {"cpf_valido": True}
2. Ordem de chamadas importava:
Problema: LLM às vezes chamava calcular_score_interno() ANTES de buscar_historico_transacoes(),
resultando em score baseado em dados incompletos
Solução: Adicionar dependências explícitas nas descrições
{
"name": "calcular_score_interno",
"description": """
Calcula score interno de crédito.
IMPORTANTE: Só chame DEPOIS de:
1. consultar_serasa()
2. buscar_historico_transacoes()
3. buscar_protestos()
O cálculo depende desses dados estarem disponíveis.
"""
}
3. Falhas em APIs externas quebravam processo:
Problema: Se Serasa API falhava (timeout, erro 500), agent travava
Solução: Fallback e tratamento de erros explícito
def consultar_serasa_com_fallback(cpf: str):
try:
return serasa_api.consultar(cpf)
except TimeoutError:
return {
"erro": "Serasa timeout",
"instrucao_para_llm": "Serasa indisponível. Prossiga com análise baseada apenas em dados internos e indique que score externo não foi possível."
}
except Exception as e:
return {
"erro": str(e),
"instrucao_para_llm": "Erro ao consultar Serasa. Baseie análise apenas em dados internos."
}
Resultados mensurados
Após 3 meses em produção:
| Métrica | Manual | Com Agent | Variação |
|---|---|---|---|
| Tempo médio | 2h 30min | 8 min | -95% |
| Custo por análise | R$ 85 | R$ 3,20 | -96% |
| Precisão | 96%* | 97% | +1% |
| Análises/semana | 50 | 180 | +260% |
| Erros críticos | 2% | 0,7% | -65% |
*Precisão manual medida em auditoria de 200 análises
Erros identificados e corrigidos:
| Tipo de Erro | Ocorrências | Causa | Solução |
|---|---|---|---|
| Parâmetro errado em API | 12 casos | LLM passava CPF formatado (com pontos) quando API esperava só números | Função wrapper faz sanitização |
| Chamada desnecessária de API paga | 8 casos | LLM consultava Serasa mesmo com dados recentes em cache | Adicionar tool verificar_cache() |
| Score calculado com dados incompletos | 5 casos | Ordem de chamadas incorreta | Dependências explícitas em descriptions |
| Timeout não tratado | 3 casos | API externa lenta | Timeout de 5s + fallback |
ROI detalhado:
Investimento:
- Desenvolvimento: R$ 38.000 (3 semanas, 2 devs)
- Testes e ajustes: R$ 12.000 (2 semanas)
- APIs (Serasa, etc): R$ 420/mês
- Infra (servidores): R$ 280/mês
- Total ano 1: R$ 58.400
Retorno ano 1:
- Economia em analistas: 130 análises/mês × R$ 81,80 = R$ 10.634/mês = R$ 127.608/ano
- Aumento de capacidade permite fechar mais clientes: estimados R$ 85.000/ano adicionais
- Total: R$ 212.608/ano
ROI: 264% Payback: 4,4 meses
Checklist: quando function calling faz sentido
Sinais de que você precisa de function calling
- Processo envolve consultar múltiplos sistemas/APIs
- Tarefas requerem dados em tempo real (não podem ser pré-processados)
- Decisões dependem de informações que mudam frequentemente
- Humanos gastam tempo significativo “colhendo dados” antes de analisar
- Processo tem etapas claras mas ordem pode variar
- Volume é alto o suficiente para justificar automação (mais de 100/mês)
Sinais de que você NÃO precisa (ainda)
- Processo é 100% linear e padronizado (automação tradicional basta)
- Dados necessários são estáticos/raramente mudam
- Ações são todas de alto risco (muito perigoso automatizar)
- Volume é muito baixo (menos de 20/mês)
- Processo muda toda semana (função calling requer estabilidade)
Perguntas de segurança obrigatórias
Antes de implementar function calling em produção:
- Todas as funções têm validação de parâmetros?
- Funções de alto risco requerem aprovação humana?
- Há audit logging de todas as chamadas?
- Funções com side effects têm dry-run mode para testes?
- Rate limiting está implementado?
- Há rollback para ações críticas?
- Monitora ção alerta sobre comportamento anômalo?
Se qualquer resposta é “não”, NÃO vá para produção ainda.
Conclusão: function calling é o que torna IA realmente útil
Function calling é a diferença entre IA que “conversa sobre” e IA que “faz”.
Três aprendizados principais:
-
Descrições claras são 80% do sucesso
- LLM decide baseado nas descriptions
- Invista tempo escrevendo descriptions detalhadas
- Inclua dependências, exemplos, constraints
-
Safeguards não são opcionais
- Funções de alto risco DEVEM ter aprovação humana
- Audit logging é obrigatório
- Validação rigorosa de parâmetros salva de desastres
-
Comece read-only, evolua gradualmente
- Primeiras funções: apenas consultas (sem side effects)
- Depois: ações reversíveis (criar rascunhos)
- Por último: ações críticas (com aprovação)
Framework de implementação:
Semana 1-2: Identificar processo e mapear funções necessárias Semana 3-4: Implementar funções read-only + testes extensivos Semana 5-6: Adicionar funções com side effects (low risk) Semana 7-8: Implementar safeguards completos Semana 9-10: Testes em staging + dry-run Semana 11-12: Produção gradual (10% → 50% → 100%)
O que fazer agora:
- Escolha 1 processo repetitivo e time-consuming
- Mapeie as “funções” que humano executa
- Implemente versão MVP (3-5 funções read-only)
- Teste exaustivamente
- Adicione funções com side effects gradualmente
- Meça impacto objetivamente
Quer ajuda para implementar function calling com segurança?
Na Orient.me, implementamos agents com function calling seguindo best practices:
- Mapeamento de processos e funções
- Implementação com safeguards completos
- Testes extensivos (100+ cenários)
- Rollout gradual e monitorado
Tempo típico: 6-12 semanas ROI médio: 380% no primeiro ano
Agende conversa gratuita para avaliar seu caso.