06-01-02 — LangGraph: grafos de estado para agentes complexos

⏱ 15 minFontes validadas em: 2026-04-29

TL;DR

LangGraph transforma o loop ReAct em um grafo de estado explícito: cada nó é uma função Python, cada aresta é uma transição condicional. Você define o que acontece quando o agente precisa usar ferramentas, lidar com erros ou bifurcar o fluxo. Com checkpointing nativo, o estado persiste entre execuções — essencial para agentes de longa duração ou com interação humana no loop.

Por que grafos de estado?

Um agente ReAct simples (pense → aja → observe → repita) funciona para tarefas lineares. Mas quando você precisa de:

  • Múltiplos agentes colaborando
  • Revisão humana em pontos específicos (human-in-the-loop)
  • Retry com lógica diferente dependendo do erro
  • Subgrafos paralelos que se sincronizam

…um loop simples não basta. LangGraph modela isso como um directed graph onde o estado é tipado e persiste.

Conceitos fundamentais

StateGraph e TypedDict

O estado é um TypedDict que flui entre todos os nós. Cada nó recebe o estado atual e retorna um dict parcial para atualizar.

from typing import TypedDict, Annotated, List
from langgraph.graph import StateGraph, END
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
import operator

# Define o estado do agente
class AgentState(TypedDict):
    messages: Annotated[List[BaseMessage], operator.add]  # lista acumulativa
    next_action: str

# Nó: chama o LLM
def call_llm(state: AgentState) -> dict:
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

# Nó: executa ferramentas
def execute_tools(state: AgentState) -> dict:
    last_message = state["messages"][-1]
    # processa tool_calls do AIMessage
    results = []
    for tool_call in last_message.tool_calls:
        result = tools_by_name[tool_call["name"]].invoke(tool_call["args"])
        results.append(ToolMessage(content=str(result), tool_call_id=tool_call["id"]))
    return {"messages": results}

# Roteamento condicional
def should_continue(state: AgentState) -> str:
    last_message = state["messages"][-1]
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    return END

# Monta o grafo
graph = StateGraph(AgentState)
graph.add_node("llm", call_llm)
graph.add_node("tools", execute_tools)
graph.set_entry_point("llm")
graph.add_conditional_edges("llm", should_continue, {"tools": "tools", END: END})
graph.add_edge("tools", "llm")  # após ferramentas, volta ao LLM

app = graph.compile()

Diagrama do fluxo

graph TD START([START]) --> LLM[🤖 call_llm] LLM -->|tem tool_calls?| TOOLS[🔧 execute_tools] LLM -->|resposta final| END_NODE([END]) TOOLS --> LLM style LLM fill:#2563eb,color:#fff style TOOLS fill:#7c3aed,color:#fff style START fill:#059669,color:#fff style END_NODE fill:#dc2626,color:#fff

Checkpointing — persistência de estado

LangGraph tem um sistema de checkpointing que salva o estado após cada nó. Isso permite:

  • Pausar e retomar: o agente para, um humano aprova, o agente continua
  • Time-travel debugging: replay a partir de qualquer checkpoint
  • Multi-turn conversation: cada thread_id mantém seu histórico
from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.postgres import PostgresSaver  # para produção

# Checkpointer em memória (dev/teste)
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)

# Cada conversa tem um thread_id único
config = {"configurable": {"thread_id": "usuario-123"}}

# Primeira mensagem
result1 = app.invoke(
    {"messages": [HumanMessage(content="Qual a temperatura em São Paulo?")]},
    config=config
)

# Segunda mensagem — estado anterior é recuperado automaticamente
result2 = app.invoke(
    {"messages": [HumanMessage(content="E no Rio?")]},
    config=config
)

# Ver histórico de estados
for state in app.get_state_history(config):
    print(state.values["messages"][-1].content)

Human-in-the-loop

from langgraph.graph import interrupt

def aprovacao_humana(state: AgentState) -> dict:
    # Pausa o grafo aqui — aguarda input externo
    decisao = interrupt("Aprovar esta ação? (sim/não)")
    if decisao == "sim":
        return {"next_action": "executar"}
    return {"next_action": "cancelar"}

# Para retomar após aprovação:
app.invoke(Command(resume="sim"), config=config)
LangGraph Cloud / LangGraph Platform: versão hospedada com API REST, filas de tasks e interface de monitoramento. Alternativa ao self-host para produção rápida.

Multi-agent com LangGraph

LangGraph suporta subgrafos: um nó pode ser outro grafo compilado. Isso permite hierarquias de agentes (supervisor → worker) com estado isolado por agente.

from langgraph.graph import StateGraph

# Subgrafo especialista em pesquisa
researcher_graph = StateGraph(ResearchState)
# ... define nós e arestas do researcher ...
researcher_app = researcher_graph.compile()

# Grafo principal usa researcher como nó
main_graph = StateGraph(MainState)
main_graph.add_node("researcher", researcher_app)  # subgrafo como nó
main_graph.add_node("writer", writer_node)

Como isso se conecta

  • 06-01-01 LangChain — LangGraph usa Runnables e tools do LangChain; são complementares
  • 06-03-04 AF 1.0: graph workflow — Microsoft Agent Framework herdou o conceito de graph workflow do AutoGen, similar ao LangGraph
  • 06-04-03 Decision matrix — LangGraph é a escolha default para agentes Python complexos com estado
  • 06-04-04 Desafio dual-framework — veremos o mesmo agente implementado em LangGraph e AF 1.0

Fontes

  1. LangGraph Docs — Core Concepts
  2. LangGraph Docs — Persistence & Checkpointing
  3. LangGraph Docs — Human-in-the-loop
  4. GitHub — langchain-ai/langgraph