Python & AI Tutorials Logo
LangChain & LangGraph

3. Construa Seu Primeiro Chat CLI com Streaming

No Capítulo 1, você fez sua primeira chamada LLM e viu uma resposta completa aparecer de uma vez. No Capítulo 2, você aprendeu os fundamentos conceituais da IA agêntica e por que o LangChain existe. Agora é hora de construir algo prático: uma aplicação de chat com streaming que parece responsiva e profissional.

Por que streaming é importante: Quando você faz uma pergunta complexa a um LLM, esperar 10-30 segundos por uma resposta completa parece quebrado. Streaming permite que tokens apareçam conforme são gerados, criando um fluxo conversacional natural. Este capítulo constrói uma aplicação de chat CLI com saída em streaming, gerenciamento adequado de configuração, capacidades de depuração e tratamento robusto de erros.

O que você vai construir: Ao final deste capítulo, você terá um script chat.py funcional que:

  • Transmite respostas LLM token por token para o terminal
  • Carrega chaves de API de forma segura a partir de variáveis de ambiente
  • Lida com diferentes tipos de modelos (chat vs modelos de raciocínio) com parâmetros apropriados
  • Fornece ferramentas de depuração para inspecionar o que é realmente enviado ao LLM
  • Trata erros comuns de forma elegante (chaves de API ausentes, falhas de rede, entradas inválidas)

3.1) Crie uma Pasta de Trabalho e Instale Pacotes

Antes de escrever qualquer código, você precisa de uma estrutura de projeto limpa e as dependências corretas. Esta seção estabelece a fundação para um projeto Python sustentável.

Estrutura do Projeto

Crie um novo diretório para sua aplicação de chat:

bash
mkdir langchain-chat
cd langchain-chat

Configuração do Ambiente Python

Crie um ambiente virtual para isolar dependências:

bash
# Cria ambiente virtual
python -m venv venv
 
# Ativa (macOS/Linux)
source venv/bin/activate
 
# Ativa (Windows)
venv\Scripts\activate

Por que ambientes virtuais? O LangChain tem muitas dependências (por exemplo, SDK OpenAI, Pydantic, bibliotecas assíncronas). Um ambiente virtual garante:

  • Seu Python do sistema permanece limpo
  • Diferentes projetos podem usar diferentes versões do LangChain
  • Dependências são reproduzíveis (via requirements.txt)

Você verá (venv) no prompt do seu terminal quando ativado.

Instalando o LangChain

Instale os pacotes principais do LangChain:

bash
pip install langchain-core==1.2.7 langchain-openai==1.1.7 python-dotenv

Detalhamento dos pacotes:

  • langchain-core: Abstrações principais (mensagens, prompts, cadeias, runnables)
  • langchain-openai: Implementações específicas da OpenAI (ChatOpenAI, embeddings)
  • python-dotenv: Carrega variáveis de ambiente de arquivos .env

Nota sobre versão: Este livro usa LangChain 1.2.x a partir de janeiro de 2026. Se você estiver lendo isso no futuro, verifique a documentação do LangChain para a versão mais recente.

Verifique a Instalação

Crie um teste simples para confirmar que tudo funciona:

python
# test_install.py
try:
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    print("✓ langchain-core: OK")
    print("✓ langchain-openai: OK")
    print("\nInstalação bem-sucedida!")
except ImportError as e:
    print(f"✗ Falha na importação: {e}")
    print("Certifique-se de que seu ambiente virtual está ativado.")

Execute:

bash
python test_install.py

Saída esperada:

✓ langchain-core: OK
✓ langchain-openai: OK
 
Instalação bem-sucedida!

Se você ver "Instalação bem-sucedida!", você está pronto para prosseguir. Se você receber um erro de importação, verifique novamente que:

  • Seu ambiente virtual está ativado (procure por (venv) no seu prompt)
  • Os pacotes foram instalados com sucesso (tente executar pip list)

Criando requirements.txt

Você acabou de instalar pacotes com comandos pip install. Embora isso funcione para aprendizado, há uma maneira melhor: arquivos requirements.txt. Esta é uma prática padrão em projetos Python por várias razões:

Por que usar requirements.txt?

  • Reprodutibilidade: Outros (ou você em 6 meses) podem instalar exatamente as mesmas versões de pacotes
  • Gerenciamento claro de dependências: Veja rapidamente quais pacotes seu projeto precisa
  • Colaboração em equipe: Membros da equipe usam versões idênticas, evitando problemas de "funciona na minha máquina"
  • Automação: Servidores ou pipelines de CI/CD podem configurar o ambiente com uma linha: pip install -r requirements.txt

Crie um arquivo requirements.txt na raiz do seu projeto:

txt
# requirements.txt
langchain-core==1.2.7
langchain-openai==1.1.7
python-dotenv

Note a sintaxe:

  • ==1.2.7 fixa uma versão exata (recomendado para reprodutibilidade)
  • Sem especificador de versão (como python-dotenv) instala a versão estável mais recente
  • Linhas começando com # são comentários

