Estado, Grafos e StateGraph
Aprenda conceitos de grafos em LangGraph: StateGraph, schema de Estado, nós, arestas, compilação e fluxo de execução.
Estado, Grafos e StateGraph
LangGraph é construído em torno do conceito de um grafo direcionado stateful. Entender como StateGraph funciona é essencial antes de escrever qualquer código de agente.
O que é um StateGraph?
StateGraph é a classe principal para construir aplicações LangGraph. Ela gerencia:
- Um schema de tipo que define a forma do estado
- Uma coleção de nós que processam e atualizam o estado
- Um conjunto de arestas que definem a topologia de execução
- Compilação que valida e congela o grafo
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
class SearchState(TypedDict):
query: str
results: list[str]
status: str
builder = StateGraph(SearchState)Sempre use StateGraph em vez da classe básica Graph. StateGraph fornece estado tipado, checkpointing e todos os recursos de produção. A classe básica Graph está depreciada para a maioria dos casos de uso.
Schema de Estado
O Estado é um dicionário que flui através de cada nó. É definido usando TypedDict, dataclass ou pydantic.BaseModel.
TypedDict (Recomendado para Iniciantes)
from typing_extensions import TypedDict
from typing import List, Optional
class AgentState(TypedDict):
messages: List[str]
turn_count: int
is_complete: bool
final_answer: Optional[str]Cada nó no grafo recebe um dict correspondente a este schema e retorna um dict parcial com apenas as chaves que deseja atualizar.
def first_node(state: AgentState) -> dict:
# Ler do estado
current_turn = state["turn_count"]
# Retornar apenas as atualizações
return {
"messages": state["messages"] + ["Hello from node 1"],
"turn_count": current_turn + 1
# is_complete e final_answer permanecem inalterados
}Dataclass State
from dataclasses import dataclass, field
from typing import List
@dataclass
class AgentState:
messages: List[str] = field(default_factory=list)
turn_count: int = 0
is_complete: bool = FalseDataclasses fornecem valores padrão e estado mutável. Use field(default_factory=...) para valores padrão mutáveis como listas.
Pydantic BaseModel State
from pydantic import BaseModel, Field
from typing import List
class AgentState(BaseModel):
messages: List[str] = Field(default_factory=list)
turn_count: int = 0
is_complete: bool = FalseUse TypedDict para prototipagem (menos boilerplate). Use BaseModel para produção (validação, serialização, schema JSON). As três abordagens funcionam de forma idêntica no nível do grafo.
Como o Estado Flui Através do Grafo
Estado Inicial → Nó A → (atualiza estado) → Nó B → (atualiza estado) → Estado Final
↑ |
└─────────── (loop de volta para A) ────────────┘
Cada nó:
- Recebe o estado completo atual como um dict
- Processa os dados (chama LLM, executa ferramentas, etc.)
- Retorna um dict parcial de atualizações
- LangGraph mescla as atualizações no estado compartilhado
Regras de Mesclagem de Estado
| Valor de Retorno | Comportamento |
|---|---|
{"key": "value"} | Atualiza state["key"] para "value" |
{"key": state["key"] + ["new"]} | Substitui state["key"] com nova lista |
return None | Nenhuma alteração no estado |
return {} | Nenhuma alteração no estado |
Atualizações de estado usam uma mesclagem superficial. Se o estado tem dicts aninhados, retornar {"nested": {"inner": 1}} substitui a chave nested inteira — não faz mesclagem profunda. Para mesclagens profundas, você precisa de redutores personalizados (abordado no curso Intermediário).
Nós
Nós são as unidades de processamento de um grafo. Um nó é simplesmente uma função Python que recebe estado e retorna atualizações.
Assinatura da Função
def node_function(state: StateType) -> dict:
# Processar estado
# Retornar atualizações
return {"key": new_value}Registro de Nó
builder = StateGraph(AgentState)
builder.add_node("process", node_function)
# ^nome ^referência da funçãoNomes de nós devem ser únicos. Use nomes descritivos como "analyze_query", "search_database", "generate_response" em vez de "node1", "node2".
Nós com Config
def node_with_config(state: StateType, config: dict) -> dict:
# Acessar parâmetros configuráveis
user_id = config.get("configurable", {}).get("user_id")
return {"processed": True}
builder.add_node("configurable", node_with_config)Nós com Argumentos de Palavra-chave Extras
from langgraph.graph import StateGraph
def node_with_kwargs(state: StateType, **kwargs) -> dict:
# kwargs contém parâmetros adicionais de tempo de execução
return {"received": kwargs.get("extra_param", "default")}Arestas
Arestas conectam nós e definem o caminho de execução.
Aresta Básica
# Após o nó A terminar, execute o nó B
builder.add_edge("A", "B")Ponto de Entrada
# Marcar o nó inicial
builder.set_entry_point("A")
# Ou usando a constante START
from langgraph.graph import START
builder.add_edge(START, "A")Ponto de Finalização
# Marcar o nó final
builder.set_finish_point("C")
# Ou usando a constante END
from langgraph.graph import END
builder.add_edge("C", END)Usar as constantes START e END é a abordagem moderna. Elas estão disponíveis em langgraph.graph e tornam a definição do grafo mais legível.
Compilação
A compilação valida a estrutura do grafo e produz um objeto executável.
# Compilar o grafo
app = builder.compile()
# O grafo agora está congelado — não é possível adicionar mais nós ou arestasO que a Compilação Faz
- Valida que todos os nós referenciados existem
- Verifica nós inalcançáveis (sem aresta de entrada)
- Verifica se o grafo está conectado (todo nó alcançável a partir de START)
- Congela a topologia para que possa ser invocada eficientemente
- Prepara checkpointers se configurados
Invocação
# Invocar com estado inicial
result = app.invoke({
"messages": [],
"turn_count": 0,
"is_complete": False,
"final_answer": None
})
# Acessar o estado final
print(result["messages"])
print(result["turn_count"])Streaming
# Stream para ver estados intermediários
for event in app.stream({
"messages": [],
"turn_count": 0,
"is_complete": False,
"final_answer": None
}):
for node_name, state_update in event.items():
if node_name != "__end__":
print(f"[{node_name}]: {state_update}")Exemplo Mínimo Completo
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
class MyState(TypedDict):
value: str
step_count: int
def step_one(state: MyState) -> dict:
print("Step 1")
return {
"value": f"Processed: {state['value']}",
"step_count": state["step_count"] + 1
}
def step_two(state: MyState) -> dict:
print("Step 2")
return {
"value": f"Final: {state['value']}",
"step_count": state["step_count"] + 1
}
# Construir
builder = StateGraph(MyState)
builder.add_node("step1", step_one)
builder.add_node("step2", step_two)
builder.add_edge(START, "step1")
builder.add_edge("step1", "step2")
builder.add_edge("step2", END)
# Compilar
app = builder.compile()
# Executar
result = app.invoke({"value": "hello", "step_count": 0})
print(result["value"]) # Final: Processed: hello
print(result["step_count"]) # 2Este padrão — definir estado, adicionar nós, adicionar arestas, compilar, invocar — é a fundação de toda aplicação LangGraph. Cada agente que você construir seguirá estes cinco passos.
Visualizando o Grafo
LangGraph suporta gerar diagramas Mermaid a partir de grafos compilados:
# Obter o diagrama Mermaid como string
mermaid_code = app.get_graph().draw_mermaid()
print(mermaid_code)
# Salvar em arquivo
with open("graph.md", "w") as f:
f.write(app.get_graph().draw_mermaid())Saída Mermaid:
Use app.get_graph().draw_mermaid() durante o desenvolvimento para verificar se a topologia do seu grafo corresponde ao seu design.
Tratamento de Erro no Nível do Grafo
from langgraph.errors import GraphRecursionError
try:
result = app.invoke(initial_state, {"recursion_limit": 10})
except GraphRecursionError:
print("Graph hit recursion limit — possible infinite loop")
except Exception as e:
print(f"Graph execution failed: {e}")Sempre defina um recursion_limit para grafos com loops. O padrão é geralmente 25 passos. Sem ele, uma aresta condicional com bug pode causar um loop infinito.
Perguntas de Prática
O que uma função de nó em LangGraph recebe e retorna?
O que o método compile() faz?
Qual das seguintes é uma forma válida de definir estado em LangGraph?
O que set_entry_point() faz?
O que acontece se uma função de nó retorna None?
Qual é o propósito da constante START?
Como LangGraph mescla atualizações de estado de um nó?
Você pode adicionar nós a um grafo compilado?
O que recursion_limit controla?
Qual ferramenta LangGraph fornece para visualizar a estrutura do grafo?
Principais Conclusões
- StateGraph é a classe principal; gerencia estado tipado, nós, arestas e compilação
- Estado é um dict definido com TypedDict, dataclass ou BaseModel
- Nós são funções que recebem o estado completo e retornam atualizações parciais
- Arestas (add_edge, START, END) definem a topologia de execução
- compile() valida e congela o grafo em um objeto executável
- invoke() executa o grafo; stream() mostra estados intermediários
- Atualizações de estado usam mesclagem superficial — chaves de nível superior são substituídas
- Sempre defina recursion_limit para grafos com loops
- Use get_graph().draw_mermaid() para visualizar seu grafo