MÓDULO 2.7

⚙️ Primeiro Agente Real com LangChain

AgentExecutor vs. LCEL, memória, tools LangChain e o projeto completo: assistente de email que lê, classifica e rascunha respostas — com código funcional e técnicas de debug sem LangSmith.

6
Tópicos
50
Minutos
Intermediário
Nível
Projeto
Tipo
1

🧩 Por que LangChain existe

Construir um agente do zero significa implementar o loop de tool calling, gerenciar o histórico de mensagens, parsear as respostas do LLM e tratar erros — tudo do zero. LangChain abstrai esse boilerplate em componentes reutilizáveis para que você foque na lógica do agente.

O que LangChain abstrai para você

Sem LangChain (do zero)

  • • Implementar o loop while tool_calls
  • • Parsear tool calls do JSON da API
  • • Gerenciar lista de mensagens manualmente
  • • Implementar retry em falhas
  • • Gerenciar memória e contexto
  • • Tratar erros de cada provider diferente

Com LangChain

  • ✓ AgentExecutor/LCEL cuida do loop
  • ✓ Tool calling automático
  • ✓ Memory objects para histórico
  • ✓ Retry configurável
  • ✓ Memory integrada ao prompt
  • ✓ Interface unificada para OpenAI/Anthropic/etc.

Quando LangChain ajuda

Prototipagem rápida, integração com muitos LLMs, uso de integrações prontas do ecossistema (vectorstores, loaders, tools)

Quando LangChain complica

Agentes muito simples (1-2 tools), quando você precisa de controle total do loop, quando abstração vira complexidade acidental

Alternativas

LlamaIndex (RAG-first), smolagents (Hugging Face, mais simples), Pydantic AI (type-safe), LangGraph (grafos de agentes)

2

🆚 AgentExecutor vs. LCEL

LangChain tem dois paradigmas de construção de agentes: o AgentExecutor (API legada mas ainda dominante em tutoriais) e o LCEL (LangChain Expression Language — o jeito novo, mais flexível). Você vai encontrar ambos na prática.

AgentExecutor (jeito antigo)

from langchain.agents import create_react_agent, AgentExecutor

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")

agent = create_react_agent(llm, tools, prompt)

executor = AgentExecutor(agent=agent, tools=tools)

result = executor.invoke({"input": "Pergunta"})

  • + Fácil de entender para iniciantes
  • + Muito material na internet
  • - API considerada legada
  • - Menos observabilidade por padrão

LCEL (jeito novo)

from langchain import hub

from langchain.agents import create_react_agent

from langchain_core.runnables import RunnableWithMessageHistory

prompt = hub.pull("hwchase17/react")

agent = create_react_agent(llm, tools, prompt)

# Composição com operador |

chain = prompt | llm.bind_tools(tools) | parser

  • + Composição declarativa com operador |
  • + Streaming nativo
  • + Melhor integração com LangSmith
  • - Curva de aprendizado maior

💡 Qual usar?

Para aprender: comece com AgentExecutor (mais simples). Para produção nova: prefira LCEL (mais observável e flexível). Para projetos legados: não migre por migrar — só migre quando tiver ganho claro.

3

💾 Adicionando memória ao agente

Sem memória, cada invocação do agente começa do zero. Para agentes conversacionais, você precisa persistir o histórico entre chamadas. LangChain tem memória in-memory (sessão atual) e persistente (entre sessões com SQLite ou Redis).

Implementação completa com RunnableWithMessageHistory

from langchain_community.chat_message_histories import SQLChatMessageHistory

from langchain_core.runnables.history import RunnableWithMessageHistory

# Persistência em SQLite — memória entre sessões!

def get_session_history(session_id: str):

return SQLChatMessageHistory(session_id, "sqlite:///memoria.db")

agent_with_memory = RunnableWithMessageHistory(

agent_executor,

get_session_history,

input_messages_key="input",

history_messages_key="chat_history"

)

# session_id permite múltiplos usuários com memórias separadas

resposta = agent_with_memory.invoke(

{"input": "Olá! Meu nome é Ana."},

config={"configurable": {"session_id": "usuario-123"}}

)

# Na próxima chamada, o agente já sabe que o usuário se chama Ana!

Opções de persistência de memória

In-Memory

ChatMessageHistory — apenas na sessão Python atual. Perde tudo quando o processo reinicia.

SQLite

SQLChatMessageHistory — persiste entre sessões. Ideal para desenvolvimento e escala pequena.

Redis

RedisChatMessageHistory — escala horizontalmente. Ideal para produção com múltiplos workers.

4

🛠️ Tools com LangChain

LangChain tem duas formas de criar tools: o decorator @tool (simples, gera schema da docstring) e StructuredTool (para tools com múltiplos parâmetros tipados). A docstring vira a description da tool no schema JSON.

@tool decorator — simples

from langchain.tools import tool

@tool

def buscar_tempo(cidade: str) -> str:

"""Retorna a previsão do tempo atual para

uma cidade. Use quando o usuário perguntar

sobre clima ou temperatura."""

return f"25°C em {cidade}, ensolarado"

# Schema gerado automaticamente!

StructuredTool — múltiplos parâmetros