Agora qualquer pessoa pode instalar todas as dependências com um único comando:

bash
pip install -r requirements.txt

Isso é muito melhor do que digitar cada pacote individualmente. Se um colega de equipe clonar seu projeto, ele só precisa:

  1. Criar um ambiente virtual
  2. Executar pip install -r requirements.txt

Não é necessário lembrar nomes ou versões de pacotes—está tudo no arquivo.

Sua Estrutura de Projeto

Após completar esta seção, sua pasta deve parecer:

langchain-chat/
├── venv/                 # Ambiente virtual (não commitar no git)
├── requirements.txt      # Lista de dependências
└── test_install.py       # Script de verificação de instalação

Próximo: A Seção 3.2 mostra como carregar chaves de API de forma segura usando arquivos .env.

3.2) Variáveis de Ambiente com .env

Chaves de API são segredos. Codificá-las diretamente no seu código é um risco de segurança (especialmente se você commitar no git). Esta seção mostra a abordagem padrão: variáveis de ambiente carregadas de um arquivo .env.

Por Que Variáveis de Ambiente?

O problema com chaves codificadas diretamente:

python
# ❌ NUNCA FAÇA ISSO
llm = ChatOpenAI(api_key="sk-proj-abc123...")

Se você commitar este código no GitHub, sua chave de API é pública. Qualquer pessoa pode usá-la, gerar cobranças na sua conta ou fazer sua chave ser revogada.

A solução: Armazene segredos em variáveis de ambiente, carregue-os em tempo de execução.

Criando o Arquivo .env

Crie um arquivo .env na raiz do seu projeto:

bash
# .env
OPENAI_API_KEY=sk-proj-sua-chave-real-aqui

Obtenha sua chave de API:

  1. Vá para platform.openai.com/api-keys
  2. Crie uma nova chave secreta
  3. Copie-a imediatamente (você não pode visualizá-la novamente)
  4. Cole no seu arquivo .env, substituindo sk-proj-sua-chave-real-aqui

Passo crítico de segurança: Antes de fazer qualquer outra coisa, proteja sua chave de API de ser commitada no git.

Crie um arquivo .gitignore na raiz do seu projeto e adicione estas linhas:

bash
# .gitignore
venv/
__pycache__/
*.pyc
.env

A linha .env diz ao git para ignorar seu arquivo de chave de API. Isso previne commitar acidentalmente segredos no controle de versão.

Sua estrutura de projeto agora:

langchain-chat/
├── venv/
├── .env                  # Sua chave de API (ignorada pelo git)
├── .gitignore           # Contém: .env, venv/, etc.
├── requirements.txt
└── test_install.py

Carregando Variáveis de Ambiente

O pacote python-dotenv carrega arquivos .env em os.environ:

python
# chat.py
import os
from dotenv import load_dotenv
 
# Carrega arquivo .env
load_dotenv()
 
# Acessa variáveis de ambiente
api_key = os.environ.get("OPENAI_API_KEY")
 
if not api_key:
    raise ValueError("OPENAI_API_KEY não encontrada no ambiente")
 
print(f"Chave de API carregada: {api_key[:8]}...")  # Mostra apenas os primeiros 8 caracteres

Como load_dotenv() funciona:

  1. Procura por um arquivo .env começando de onde você executa o script
  2. Lê cada linha no formato CHAVE=valor
  3. Adiciona cada variável a os.environ
  4. Se uma variável já estiver definida (por exemplo, pela sua plataforma de hospedagem), ela não será sobrescrita—o valor existente permanece

Usando a Chave de API com LangChain

As implementações OpenAI do LangChain (ChatOpenAI, etc.) automaticamente procuram por OPENAI_API_KEY em os.environ:

python
from langchain_openai import ChatOpenAI
 
load_dotenv()
 
# Isso automaticamente usa os.environ["OPENAI_API_KEY"]
llm = ChatOpenAI(model="gpt-4o-mini")

Convenção do LangChain: Quando você cria ChatOpenAI() sem um parâmetro api_key, ele automaticamente procura por OPENAI_API_KEY no ambiente. Este é um padrão comum nas integrações do LangChain.

Chave de API explícita (para testes ou múltiplas chaves):

python
llm = ChatOpenAI(
    model="gpt-4o-mini",
    api_key=os.environ.get("OPENAI_API_KEY")
)

Isso é útil quando você tem múltiplas chaves de API (desenvolvimento vs produção) ou quer ser explícito sobre qual chave é usada.

Variáveis de Ambiente em Produção

Em ambientes de produção (plataformas em nuvem, containers Docker), você não usa arquivos .env. Em vez disso, você configura variáveis de ambiente através das configurações da plataforma:

  • Docker: Use a flag -e ao executar containers
  • Plataformas em nuvem: Defina variáveis de ambiente nos painéis de configuração
  • CI/CD: Use ferramentas de gerenciamento de segredos

