Se você é desenvolvedor e ainda não integrou nenhuma API de LLM em um projeto real, este artigo vai do zero ao primeiro sistema funcionando.
Sem teoria sobre transformers ou atenção multi-cabeça. Direto para o que você precisa saber para começar a construir hoje.
O modelo mental correto sobre APIs de LLM
APIs de LLM não são como APIs REST convencionais.
Numa API REST tradicional, você faz uma requisição estruturada e recebe uma resposta determinística — sempre a mesma para os mesmos inputs. GET /users/123 retorna sempre o mesmo JSON.
APIs de LLM são probabilísticas: o modelo gera o próximo token com base em probabilidades calculadas durante a inferência. O mesmo input pode gerar respostas ligeiramente (ou significativamente) diferentes a cada chamada.
# Mesmo prompt, respostas potencialmente diferentes
response1 = llm.invoke("Liste 3 vantagens de Python")
# "1. Sintaxe clara, 2. Grande ecossistema, 3. Versatilidade"
response2 = llm.invoke("Liste 3 vantagens de Python")
# "1. Facilidade de aprendizado, 2. Bibliotecas ricas, 3. Comunidade ativa"
Ambas são respostas válidas, mas são textos diferentes. Isso tem implicações para testes, caching e design de sistemas.
Também são stateless por padrão: cada requisição é independente. Se você quer manter uma conversa, você gerencia o histórico enviando-o em cada requisição.
Autenticação e configuração inicial
Todas as APIs usam autenticação por API key. A key é um secret que dá acesso total à sua conta — trate como senha.
Regra fundamental: nunca coloque a key diretamente no código.
Setup seguro
# ❌ ERRADO - key no código
client = OpenAI(api_key="sk-proj-abc123...") # NUNCA faça isso
# ✅ CORRETO - key em variável de ambiente
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
No terminal (desenvolvimento local):
export OPENAI_API_KEY="sk-proj-..."
export ANTHROPIC_API_KEY="sk-ant-..."
export GOOGLE_API_KEY="..."
Em produção, use secrets manager:
- AWS: AWS Secrets Manager ou Parameter Store
- GCP: Secret Manager
- Azure: Key Vault
- Alternativa cloud-agnostic: Doppler, Infisical
# Exemplo com AWS Secrets Manager
import boto3
import json
def get_secret(secret_name):
client = boto3.client('secretsmanager', region_name='us-east-1')
response = client.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
api_key = get_secret('prod/openai-api-key')['OPENAI_API_KEY']
Primeira chamada: OpenAI (GPT)
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o-mini", # modelo mais barato para começar
messages=[
{
"role": "system",
"content": "Você é um assistente especializado em legislação trabalhista brasileira."
},
{
"role": "user",
"content": "Qual o prazo para pagamento de férias?"
}
],
temperature=0.3, # baixa = mais determinístico
max_tokens=500, # limite de output
timeout=30 # timeout de requisição
)
print(response.choices[0].message.content)
print(f"Tokens usados: {response.usage.total_tokens}")
print(f"Custo estimado: ${response.usage.total_tokens * 0.00015 / 1000:.6f}")
Anatomia da requisição
model: qual modelo usar. Opções comuns:
gpt-4o: mais capaz, mais caro ($2.50/$10 por 1M tokens input/output)gpt-4o-mini: barato e bom para maioria dos casos ($0.15/$0.60 por 1M tokens)gpt-3.5-turbo: legacy, sendo descontinuado
messages: array com o histórico da conversa
system: instruções sobre o comportamento do assistenteuser: mensagem do usuárioassistant: respostas anteriores do modelo (para manter contexto)
temperature (0-2): controla aleatoriedade
0: mais determinístico (sempre escolhe o token mais provável)1: padrão, balanceado2: muito criativo/aleatório
max_tokens: limite de tokens na resposta
- Sempre defina um limite para evitar respostas infinitas
- 1 token ≈ 0,75 palavras em inglês, ≈ 0,5 palavras em português
timeout: tempo máximo de espera pela resposta (segundos)
Mantendo histórico de conversa (sistema stateless)
Como a API não guarda o histórico, você gerencia:
# Estado da conversa (pode ser salvo em DB, Redis, etc.)
messages = [
{"role": "system", "content": "Você é um assistente de atendimento da Empresa XYZ."}
]
def chat(user_input: str) -> str:
# Adiciona mensagem do usuário
messages.append({"role": "user", "content": user_input})
# Chama a API com todo o histórico
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
temperature=0.7,
max_tokens=300
)
# Extrai resposta
assistant_message = response.choices[0].message.content
# Adiciona resposta ao histórico
messages.append({"role": "assistant", "content": assistant_message})
return assistant_message
# Uso
print(chat("Qual é o prazo de entrega?"))
# "O prazo padrão é de 5 a 7 dias úteis..."
print(chat("E para São Paulo capital?")) # Contexto da pergunta anterior é mantido
# "Para São Paulo capital, o prazo é reduzido para 2 a 3 dias úteis..."
Problema: histórico cresce indefinidamente
A cada pergunta, o histórico aumenta. Você paga pelos tokens de todo o histórico em cada requisição.
Solução 1: Limite de mensagens
MAX_MESSAGES = 10 # Mantém só últimas 5 perguntas + 5 respostas
def trim_messages(messages):
# Sempre mantém a system message
system_msg = messages[0]
recent_msgs = messages[-(MAX_MESSAGES-1):]
return [system_msg] + recent_msgs
Solução 2: Summarização periódica
def summarize_conversation(messages):
# A cada 20 mensagens, resume a conversa
if len(messages) > 20:
summary_prompt = f"""
Resuma a conversa abaixo em 2-3 parágrafos mantendo informações importantes:
{format_messages(messages)}
"""
summary = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": summary_prompt}],
max_tokens=300
).choices[0].message.content
# Reseta histórico com o resumo
return [
messages[0], # system message
{"role": "assistant", "content": f"Contexto anterior: {summary}"}
]
return messages
Structured output: extraindo dados estruturados (JSON confiável)
Para extrair informações de documentos ou gerar outputs estruturados, use response_format.
Exemplo: extração de dados de contrato
from pydantic import BaseModel
from typing import List, Optional
class ContratoInfo(BaseModel):
partes: List[str]
valor_total: float
moeda: str
prazo_vigencia_dias: int
data_inicio: str # YYYY-MM-DD
data_termino: str
tem_clausula_rescisao: bool
tem_clausula_multa: bool
valor_multa: Optional[float] = None
# Usar o novo structured outputs da OpenAI
response = client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "Extraia as informações do contrato fornecido. Se algum campo não estiver presente, use null."
},
{
"role": "user",
"content": f"Contrato: {texto_do_contrato}"
}
],
response_format=ContratoInfo,
temperature=0 # determinístico para extração
)
# Acesso type-safe aos dados
info = response.choices[0].message.parsed
print(f"Partes: {', '.join(info.partes)}")
print(f"Valor: {info.moeda} {info.valor_total:,.2f}")
print(f"Vigência: {info.prazo_vigencia_dias} dias")
if info.tem_clausula_multa and info.valor_multa:
print(f"Multa por rescisão: {info.moeda} {info.valor_multa:,.2f}")
Anthropic (Claude)
Claude tem uma API similar mas com algumas diferenças:
import anthropic
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
message = client.messages.create(
model="claude-3-5-haiku-20241022", # mais barato
max_tokens=500,
system="Você é um especialista em análise de documentos fiscais brasileiros.",
messages=[
{
"role": "user",
"content": "Qual é a diferença entre ICMS e ISS?"
}
]
)
print(message.content[0].text)
print(f"Tokens: input={message.usage.input_tokens}, output={message.usage.output_tokens}")
Diferenças principais vs OpenAI:
systemé parâmetro separado (não vai no arraymessages)max_tokensé obrigatóriocontentretorna array de blocos (texto, imagens, tool use)- Modelos:
claude-3-5-sonnet-20241022(mais capaz),claude-3-5-haiku-20241022(mais barato)
Google (Gemini)
import google.generativeai as genai
genai.configure(api_key=os.environ.get("GOOGLE_API_KEY"))
model = genai.GenerativeModel("gemini-1.5-flash")
response = model.generate_content("Explique RAG para um gestor não técnico em 3 parágrafos.")
print(response.text)
# Conversa com histórico
chat = model.start_chat(history=[])
response1 = chat.send_message("Qual é a capital do Brasil?")
response2 = chat.send_message("Quantos habitantes tem?")
print(response2.text) # Usa contexto da pergunta anterior
Tratamento de erros (o que você vai enfrentar)
Rate limits (429)
Você excedeu o limite de requisições por minuto/dia.
Solução: retry com backoff exponencial
import time
from openai import RateLimitError, APITimeoutError, APIError
def call_with_retry(messages, max_retries=3):
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
timeout=30,
max_tokens=500
)
return response.choices[0].message.content
except RateLimitError as e:
if attempt == max_retries - 1:
raise
wait_time = (2 ** attempt) * 1 # 1s, 2s, 4s
print(f"Rate limit atingido. Aguardando {wait_time}s...")
time.sleep(wait_time)
except APITimeoutError:
if attempt == max_retries - 1:
raise
print(f"Timeout. Tentando novamente...")
time.sleep(2)
except APIError as e:
if e.status_code >= 500: # Erro do servidor
if attempt == max_retries - 1:
raise
time.sleep(2)
else: # Erro do cliente (400, 401, etc.)
raise
raise Exception("Falha após todas as tentativas")
Context length exceeded
Você enviou mais tokens do que o modelo suporta.
from tiktoken import encoding_for_model
def count_tokens(text: str, model: str = "gpt-4o-mini") -> int:
encoding = encoding_for_model(model)
return len(encoding.encode(text))
def trim_to_fit(text: str, max_tokens: int, model: str = "gpt-4o-mini") -> str:
encoding = encoding_for_model(model)
tokens = encoding.encode(text)
if len(tokens) <= max_tokens:
return text
# Corta os tokens e decodifica
return encoding.decode(tokens[:max_tokens])
# Uso
documento_grande = load_documento()
tokens = count_tokens(documento_grande)
if tokens > 120000: # limite do gpt-4o-mini
documento_grande = trim_to_fit(documento_grande, 100000) # margem de segurança
Boas práticas para produção
1. Sempre defina max_tokens
Sem limite, uma resposta pode gerar tokens indefinidamente.
# ❌ Perigoso
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
# Sem max_tokens!
)
# ✅ Seguro
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
max_tokens=1000 # Define limite claro
)
2. Logue inputs e outputs
Essencial para debug, auditoria e identificação de comportamentos inesperados.
import logging
import json
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def call_llm_with_logging(messages, model="gpt-4o-mini"):
logger.info(f"LLM Request: {json.dumps(messages, ensure_ascii=False)}")
response = client.chat.completions.create(
model=model,
messages=messages,
max_tokens=500
)
output = response.choices[0].message.content
logger.info(f"LLM Response: {output}")
logger.info(f"Tokens used: {response.usage.total_tokens}")
return output
3. Valide a saída antes de usar
LLMs podem retornar formatos inesperados, mesmo com structured output.
def extract_and_validate(document: str) -> Optional[ContratoInfo]:
try:
response = client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"Extraia: {document}"}],
response_format=ContratoInfo
)
info = response.choices[0].message.parsed
# Validações de negócio
if info.valor_total <= 0:
logger.warning("Valor total inválido")
return None
if info.prazo_vigencia_dias <= 0:
logger.warning("Prazo inválido")
return None
return info
except Exception as e:
logger.error(f"Falha na extração: {e}")
return None
4. Monitore custos
Configure alertas de billing. Um bug em loop pode gerar milhares de requisições.
class CostTracker:
def __init__(self):
self.total_tokens = 0
self.total_cost = 0
def track(self, model: str, usage):
# Preços por 1M tokens (maio 2026)
prices = {
"gpt-4o-mini": {"input": 0.15, "output": 0.60},
"gpt-4o": {"input": 2.50, "output": 10.00},
"claude-3-5-haiku-20241022": {"input": 0.80, "output": 4.00},
}
price = prices.get(model, prices["gpt-4o-mini"])
cost = (
(usage.input_tokens * price["input"] / 1_000_000) +
(usage.output_tokens * price["output"] / 1_000_000)
)
self.total_tokens += usage.total_tokens
self.total_cost += cost
if self.total_cost > 100: # Alerta se ultrapassar $100
logger.warning(f"ALERTA: Custo acumulado: ${self.total_cost:.2f}")
# Uso
tracker = CostTracker()
response = client.chat.completions.create(...)
tracker.track("gpt-4o-mini", response.usage)
5. Use streaming para UX responsiva
Em vez de esperar a resposta completa, mostre o texto sendo gerado:
def chat_with_streaming(user_input: str):
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": user_input}],
stream=True, # Habilita streaming
max_tokens=500
)
full_response = ""
for chunk in stream:
if chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
print(content, end="", flush=True)
full_response += content
print() # Nova linha no final
return full_response
# Uso
response = chat_with_streaming("Explique o que é machine learning")
# O texto aparece palavra por palavra, não tudo de uma vez
Comparação rápida de provedores
| Aspecto | OpenAI (GPT) | Anthropic (Claude) | Google (Gemini) |
|---|---|---|---|
| Melhor modelo | GPT-4o | Claude 3.5 Sonnet | Gemini 1.5 Pro |
| Modelo barato | GPT-4o-mini ($0.15/$0.60) | Claude 3.5 Haiku ($0.80/$4.00) | Gemini 1.5 Flash (grátis até limite) |
| Contexto máximo | 128k tokens | 200k tokens | 2M tokens |
| Structured output nativo | ✅ Sim | ⚠️ Prompt-based | ⚠️ Prompt-based |
| Function calling | ✅ Excelente | ✅ Bom | ✅ Bom |
| Imagens | ✅ GPT-4o | ✅ Todos modelos | ✅ Todos modelos |
| Bom para português | ✅ Muito bom | ✅ Excelente | ✅ Bom |
Exemplo completo: sistema RAG básico
from openai import OpenAI
import chromadb
client = OpenAI()
chroma_client = chromadb.Client()
collection = chroma_client.get_or_create_collection("docs")
def add_document(doc_text: str, doc_id: str):
# Gera embedding
embedding_response = client.embeddings.create(
input=doc_text,
model="text-embedding-3-small"
)
embedding = embedding_response.data[0].embedding
# Armazena no vector DB
collection.add(
documents=[doc_text],
embeddings=[embedding],
ids=[doc_id]
)
def rag_query(question: str, top_k: int = 3) -> str:
# 1. Embed a pergunta
query_embedding = client.embeddings.create(
input=question,
model="text-embedding-3-small"
).data[0].embedding
# 2. Buscar documentos similares
results = collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)
context = "\n\n".join(results["documents"][0])
# 3. Gerar resposta com contexto
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": f"Responda com base no contexto fornecido:\n\n{context}"
},
{"role": "user", "content": question}
],
temperature=0.3,
max_tokens=500
)
return response.choices[0].message.content
# Uso
add_document("A política de reembolso permite até R$ 100 em despesas de transporte.", "doc1")
add_document("Férias devem ser solicitadas com 30 dias de antecedência.", "doc2")
resposta = rag_query("Qual é o limite de reembolso de transporte?")
print(resposta)
Se você é desenvolvedor e quer aprender como estruturar sistemas de IA em produção além das primeiras chamadas de API (arquitetura, monitoramento, custos, segurança), entre em contato. Desenvolvemos e mantemos sistemas de IA para empresas brasileiras e compartilhamos as práticas que funcionam em produção.