from langchain.tools import StructuredTool

from pydantic import BaseModel

class BuscaInput(BaseModel):

cidade: str

dias: int = 1

tool = StructuredTool.from_function(

func=buscar_tempo_fn,

args_schema=BuscaInput,

handle_tool_error=True # não quebra o agente

)

Tools built-in do LangChain

TavilySearchResults

Busca web com Tavily

DuckDuckGoSearchRun

Busca gratuita

WikipediaQueryRun

Busca na Wikipedia

PythonREPLTool

Executa Python

5

📧 Projeto: assistente de email

O projeto completo: um agente que lê emails, classifica por prioridade e rascunha respostas para os urgentes. Integra memória, structured output e tools customizadas em um sistema funcional e extensível.

Código completo comentado

from langchain.agents import AgentExecutor, create_react_agent

from langchain_openai import ChatOpenAI

from langchain.tools import tool

from pydantic import BaseModel

from langchain import hub

INBOX = [

{"id": 1, "de": "joao@empresa.com", "assunto": "URGENTE: servidor caiu",

"corpo": "Produção está fora desde 14h. Clientes reportando erro 500."},

{"id": 2, "de": "newsletter@startup.com", "assunto": "10 dicas de produtividade",

"corpo": "Confira as melhores práticas para..."},

]

class ClassificacaoEmail(BaseModel):

categoria: str # "urgente", "normal", "spam"

prioridade: int # 1-5

requer_resposta: bool

@tool

def ler_inbox() -> str:

"""Lista todos os emails na caixa de entrada. Use para ver os emails disponíveis."""

return str([{"id": e["id"], "assunto": e["assunto"], "de": e["de"]} for e in INBOX])

@tool

def ler_email(email_id: int) -> str:

"""Lê o conteúdo completo de um email pelo ID."""

email = next((e for e in INBOX if e["id"] == email_id), None)

return str(email) if email else "Email não encontrado"

@tool

def rascunhar_resposta(email_id: int, tom: str = "profissional") -> str:

"""Rascunha uma resposta para o email. Tom pode ser: profissional, amigável, urgente."""

email = next((e for e in INBOX if e["id"] == email_id), None)

if not email: return "Email não encontrado"

return f"Rascunho para {email['de']}: [Texto personalizado baseado em '{email['assunto']}' em tom {tom}]"

tools = [ler_inbox, ler_email, rascunhar_resposta]

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)

prompt = hub.pull("hwchase17/react")

agent = create_react_agent(llm, tools, prompt)

executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

resultado = executor.invoke({

"input": "Verifique minha caixa de entrada, classifique os emails e rascunhe resposta para os urgentes."

})

6

🐛 Depurando sem LangSmith

LangSmith é ótimo, mas nem sempre está disponível ou configurado. Com verbose=True e callbacks customizados, você tem visibilidade completa do que o agente está fazendo direto no terminal — sem dependência externa.

Callback handler para debug completo

from langchain.callbacks.base import BaseCallbackHandler

from datetime import datetime

class DebugCallbackHandler(BaseCallbackHandler):

def on_llm_start(self, serialized, prompts, **kwargs):

print(f"\n[LLM START] {datetime.now().strftime('%H:%M:%S')}")

print(f"Input tokens estimados: ~{len(prompts[0])//4}")

def on_llm_end(self, response, **kwargs):

output = response.generations[0][0].text

print(f"[LLM END] Output: {output[:200]}...")

def on_tool_start(self, serialized, input_str, **kwargs):

print(f"\n[TOOL CALL] {serialized['name']}")

print(f"Input: {input_str}")

def on_tool_end(self, output, **kwargs):

print(f"[TOOL RESULT] {str(output)[:300]}")

executor = AgentExecutor(

agent=agent, tools=tools,

callbacks=[DebugCallbackHandler()] # ← adicione aqui

)

verbose=True — o mais simples

Adicione verbose=True ao AgentExecutor para ver a trace completa de Thought/Action/Observation no console:

executor = AgentExecutor(

agent=agent, tools=tools,

verbose=True # ← basta isso

)

Identificando o passo que falhou

  1. Leia o output do verbose do fim para o início
  2. Ache o último "Thought" antes do erro
  3. Veja qual "Action" foi chamada e com quais inputs
  4. Veja o "Observation" (resultado da tool)
  5. Decida: bug no prompt, na tool ou na lógica?

Resumo do Módulo 2.7

Por que LangChain — abstrai o loop, histórico, retry e interfaces de múltiplos LLMs; foca você na lógica
AgentExecutor vs. LCEL — AgentExecutor para aprender, LCEL para produção nova; ambos coexistem
Memória — RunnableWithMessageHistory + SQLite = memória persistente entre sessões em 5 linhas
Tools LangChain — @tool decorator gera schema da docstring; StructuredTool para múltiplos parâmetros
Projeto email — agente completo com 3 tools customizadas, classificação e rascunho de resposta
Debug sem LangSmith — verbose=True + BaseCallbackHandler dão visibilidade completa sem dependência externa

Próximo Módulo:

2.8 — Depuração e Rastreamento: LangSmith em 3 minutos, lendo traces, identificando root causes e boas práticas de logging em produção.