A parte importante: seu código não muda. os.environ.get("OPENAI_API_KEY") funciona da mesma forma se a variável vem de um arquivo .env ou de uma plataforma em nuvem. Cobriremos deployment em detalhes em capítulos posteriores.

Verifique Sua Configuração

Para confirmar que tudo está funcionando, você pode testar o código de carregamento de variável de ambiente mostrado anteriormente. Se seu arquivo .env estiver configurado corretamente, os.environ.get("OPENAI_API_KEY") retornará sua chave de API.

Se os.environ.get("OPENAI_API_KEY") retornar None, verifique que:

  1. Você chamou load_dotenv() antes de acessar a variável de ambiente
  2. .env existe na raiz do projeto
  3. OPENAI_API_KEY=sk-proj-... está escrito corretamente em .env
  4. Você está executando a partir do diretório raiz do projeto

Próximo: A Seção 3.3 implementa o loop de chat real com saída em streaming.

3.3) Implementando o Loop de Chat com Saída em Streaming

Agora você vai construir o loop de chat principal. Esta seção introduz streaming - a diferença chave entre um chatbot lento e um responsivo.

Entendendo Streaming

Sem streaming (abordagem do Capítulo 1):

python
response = llm.invoke("Escreva um ensaio de 500 palavras sobre IA")
print(response.content)  # Espera 20 segundos, então o ensaio inteiro aparece

Com streaming:

python
for chunk in llm.stream("Escreva um ensaio de 500 palavras sobre IA"):
    print(chunk.content, end="", flush=True)  # Tokens aparecem conforme são gerados

Por que streaming é importante:

  • Feedback imediato: Em vez de olhar para uma tela em branco por 20 segundos, você vê palavras aparecendo imediatamente
  • Sensação de conversa natural: Assim como falar com uma pessoa - respostas vêm progressivamente, não todas de uma vez
  • Economize tempo e dinheiro: Se o LLM começar a dar a resposta errada, você pode pará-lo cedo em vez de esperar por uma resposta completa (inútil)
  • Melhor depuração: Ao construir aplicações, você pode identificar problemas (como erros de formatação) conforme eles acontecem, não após uma longa espera

O que streaming realmente é: Streaming é entrega incremental do mesmo texto de resposta. Ele não expõe raciocínio oculto ou processos internos do modelo - apenas mostra saída parcial conforme ela se torna disponível da API. Pense nisso como baixar um arquivo: você vê o progresso conforme os pedaços chegam, mas o conteúdo do arquivo é o mesmo se você baixá-lo todo de uma vez ou em partes.

Nota sobre limites de chunks: Chunks não são garantidos de alinhar com palavras ou frases. A API envia tokens em pequenos lotes para eficiência, então um chunk pode ser "Ol", "á! Co", "mo", " posso", " ajudar", "?". Isso é normal e esperado - não tente analisar significado de chunks individuais.

O Loop de Chat Básico

Aqui está um loop de chat com streaming mínimo:

python
# chat.py
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
def main():
    load_dotenv()
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    print("Chat iniciado. Digite 'quit' ou 'exit' para parar.\n")
    
    while True:
        user_input = input("Você: ")
        
        if user_input.lower() in ["quit", "exit"]:
            print("Até logo!")
            break
        
        print("Assistente: ", end="", flush=True)
        
        for chunk in llm.stream([HumanMessage(content=user_input)]):
            print(chunk.content, end="", flush=True)
        
        print("\n")
 
if __name__ == "__main__":
    main()

Como isso funciona:

  1. while True:: Loop infinito para conversa contínua
  2. input("Você: "): Obtém entrada do usuário do terminal
  3. llm.stream([HumanMessage(...)]): Transmite resposta do LLM
  4. Saída em streaming com parâmetros especiais:
    • end="": Não adiciona nova linha após cada chunk (mantém saída na mesma linha)
    • flush=True: Força saída imediata para o terminal sem buffer

Por que [HumanMessage(content=user_input)]?

Os modelos de chat do LangChain esperam uma lista de mensagens, não uma string bruta. Cada mensagem tem um papel:

  • HumanMessage: Entrada do usuário
  • AIMessage: Resposta do LLM
  • SystemMessage: Instruções para o LLM (coberto no Capítulo 4)

Mesmo para uma única mensagem de usuário, você passa uma lista: [HumanMessage(content="Olá")].

Limitação chave - Conversas de turno único: Este loop de chat é intencionalmente sem estado. Cada requisição envia apenas a mensagem atual, não o histórico de conversa anterior. Isso significa:

  • O LLM não vai lembrar o que você perguntou antes
  • Perguntas de acompanhamento como "E quanto à sua população?" não funcionarão depois de perguntar "Qual é a capital da França?"
  • Esta é uma característica fundamental do LLM - eles não têm memória a menos que você forneça contexto explicitamente

Exemplo da limitação:

Você: Qual é a capital da França?
Assistente: Paris.
Você: Qual é sua população?
Assistente: Não tenho contexto suficiente. Sobre qual cidade você está perguntando?

O loop while True fornece continuidade de UX (você pode continuar conversando), mas cada turno é independente. Vindo no Capítulo 8: Implementaremos memória de conversa armazenando e reenviando histórico de mensagens com cada requisição.

Executando o Loop de Chat

bash
python chat.py

Exemplo de interação:

Chat iniciado. Digite 'quit' ou 'exit' para parar.
 
Você: O que é LangChain?
Assistente: LangChain é um framework para desenvolver aplicações alimentadas por modelos de linguagem. Ele fornece ferramentas para gerenciamento de prompts, cadeias, agentes e memória.
 
Você: Me dê um exemplo simples
Assistente: Aqui está um exemplo básico: ...
 
Você: quit
Até logo!

Entendendo a API de Streaming

O que é um "chunk"?

Cada chunk é um objeto AIMessageChunk com:

  • content: Os tokens de texto gerados
  • response_metadata: Informações do modelo, contagens de tokens, etc.
python
for chunk in llm.stream([HumanMessage(content="Olá")]):
    print(f"Chunk: {chunk}")
    print(f"Conteúdo: {chunk.content}")
    print(f"Tipo: {type(chunk)}")

Saída:

Chunk: content='Olá' response_metadata={'model_provider': 'openai', ...}
Conteúdo: Olá
Tipo: <class 'langchain_core.messages.ai.AIMessageChunk'>
 
Chunk: content='!' response_metadata={...}
Conteúdo: !
Tipo: <class 'langchain_core.messages.ai.AIMessageChunk'>
 
Chunk: content=' Como' response_metadata={...}
Conteúdo:  Como
Tipo: <class 'langchain_core.messages.ai.AIMessageChunk'>

Acumulando a Resposta Completa

Às vezes você precisa da resposta completa (para logging, testes ou processamento adicional):

python
def chat_with_accumulation():
    load_dotenv()
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    user_input = input("Você: ")
    
    full_response = ""
    print("Assistente: ", end="", flush=True)
    
    for chunk in llm.stream([HumanMessage(content=user_input)]):
        print(chunk.content, end="", flush=True)
        full_response += chunk.content
    
    print("\n")
    
    # Agora você tem a resposta completa
    print(f"[DEBUG] Comprimento da resposta completa: {len(full_response)} caracteres")
    return full_response

Este padrão é comum quando você precisa:

  • Salvar a conversa em um banco de dados
  • Analisar a resposta para dados estruturados
  • Calcular uso de tokens ou custos
LLMChatLoopUserLLMChatLoopUserloop[Múltiplos chunks]"O que é LangChain?"stream([HumanMessage(...)])chunk: "Lang"print("Lang")chunk: "Chain "print("Chain ")chunk: "é um"print("é um")Stream completo"\n" (nova linha)Próxima entrada...

Sua estrutura de projeto após esta seção:

langchain-chat/
├── venv/
├── .env
├── .gitignore
├── requirements.txt
├── test_install.py
└── chat.py              # Loop de chat com streaming (novo!)

Próximo: A Seção 3.4 mostra como lidar com diferentes tipos de modelos com configuração inteligente de parâmetros.

3.4) Configuração Inteligente: Lidando com Parâmetros para Modelos de Raciocínio vs Chat

A OpenAI oferece dois tipos de modelos com diferentes capacidades e mecanismos de controle:

Modelos de chat (gpt-4o, gpt-4o-mini):

  • Rápidos e conversacionais
  • Suportam temperature para controlar aleatoriedade e criatividade
  • Melhores para tarefas gerais, escrita criativa, codificação de rotina

Modelos de raciocínio (o1, o3, GPT-5):

  • Mais lentos mas mais lógicos e consistentes
  • NÃO suportam temperature (usam raciocínio interno em vez disso)
  • Melhores para matemática complexa, planejamento multi-etapas, análise formal

A diferença chave: Modelos de chat usam amostragem probabilística (você controla a aleatoriedade), enquanto modelos de raciocínio usam lógica interna determinística (o modelo controla seu próprio processo de raciocínio).

Entendendo Temperature (Apenas Modelos de Chat)

O que é temperature?

Temperature é um número entre 0.0 e 2.0 que controla quão criativas são as respostas do modelo. Em valores baixos (perto de 0), você obtém respostas consistentes e previsíveis. Em valores altos (perto de 2.0), você obtém respostas criativas e variadas. Pense nisso como um "dial de criatividade".

Como funciona: Ao gerar cada palavra, o modelo vê muitas palavras possíveis seguintes com diferentes probabilidades. Temperature afeta como o modelo escolhe:

  • Temperature baixa (0.0): Quase sempre escolhe a palavra de maior probabilidade → respostas consistentes e focadas
  • Temperature alta (2.0): Mais provável de escolher palavras de menor probabilidade → respostas diversas e criativas

