Guia prático de APIs de IA para desenvolvedores que ainda não usaram

Guia introdutório para desenvolvedores: como usar as APIs da OpenAI, Anthropic e Google, autenticação, primeiros exemplos em Python, tratamento de erros e boas práticas.

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 assistente
  • user: mensagem do usuário
  • assistant: 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, balanceado
  • 2: 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 array messages)
  • max_tokens é obrigatório
  • content retorna 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

AspectoOpenAI (GPT)Anthropic (Claude)Google (Gemini)
Melhor modeloGPT-4oClaude 3.5 SonnetGemini 1.5 Pro
Modelo baratoGPT-4o-mini ($0.15/$0.60)Claude 3.5 Haiku ($0.80/$4.00)Gemini 1.5 Flash (grátis até limite)
Contexto máximo128k tokens200k tokens2M 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.

Pronto para sair do manual?

Agende o diagnóstico gratuito. Vamos mapear o gargalo, estimar o impacto e definir o primeiro resultado mensurável.

Você sai com clareza — não com um pitch de vendas.