Importante: Temperature só funciona com modelos de chat (gpt-4o, gpt-4o-mini). Ela não se aplica a modelos de raciocínio (GPT-5, o1, o3), que usam lógica interna em vez de amostragem probabilística.

Guia de valores de temperature:

  • 0.0: Altamente determinístico, focado e consistente

    • Use para: Q&A factual, geração de código de rotina, saída estruturada
    • Mesma entrada → saída quase idêntica toda vez
    • Exemplo: "Quanto é 2+2?" → Sempre "4"
  • 0.7–1.0: Comportamento de amostragem padrão (padrão é 1.0)

    • Use para: conversa geral, explicações, respostas balanceadas
    • Variação moderada em fraseado e exemplos
    • Exemplo: "Explique fotossíntese" → Redação diferente cada vez, mesma informação central
  • 1.2–2.0: Mais criativo e diverso, menos previsível

    • Use para: escrita criativa, brainstorming, ideação
    • Alta variação em tom, estrutura e redação
    • Exemplo: "Escreva um poema sobre a lua" → Estilos muito diferentes cada vez

Nota: Valores acima de 1.0 aumentam criatividade mas podem reduzir precisão factual e coerência. Valor máximo é 2.0.

Exemplo: Impacto de temperature (apenas modelos de chat)

python
# Temperature 0.0 - determinístico, mesma resposta toda vez
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.0)
response = llm.invoke([HumanMessage(content="Quanto é 2+2?")])
print(response.content)  # Saída: 4
 
# Temperature 1.0 - comportamento padrão, leve variação possível
llm = ChatOpenAI(model="gpt-4o-mini", temperature=1.0)
response = llm.invoke([HumanMessage(content="Quanto é 2+2?")])
print(response.content)  # Saída: 4 (pode incluir breve explicação)

Para perguntas fechadas e factuais, temperature tem pouco efeito na correção.

Para tarefas abertas ou criativas, temperature influencia significativamente diversidade, tom e estilo.

O Que Acontece Se Você Usar Parâmetros de Modelo de Chat em Modelos de Raciocínio?

Depende do modelo - alguns rejeitam, outros ignoram silenciosamente:

python
# ❌ Isso falhará com modelos o3
llm = ChatOpenAI(model="o3-mini", temperature=0.7)

Erro:

BadRequestError: Temperature is not supported with this model

Modelos diferentes, políticas diferentes:

  • modelos o1 / o3: Rejeitam explicitamente parâmetros não suportados. Se temperature for incluída, a API retorna um erro 400 BadRequest imediatamente.
  • modelos GPT-5: Mais permissivos - o parâmetro é aceito mas silenciosamente ignorado. Sua requisição tem sucesso, mas temperature não tem efeito.

Por que isso importa: Sempre verifique qual modelo você está usando e configure parâmetros adequadamente. Usar parâmetros errados pode causar erros ou falhar silenciosamente, desperdiçando tempo de depuração.

Como Controlar o Comportamento de Modelos de Raciocínio

Agora você sabe que modelos de chat usam temperature e modelos de raciocínio não. Então como você controla modelos de raciocínio?

Modelos de raciocínio são ajustados através de design de prompt, não parâmetros:

  • Modelos de raciocínio não expõem temperature ou controles similares
  • Em vez disso, você guia o comportamento por como você escreve o prompt:
    • Instruções explícitas: "Pense passo a passo", "Mostre seu trabalho"
    • Restrições como regras: "Você não deve assumir...", "Sempre verifique..."
    • Requisitos estruturados: "Saída em formato JSON", "Inclua raciocínio antes da resposta"
    • Lógica de decisão: "Se condição A, então faça X, caso contrário faça Y"

Exemplo: Parâmetros de chat vs Prompts de raciocínio

python
# ❌ Abordagem de chat - não funcionará com modelos de raciocínio
llm = ChatOpenAI(model="o3-mini", temperature=0.5)
# Erro: BadRequestError: Temperature is not supported
 
# ✅ Abordagem de raciocínio - guie através da estrutura do prompt
prompt = """
Resolva este problema passo a passo:
1. Declare o que você sabe
2. Mostre seus cálculos
3. Verifique sua resposta
 
Problema: Se x + 5 = 12, qual é x?
"""
llm = ChatOpenAI(model="o3-mini")
response = llm.invoke([HumanMessage(content=prompt)])
print(response.content)

Saída:

1. O que eu sei: x + 5 = 12
2. Cálculos: x = 12 - 5 = 7
3. Verificação: 7 + 5 = 12 ✓
 
Resposta: x = 7

Insight chave: Modelos de chat são controlados por parâmetros, modelos de raciocínio são controlados por prompts.

Tabela de Decisão de Seleção de Modelo

Agora que você entende como controlar ambos os tipos de modelos, aqui está quando usar cada um:

Tipo de TarefaModelo RecomendadoPor Quê
Conversa geralgpt-4o-miniRápido, baixo custo, conversacional
Q&A simplesgpt-4o-miniSuficiente para busca factual
Escrita criativagpt-4o-mini (temp 0.8–1.0)Temperature habilita criatividade
Geração de códigoGPT-5Melhor planejamento lógico
Raciocínio complexoGPT-5Otimizado para lógica multi-etapas
Problemas matemáticoso3 / o1Modelos de raciocínio dedicados
Planejamento multi-etapasGPT-5Forte em planejamento de longo horizonte
Análise formal (legal/política)o3Estritamente determinístico

Trade-offs de Custo e Latência

Entender os trade-offs práticos ajuda você a escolher o modelo certo para seu caso de uso:

Tipo de ModeloVelocidade (Latência Típica)Custo (Relativo)Melhor Para
gpt-4o-miniMuito rápido (<2s)Muito baixoConversa geral, tarefas simples
gpt-4oRápido (1–4s)MédioChat de maior qualidade, tarefas multimodais
GPT-5Moderado (3–8s)AltoRaciocínio complexo, planejamento
o1 / o3Mais lento (5–15s+)Mais altoRaciocínio determinístico, lógica formal

Notas:

  • Velocidade reflete latência de resposta típica (varia por comprimento e complexidade do prompt)
  • Custo é uma comparação relativa - verifique preços atuais no site da OpenAI
  • Modelos de raciocínio trocam velocidade e custo por consistência e correção
  • Modelos de chat priorizam responsividade e eficiência

Quando usar modelos de raciocínio (GPT-5, o1, o3):

  • Problemas de matemática e STEM(Ciência, Tecnologia, Engenharia, Matemática) multi-etapas requerendo etapas intermediárias corretas
  • Análise lógica complexa com dependências e restrições
  • Depuração de código com múltiplas causas interagindo
  • Tarefas de planejamento com muitas regras, casos extremos ou trade-offs
  • Fluxos de trabalho de agentes requerendo consistência e pensamento de longo horizonte

Quando usar modelos de chat (gpt-4o, gpt-4o-mini):

  • Conversa geral e chat interativo
  • Q&A simples com profundidade de raciocínio limitada
  • Geração de conteúdo (blogs, resumos, escrita criativa)
  • Geração de código de rotina e tarefas boilerplate
  • Aplicações onde velocidade e custo importam mais que raciocínio profundo

Próximo: A Seção 3.5 mostra técnicas de depuração para inspecionar o que é realmente enviado ao LLM.

3.5) Depuração: Inspecionando Respostas e Uso de Tokens

Quando seu LLM se comporta inesperadamente, você precisa ver exatamente o que foi enviado e recebido. Esta seção mostra como inspecionar chamadas LLM e depurar problemas.

Por Que Depuração Importa

Cenários comuns de depuração:

  • "Por que o LLM deu esta resposta?" → Verifique o prompt exato
  • "Quanto custou esta requisição?" → Verifique uso de tokens
  • "Por que isso está tão lento?" → Meça latência
  • "Minha formatação de mensagem está correta?" → Inspecione a estrutura da mensagem

O desafio: Quando você chama llm.invoke(), você obtém um objeto de resposta. Mas o que realmente está nele? Que informação está disponível para depuração?

Entendendo o Objeto de Resposta

Antes de depurar, você precisa entender o que llm.invoke() retorna.

Estrutura básica:

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Olá")])
 
# O que está na resposta?
print(type(response))  # AIMessage
print(response.content)  # O texto real
print(response.response_metadata)  # Uso de tokens, informações do modelo, etc.

Saída:

<class 'langchain_core.messages.ai.AIMessage'>
Olá! Como posso ajudá-lo hoje?
{
  'token_usage': {
    'completion_tokens': 9,
    'prompt_tokens': 8,
    'total_tokens': 17
  },
  'model_name': 'gpt-4o-mini-2024-07-18',
  'finish_reason': 'stop',
  ...
}

Partes chave da resposta:

  • response.content: O texto que o LLM gerou
  • response.response_metadata: Dicionário com:
    • token_usage: Quantos tokens foram usados (para cálculo de custo)
    • model_name: Versão exata do modelo que respondeu
    • finish_reason: Por que a geração parou (veja seção Modo Debug para detalhes)

Acessando uso de tokens:

python
token_usage = response.response_metadata['token_usage']
print(f"Tokens de prompt: {token_usage['prompt_tokens']}")
print(f"Tokens de resposta: {token_usage['completion_tokens']}")
print(f"Total: {token_usage['total_tokens']}")

Saída:

Tokens de prompt: 8
Tokens de resposta: 9
Total: 17

Por que isso importa: Você precisa desses valores para depuração, rastreamento de custos e otimização de seus prompts.

Calculando Custos a Partir do Uso de Tokens

Uso de tokens determina custo. Cada modelo tem preços diferentes:

GPT-4o-mini (a partir de janeiro de 2026):

  • Entrada: $0.15 por 1M tokens
  • Saída: $0.60 por 1M tokens

GPT-4o:

  • Entrada: $2.50 por 1M tokens
  • Saída: $10.00 por 1M tokens

Função de cálculo de custo:

python
def calculate_cost(token_usage, model_name):
    """Calcula custo baseado no uso de tokens."""
    prompt_tokens = token_usage.get('prompt_tokens', 0)
    completion_tokens = token_usage.get('completion_tokens', 0)
    
    # Preços por 1M tokens (a partir de janeiro de 2026)
    pricing = {
        'gpt-4o-mini': {'input': 0.15, 'output': 0.60},
        'gpt-4o': {'input': 2.50, 'output': 10.00},
        'gpt-5': {'input': 1.25, 'output': 10.00},
    }
    
    if model_name not in pricing:
        return None
    
    input_cost = (prompt_tokens / 1_000_000) * pricing[model_name]['input']
    output_cost = (completion_tokens / 1_000_000) * pricing[model_name]['output']
    
    return input_cost + output_cost
 
# Exemplo
response = llm.invoke([HumanMessage(content="Explique computação quântica")])
token_usage = response.response_metadata['token_usage']
cost = calculate_cost(token_usage, "gpt-4o-mini")
print(f"Custo: ${cost:.6f}")

Saída:

Custo: $0.000123

Por que isso importa: Aplicações de produção podem lidar com 50.000+ requisições/dia. A $0.002 por requisição, isso é $3.000/mês. Use o modelo errado ou prompts inchados, e custos saltam para $30.000/mês. Um bug de loop de retry pode queimar milhares durante a noite. Rastreie uso de tokens desde o primeiro dia.

Habilitando Modo Debug (Quando Você Precisa de Detalhes Brutos da API)

O objeto de resposta e wrapper customizado lidam com a maioria das necessidades de depuração. Mas às vezes você precisa ver exatamente o que o LangChain envia para a OpenAI - a requisição e resposta JSON brutas.

Quando você pode precisar disso:

  • Depurar formatação de mensagens do LangChain
  • Verificar se parâmetros da API estão definidos corretamente
  • Investigar erros inesperados da API
  • Entender o payload exato da API

O LangChain tem logging de debug integrado via langchain_core.globals:

python
from langchain_core.globals import set_debug
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
set_debug(True)
 
# Agora todas as chamadas LLM imprimirão informações de debug
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Olá")])

Saída:

[llm/start] [llm:ChatOpenAI] Entering LLM run with input:
{
  "prompts": [
    "Human: Olá"
  ]
}
[llm/end] [llm:ChatOpenAI] [1.45s] Exiting LLM run with output:
{
  "generations": [
    [
      {
        "text": "Olá! Como posso ajudá-lo hoje?",
        "generation_info": {
          "finish_reason": "stop",
          "logprobs": null
        },
        "type": "ChatGeneration",
        ...
      }
    ]
  ],
  "llm_output": {
    "token_usage": {
      "completion_tokens": 9,
      "prompt_tokens": 8,
      "total_tokens": 17,
      ...
    },
    "model_provider": "openai",
    "model_name": "gpt-4o-mini-2024-07-18",
    ...
  },
}

Nota: Formato de saída varia por provedor LLM. Este exemplo mostra a estrutura da OpenAI.

O que a saída de debug revela:

Modo debug mostra o fluxo completo de comunicação LangChain → OpenAI:

1. Transformação de formato de mensagem:

python
# Seu código
[HumanMessage(content="Olá")]
 
# O que você vê na saída de debug
{
  "prompts": ["Human: Olá"]
}

Modo debug mostra como o LangChain representa sua mensagem internamente antes de enviar ao LLM.

2. Status de conclusão de geração:

python
"finish_reason": "stop"

Por que a geração terminou:

  • "stop": O modelo completou a resposta naturalmente
  • "length": A resposta foi cortada porque atingiu o limite de max_tokens
  • "tool_calls": O modelo terminou a geração produzindo instruções de chamada de ferramenta em vez de uma resposta de texto final (Capítulo 12)
  • "content_filter": A resposta foi bloqueada ou suprimida devido a regras de segurança ou moderação de conteúdo

Se você ver "length", aumente max_tokens para obter a resposta completa.

3. Detalhamento de uso de tokens:

python
"token_usage": {
  "completion_tokens": 9,
  "prompt_tokens": 8,
  "total_tokens": 17,
  "completion_tokens_details": {
    "reasoning_tokens": 0  # Para modelos de raciocínio (o1/o3, etc.)
  },
  "prompt_tokens_details": {
    "cached_tokens": 0  # Cache de prompt (economiza custos)
  }
}

Além de contagens básicas, você pode ver:

  • reasoning_tokens: Etapas de raciocínio interno (apenas para modelos de raciocínio)
  • cached_tokens: Quantos tokens de prompt foram servidos do cache (reduz custo)

4. Versão do modelo e fingerprint:

python
"model_name": "gpt-4o-mini-2024-07-18",
"system_fingerprint": "fp_8bbc38b4db"
  • model_name: Versão snapshot exata (explica por que respostas mudam ao longo do tempo)
  • system_fingerprint: ID de configuração de backend da OpenAI (muda quando eles atualizam sistemas)

5. Tempo de requisição:

python
[llm/end] [llm:ChatOpenAI] [1.56s]

O [1.45s] mostra duração total da requisição—útil para identificar consultas lentas.

Próximo: A Seção 3.6 mostra como lidar com erros comuns de forma elegante.

3.6) Lidando com Falhas (Simule e corrija erros comuns)

Aplicações LLM de produção enfrentam modos de falha previsíveis: credenciais ausentes, timeouts de rede, limites de taxa e entradas inválidas. Esta seção mostra como lidar com esses erros de forma elegante e construir aplicações robustas desde o primeiro dia.

Os Seis Erros Comuns

1. Chave de API Ausente

Quando isso acontece: Você tenta criar uma instância ChatOpenAI, mas OPENAI_API_KEY não está definida no seu ambiente.

Exemplo:

python
# arquivo .env não existe, ou OPENAI_API_KEY não está definida
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Olá")])

Erro que você verá:

OpenAIError: The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variable

Como corrigir:

  1. Verifique se seu arquivo .env existe na raiz do projeto
  2. Verifique se o nome da chave é exatamente OPENAI_API_KEY (erro comum: OPENAPI_KEY)
  3. Certifique-se de que load_dotenv() é chamado antes de criar o LLM

2. Chave de API Errada

Quando isso acontece: Seu arquivo .env contém uma chave de API inválida, expirada ou copiada incorretamente.

Exemplo:

python
# .env tem: OPENAI_API_KEY=sk-invalid-key-12345
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Olá")])

Erro que você verá:

AuthenticationError: Incorrect API key provided

Como corrigir:

  1. Vá para https://platform.openai.com/api-keys
  2. Verifique se sua chave ainda está ativa (não revogada ou expirada)
  3. Gere uma nova chave se necessário
  4. Copie a chave inteira cuidadosamente (erro comum: faltando primeiros/últimos caracteres)
  5. Cole em .env sem espaços extras:
bash
OPENAI_API_KEY=sk-proj-chaveexataaqui

3. Falhas de Rede

Quando isso acontece: Sua conexão de internet cai, ou os servidores da OpenAI estão temporariamente inacessíveis durante uma requisição.

Exemplo:

python
# WiFi desconecta no meio da requisição, ou API da OpenAI está fora do ar
response = llm.invoke([HumanMessage(content="Olá")])

Erro que você verá:

APIConnectionError: Connection error

Como corrigir:

  1. Verifique sua conexão de internet
  2. Verifique status da OpenAI em https://status.openai.com

4. Limites de Taxa

Quando isso acontece: Você envia muitas requisições em um curto período e excede sua cota de API.

Exemplo:

python
# Enviando 1000 requisições instantaneamente
for i in range(1000):
    llm.invoke([HumanMessage(content=f"Requisição {i}")])

Erro que você verá:

RateLimitError: Rate limit reached for requests

Como corrigir:

  1. Verifique seus limites de taxa em https://platform.openai.com/account/limits
  2. Atualize seu plano se precisar de limites maiores
  3. Use processamento em lote para cargas de trabalho grandes (coberto no Capítulo 6)

5. Nome de Modelo Inválido

Quando isso acontece: Você especifica um nome de modelo que não existe ou não está disponível no seu plano.

Exemplo:

python
llm = ChatOpenAI(model="gpt-99-ultra")  # Não existe
response = llm.invoke([HumanMessage(content="Olá")])

Erro que você verá:

NotFoundError: The model `gpt-99-ultra` does not exist or you do not have access to it

Como corrigir:

  1. Verifique modelos disponíveis no seu plano em https://platform.openai.com/docs/models

6. Limite de Tokens Excedido

Quando isso acontece: Seu prompt é muito longo e excede a janela de contexto máxima do modelo.

Exemplo:

python
# Criando um prompt de 1 milhão de caracteres
huge_prompt = "x" * 1_000_000
response = llm.invoke([HumanMessage(content=huge_prompt)])

Erro que você verá:

BadRequestError: This model's maximum context length is 128000 tokens. However, your messages resulted in 250000 tokens.

Como corrigir:

  1. Verifique comprimento de entrada antes de enviar
  2. Conheça os limites do seu modelo:
    • gpt-4o-mini: 128K tokens
    • gpt-4o: 128K tokens
    • gpt-5: 400K tokens
  3. Para documentos longos, use chunking ou sumarização (coberto no Capítulo 9)

Próximos passos: O Capítulo 4 mostra como projetar templates de prompt reutilizáveis que separam engenharia de prompt do código da aplicação.