# Seu Primeiro Agente com LangGraph: Tutorial Passo a Passo

> Tutorial hands-on completo para construir seu primeiro agente de IA com LangGraph em Python — do zero ao agente de pesquisa funcional com busca na web e...

Source: https://agentify.ia.br/blog/primeiro-agente-langgraph/

> **TL;DR** — Neste tutorial você vai construir um agente de pesquisa funcional com LangGraph que busca informações na web, avalia se tem dados suficientes e gera um relatório estruturado. O fluxo completo: instalar dependências → definir estado → criar nós de processamento → conectar arestas → adicionar lógica condicional → compilar e executar o grafo. Código completo e pronto para rodar no final do artigo.

---

## O Que Vamos Construir

Vamos criar um **agente de pesquisa** que faz algo que nenhuma cadeia linear de prompts consegue: ele toma decisões sozinho sobre quando parar de buscar informação.

O fluxo funciona assim:

- Recebe um tópico de pesquisa

- Gera consultas de busca relevantes

- Pesquisa na web usando a API Tavily

- Analisa os resultados encontrados

- **Decide se precisa buscar mais** — se sim, volta ao passo 2 com consultas refinadas

- Quando tem informação suficiente, escreve um relatório estruturado

Essa capacidade de loop — buscar, avaliar, decidir e voltar — é exatamente o que separa um agente de um simples chain de prompts. É o padrão que empresas como Klarna, Uber e Replit usam em produção com LangGraph desde 2025.

O LangGraph modela esse comportamento como um **grafo dirigido**: cada etapa é um nó (função Python), cada transição é uma aresta, e as decisões do agente são arestas condicionais. Você define a estrutura, o framework cuida do estado, persistência e fluxo de controle.

---

## Pré-requisitos

Antes de começar, confirme que você tem:

- **Python 3.11 ou superior** — LangGraph 1.x exige essa versão mínima

- **Uma chave de API da OpenAI** — usamos `gpt-4o-mini` por custo-benefício (funciona com qualquer LLM compatível)

- **Uma chave de API da Tavily** — para busca web (o plano gratuito dá 1.000 buscas por mês)

- **Familiaridade básica com Python** — funções, dicionários, type hints

Se você nunca trabalhou com agentes antes, vale dar uma olhada no artigo sobre [arquitetura de harness e loop](/blog/harness-e-loop-arquitetura-agentes) para entender o modelo mental por trás do que vamos implementar.

---

## Instalação

Crie um diretório para o projeto e configure um ambiente virtual:

```
mkdir langgraph-pesquisa
cd langgraph-pesquisa
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
```

Instale as dependências com versões fixas:

```
pip install langgraph==0.3.34 \
 langchain-openai==0.3.12 \
 langchain-community==0.3.7 \
 tavily-python==0.5.0 \
 python-dotenv==1.1.0
```

> **Nota:** O pacote `langgraph` no PyPI está na versão 0.3.x como runtime instalável, embora o framework seja comercialmente identificado como LangGraph 1.0+. Fixe versões em produção para evitar breaking changes.

Crie um arquivo `.env` na raiz do projeto para suas chaves:

```
OPENAI_API_KEY=sk-sua-chave-openai-aqui
TAVILY_API_KEY=tvly-sua-chave-tavily-aqui
```

Teste a instalação:

```
import langgraph
print(f"LangGraph versão: {langgraph.__version__}")
```

Se o número de versão aparecer sem erros, estamos prontos.

---

## Definir o Estado

O estado é a **memória de trabalho** do agente — um dicionário tipado que flui pelo grafo inteiro. Cada nó lê dele e escreve nele. Essa é a decisão de design mais importante: o estado define o que seu agente sabe e consegue rastrear.

Crie um arquivo `agente.py`:

```
"""Agente de pesquisa construído com LangGraph."""

import os
from typing import TypedDict, Annotated
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.memory import MemorySaver
from tavily import TavilyClient

load_dotenv()

# --- Schema do Estado ---

class EstadoPesquisa(TypedDict):
 """Memória de trabalho do agente."""
 messages: Annotated[list, add_messages] # Histórico de conversação
 topico: str # O que estamos pesquisando
 consultas: list[str] # Queries já executadas
 fontes: list[dict] # Resultados brutos da busca
 analise: str # Análise das fontes
 relatorio_final: str # Relatório final de pesquisa
 iteracao: int # Quantas rodadas de pesquisa fizemos
 max_iteracoes: int # Limite de segurança contra loops infinitos
```

Pontos importantes sobre essa definição:

- **`messages`** usa a anotação `add_messages` — isso faz o LangGraph *acumular* mensagens ao invés de substituí-las. O histórico de conversação cresce naturalmente.

- **`iteracao` e `max_iteracoes`** previnem loops infinitos. Isso não é opcional — qualquer agente que pode fazer loop precisa de um freio de emergência.

- **Cada campo tem propósito claro.** Quando você precisar debugar o agente (e vai precisar), nomes descritivos economizam horas.

---

## Criar os Nós

Nós são funções Python simples que recebem o estado atual e retornam uma atualização parcial. Sem classes base, sem decoradores obrigatórios. Cada nó faz uma coisa só.

### Setup do LLM e Cliente de Busca

```
# --- Setup ---

llm = ChatOpenAI(
 model="gpt-4o-mini",
 temperature=0.1, # Temperatura baixa para pesquisa factual
)

tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
```

### Nó 1: Gerar Consultas de Busca

```
# --- Nós ---

def gerar_consultas(state: EstadoPesquisa) -> dict:
 """Transforma o tópico em consultas de busca específicas."""
 topico = state["topico"]

 # Em iterações posteriores, refina baseado no que já encontramos
 contexto_existente = ""
 if state.get("analise"):
 contexto_existente = (
 f"\n\nJá sabemos o seguinte:\n{state['analise']}\n\n"
 "Gere consultas para preencher lacunas no nosso conhecimento."
 )

 response = llm.invoke([
 SystemMessage(content=(
 "Você é um assistente de pesquisa. Gere 3 consultas de busca "
 "específicas e diversas para pesquisar o tópico dado. "
 "Retorne apenas as consultas, uma por linha. "
 "Sem numeração, sem texto extra."
 f"{contexto_existente}"
 )),
 HumanMessage(content=f"Tópico de pesquisa: {topico}"),
 ])

 novas_consultas = [
 q.strip() for q in response.content.strip().split("\n") if q.strip()
 ]

 return {
 "consultas": state.get("consultas", []) + novas_consultas,
 "messages": [response],
 }
```

Repare no padrão de **comportamento adaptativo**: na primeira execução, o nó gera consultas amplas. Em rodadas posteriores, ele lê sua própria análise anterior e gera consultas para preencher lacunas. O agente literalmente fica mais inteligente a cada loop.

### Nó 2: Buscar na Web

```
def buscar_web(state: EstadoPesquisa) -> dict:
 """Executa as consultas de busca e coleta resultados."""
 consultas = state.get("consultas", [])
 # Busca apenas o último lote de consultas (últimas 3)
 consultas_recentes = consultas[-:]
 todos_resultados = state.get("fontes", [])

 for consulta in consultas_recentes:
 try:
 response = tavily.search(
 query=consulta,
 max_results=,
 include_raw_content=False,
 )
 for resultado in response.get("results", []):
 # Evita URLs duplicadas
 if not any(f["url"] == resultado["url"] for f in todos_resultados):
 todos_resultados.append({
 "titulo": resultado.get("title", ""),
 "url": resultado.get("url", ""),
 "conteudo": resultado.get("content", ""),
 "consulta": consulta,
 })
 except Exception as e:
 # Loga mas não crasha — o agente trabalha com resultados parciais
 print(f"Busca falhou para '{consulta}': {e}")

 return {"fontes": todos_resultados}
```

A deduplicação e o tratamento de erros são intencionais. Se uma API de busca falhar, o agente continua com os resultados que tem. Resiliência é parte do design.

### Nó 3: Analisar Resultados

```
def analisar_resultados(state: EstadoPesquisa) -> dict:
 """Analisa os resultados e avalia se temos informação suficiente."""
 fontes = state.get("fontes", [])

 if not fontes:
 return {
 "analise": "Nenhum resultado encontrado. Preciso tentar consultas diferentes.",
 "iteracao": state.get("iteracao", ) +,
 }

 # Formata as fontes para o LLM
 texto_fontes = ""
 for i, fonte in enumerate(fontes, ):
 texto_fontes += (
 f"\n[{i}] {fonte['titulo']}\n"
 f"URL: {fonte['url']}\n"
 f"{fonte['conteudo']}\n"
 )

 response = llm.invoke([
 SystemMessage(content=(
 "Você é um analista de pesquisa. Analise os resultados de busca "
 "sobre o tópico dado. Forneça:\n"
 "1. Principais descobertas (o que sabemos)\n"
 "2. Lacunas (o que ainda precisamos descobrir)\n"
 "3. Nível de confiança (baixo/médio/alto) no nosso entendimento geral\n\n"
 "Seja específico e cite números das fontes."
 )),
 HumanMessage(content=f"Tópico: {state['topico']}\n\nFontes:{texto_fontes}"),
 ])

 return {
 "analise": response.content,
 "iteracao": state.get("iteracao", ) +,
 "messages": [response],
 }
```

### Nó 4: Escrever Relatório

```
def escrever_relatorio(state: EstadoPesquisa) -> dict:
 """Escreve um relatório estruturado com base nas descobertas."""
 fontes = state.get("fontes", [])
 analise = state.get("analise", "")

 texto_fontes = ""
 for i, fonte in enumerate(fontes, ):
 texto_fontes += (
 f"\n[{i}] {fonte['titulo']}\n"
 f"URL: {fonte['url']}\n"
 f"{fonte['conteudo']}\n"
 )

 response = llm.invoke([
 SystemMessage(content=(
 "Você é um redator de pesquisa. Escreva um relatório claro e "
 "bem estruturado com base na análise e fontes fornecidas. Inclua:\n"
 "- Resumo executivo (2-3 frases)\n"
 "- Principais descobertas com citações [1], [2], etc.\n"
 "- Conclusões\n"
 "- Lista de fontes\n\n"
 "Escreva para um público técnico. Seja factual e específico."
 )),
 HumanMessage(content=(
 f"Tópico: {state['topico']}\n\n"
 f"Análise:\n{analise}\n\n"
 f"Fontes:{texto_fontes}"
 )),
 ])

 return {
 "relatorio_final": response.content,
 "messages": [response],
 }
```

---

## Montar o Grafo

Aqui é onde o LangGraph brilha. Conectamos os nós com arestas e definimos o fluxo linear básico:

```
# --- Construir o Grafo ---

workflow = StateGraph(EstadoPesquisa)

# Adicionar nós
workflow.add_node("gerar_consultas", gerar_consultas)
workflow.add_node("buscar_web", buscar_web)
workflow.add_node("analisar_resultados", analisar_resultados)
workflow.add_node("escrever_relatorio", escrever_relatorio)

# Adicionar arestas lineares
workflow.add_edge(START, "gerar_consultas")
workflow.add_edge("gerar_consultas", "buscar_web")
workflow.add_edge("buscar_web", "analisar_resultados")
workflow.add_edge("escrever_relatorio", END)
```

Até aqui temos um fluxo linear: START → gerar_consultas → buscar_web → analisar_resultados → … → escrever_relatorio → END. O que falta é a decisão no meio — o agente precisa escolher entre continuar pesquisando ou ir para a escrita.

---

## Executar o Agente (Versão Linear)

Antes de adicionar a lógica condicional, vamos compilar e testar o fluxo básico para garantir que tudo funciona. Temporariamente, conecte `analisar_resultados` direto ao relatório:

```
# Versão simplificada para teste
workflow.add_edge("analisar_resultados", "escrever_relatorio")

# Compilar
agente = workflow.compile()

# Executar
resultado = agente.invoke({
 "topico": "Como empresas estão usando agentes de IA em produção em 2026",
 "messages": [],
 "consultas": [],
 "fontes": [],
 "analise": "",
 "relatorio_final": "",
 "iteracao": ,
 "max_iteracoes": ,
})

print(resultado["relatorio_final"])
```

Se o relatório aparecer, seu grafo básico está funcionando. Agora vamos adicionar inteligência.

---

## Adicionar Lógica Condicional

A aresta condicional é o coração de qualquer agente. É aqui que o grafo deixa de ser um pipeline linear e vira algo que raciocina sobre o próprio progresso.

### A Função de Roteamento

```
# --- Lógica de Roteamento ---

def deve_continuar_pesquisa(state: EstadoPesquisa) -> str:
 """Decide se continua pesquisando ou escreve o relatório."""
 iteracao = state.get("iteracao", )
 max_iteracoes = state.get("max_iteracoes", )
 analise = state.get("analise", "")

 # Hard stop: previne loops infinitos
 if iteracao >= max_iteracoes:
 return "escrever_relatorio"

 # Se a análise menciona confiança baixa ou lacunas, continua
 analise_lower = analise.lower()
 if "baixo" in analise_lower and "confiança" in analise_lower:
 return "gerar_consultas"
 if "lacunas significativas" in analise_lower or "precisamos" in analise_lower:
 return "gerar_consultas"

 # Caso contrário, temos informação suficiente
 return "escrever_relatorio"
```

A função retorna uma string — o nome do próximo nó. É assim que o agente decide se continua buscando ou parte para a escrita. Repare no `max_iteracoes` como trava de segurança: **nunca** permita que um agente faça loop sem limite.

### Montando o Grafo Completo

Agora substituímos a aresta fixa entre `analisar_resultados` e `escrever_relatorio` pela aresta condicional:

```
# --- Grafo Completo ---

workflow = StateGraph(EstadoPesquisa)

# Nós
workflow.add_node("gerar_consultas", gerar_consultas)
workflow.add_node("buscar_web", buscar_web)
workflow.add_node("analisar_resultados", analisar_resultados)
workflow.add_node("escrever_relatorio", escrever_relatorio)

# Arestas
workflow.add_edge(START, "gerar_consultas")
workflow.add_edge("gerar_consultas", "buscar_web")
workflow.add_edge("buscar_web", "analisar_resultados")

# Aresta condicional: o agente decide se faz loop ou finaliza
workflow.add_conditional_edges(
 "analisar_resultados",
 deve_continuar_pesquisa,
 {
 "gerar_consultas": "gerar_consultas",
 "escrever_relatorio": "escrever_relatorio",
 },
)

workflow.add_edge("escrever_relatorio", END)

# Memória para persistência de estado
memory = MemorySaver()
agente = workflow.compile(checkpointer=memory)
```

O fluxo agora funciona assim:

- **START → gerar_consultas** — cria consultas de busca a partir do tópico

- **gerar_consultas → buscar_web** — executa as consultas

- **buscar_web → analisar_resultados** — avalia o que encontrou

- **analisar_resultados →???** — a aresta condicional entra em ação. Se a análise diz que precisamos mais informação, voltamos a gerar consultas. Se temos o suficiente, seguimos para a escrita.

- **escrever_relatorio → END** — saída do relatório final

Esse loop — buscar, avaliar, decidir — é o padrão fundamental de agentes. Se quiser entender a diferença entre esse modelo e [agent frameworks versus coding agents](/blog/agent-frameworks-vs-coding-agents), vale a leitura para contextualizar.

---

## Resultado Final

Aqui está o código completo e funcional, pronto para copiar e executar:

```
"""Agente de pesquisa com LangGraph — código completo."""

import os
from typing import TypedDict, Annotated
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.memory import MemorySaver
from tavily import TavilyClient

load_dotenv()

# ============================================================
# ESTADO
# ============================================================

class EstadoPesquisa(TypedDict):
 """Memória de trabalho do agente."""
 messages: Annotated[list, add_messages]
 topico: str
 consultas: list[str]
 fontes: list[dict]
 analise: str
 relatorio_final: str
 iteracao: int
 max_iteracoes: int

# ============================================================
# SETUP
# ============================================================

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.1)
tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))

# ============================================================
# NÓS
# ============================================================

def gerar_consultas(state: EstadoPesquisa) -> dict:
 """Transforma o tópico em consultas de busca específicas."""
 topico = state["topico"]
 contexto_existente = ""
 if state.get("analise"):
 contexto_existente = (
 f"\n\nJá sabemos:\n{state['analise']}\n\n"
 "Gere consultas para preencher lacunas."
 )
 response = llm.invoke([
 SystemMessage(content=(
 "Você é um assistente de pesquisa. Gere 3 consultas de busca "
 "específicas e diversas para o tópico dado. Retorne apenas as "
 "consultas, uma por linha. Sem numeração."
 f"{contexto_existente}"
 )),
 HumanMessage(content=f"Tópico: {topico}"),
 ])
 novas_consultas = [
 q.strip() for q in response.content.strip().split("\n") if q.strip()
 ]
 return {
 "consultas": state.get("consultas", []) + novas_consultas,
 "messages": [response],
 }

def buscar_web(state: EstadoPesquisa) -> dict:
 """Executa consultas de busca e coleta resultados."""
 consultas_recentes = state.get("consultas", [])[-:]
 todos_resultados = state.get("fontes", [])

 for consulta in consultas_recentes:
 try:
 response = tavily.search(
 query=consulta,
 max_results=,
 include_raw_content=False,
 )
 for resultado in response.get("results", []):
 if not any(f["url"] == resultado["url"] for f in todos_resultados):
 todos_resultados.append({
 "titulo": resultado.get("title", ""),
 "url": resultado.get("url", ""),
 "conteudo": resultado.get("content", ""),
 "consulta": consulta,
 })
 except Exception as e:
 print(f"Busca falhou para '{consulta}': {e}")

 return {"fontes": todos_resultados}

def analisar_resultados(state: EstadoPesquisa) -> dict:
 """Analisa resultados e avalia se temos informação suficiente."""
 fontes = state.get("fontes", [])
 if not fontes:
 return {
 "analise": "Nenhum resultado. Precisamos tentar consultas diferentes.",
 "iteracao": state.get("iteracao", ) +,
 }

 texto_fontes = ""
 for i, fonte in enumerate(fontes, ):
 texto_fontes += f"\n[{i}] {fonte['titulo']}\nURL: {fonte['url']}\n{fonte['conteudo']}\n"

 response = llm.invoke([
 SystemMessage(content=(
 "Você é um analista de pesquisa. Analise os resultados sobre o "
 "tópico dado. Forneça:\n"
 "1. Principais descobertas\n"
 "2. Lacunas no conhecimento\n"
 "3. Nível de confiança (baixo/médio/alto)\n\n"
 "Cite números das fontes."
 )),
 HumanMessage(content=f"Tópico: {state['topico']}\n\nFontes:{texto_fontes}"),
 ])

 return {
 "analise": response.content,
 "iteracao": state.get("iteracao", ) +,
 "messages": [response],
 }

def escrever_relatorio(state: EstadoPesquisa) -> dict:
 """Escreve o relatório final estruturado."""
 fontes = state.get("fontes", [])
 analise = state.get("analise", "")

 texto_fontes = ""
 for i, fonte in enumerate(fontes, ):
 texto_fontes += f"\n[{i}] {fonte['titulo']}\nURL: {fonte['url']}\n{fonte['conteudo']}\n"

 response = llm.invoke([
 SystemMessage(content=(
 "Você é um redator técnico. Escreva um relatório de pesquisa "
 "claro e estruturado. Inclua:\n"
 "- Resumo executivo (2-3 frases)\n"
 "- Principais descobertas com citações [1], [2]\n"
 "- Conclusões\n"
 "- Lista de fontes\n\n"
 "Público: técnico. Tom: factual e direto."
 )),
 HumanMessage(content=(
 f"Tópico: {state['topico']}\n\n"
 f"Análise:\n{analise}\n\nFontes:{texto_fontes}"
 )),
 ])

 return {
 "relatorio_final": response.content,
 "messages": [response],
 }

# ============================================================
# ROTEAMENTO
# ============================================================

def deve_continuar_pesquisa(state: EstadoPesquisa) -> str:
 """Decide: continuar pesquisando ou escrever o relatório."""
 iteracao = state.get("iteracao", )
 max_iteracoes = state.get("max_iteracoes", )
 analise = state.get("analise", "")

 if iteracao >= max_iteracoes:
 return "escrever_relatorio"

 analise_lower = analise.lower()
 if "baixo" in analise_lower and "confiança" in analise_lower:
 return "gerar_consultas"
 if "lacunas significativas" in analise_lower or "precisamos" in analise_lower:
 return "gerar_consultas"

 return "escrever_relatorio"

# ============================================================
# GRAFO
# ============================================================

workflow = StateGraph(EstadoPesquisa)

workflow.add_node("gerar_consultas", gerar_consultas)
workflow.add_node("buscar_web", buscar_web)
workflow.add_node("analisar_resultados", analisar_resultados)
workflow.add_node("escrever_relatorio", escrever_relatorio)

workflow.add_edge(START, "gerar_consultas")
workflow.add_edge("gerar_consultas", "buscar_web")
workflow.add_edge("buscar_web", "analisar_resultados")
workflow.add_conditional_edges(
 "analisar_resultados",
 deve_continuar_pesquisa,
 {
 "gerar_consultas": "gerar_consultas",
 "escrever_relatorio": "escrever_relatorio",
 },
)
workflow.add_edge("escrever_relatorio", END)

memory = MemorySaver()
agente = workflow.compile(checkpointer=memory)

# ============================================================
# EXECUÇÃO
# ============================================================

if __name__ == "__main__":
 print("=" *)
 print(" Agente de Pesquisa — LangGraph")
 print("=" *)

 topico = input("\nDigite o tópico de pesquisa: ").strip()
 if not topico:
 topico = "Como empresas estão usando agentes de IA em produção em 2026"

 print(f"\nPesquisando: {topico}")
 print("-" *)

 estado_inicial = {
 "topico": topico,
 "messages": [],
 "consultas": [],
 "fontes": [],
 "analise": "",
 "relatorio_final": "",
 "iteracao": ,
 "max_iteracoes": ,
 }

 config = {"configurable": {"thread_id": "sessao-001"}}

 for evento in agente.stream(estado_inicial, config=config):
 for nome_no, saida in evento.items():
 print(f"\n>> Nó: {nome_no}")
 if nome_no == "gerar_consultas" and "consultas" in saida:
 print(f" Consultas: {saida['consultas'][-:]}")
 elif nome_no == "buscar_web" and "fontes" in saida:
 print(f" {len(saida['fontes'])} fontes encontradas")
 elif nome_no == "analisar_resultados" and "analise" in saida:
 print(f" Iteração: {saida.get('iteracao', '?')}")
 print(f" Análise: {saida['analise'][:]}...")
 elif nome_no == "escrever_relatorio" and "relatorio_final" in saida:
 print(f"\n{'=' * 60}")
 print(" RELATÓRIO DE PESQUISA")
 print("=" *)
 print(saida["relatorio_final"])

 print(f"\n{'=' * 60}")
 print("Pesquisa concluída.")
```

Execute com:

```
python agente.py
```

Você verá o agente trabalhando em tempo real — gerando consultas, buscando, avaliando, potencialmente voltando para buscar mais, e finalmente escrevendo o relatório.

---

## Próximos Passos

Você acabou de construir um agente funcional com LangGraph. Os conceitos que aprendeu — `StateGraph`, `add_node`, `add_edge`, `add_conditional_edges`, `compile` — são os mesmos que rodam em produção nas empresas que mencionamos.

A partir daqui, os caminhos se abrem:

**Adicionar Human-in-the-Loop.** LangGraph permite inserir pontos de aprovação humana em qualquer lugar do grafo. O agente pausa, espera a aprovação e continua. Essencial para agentes que tomam ações no mundo real.

**Persistência em banco de dados.** O `MemorySaver` que usamos guarda estado em memória. Para produção, troque por `SqliteSaver` ou `PostgresSaver` e seu agente sobrevive a reinícios.

**Sistemas multi-agente.** No LangGraph, um nó pode ser outro grafo compilado. Você pode ter um agente supervisor delegando para agentes especialistas — pesquisador, redator, revisor — cada um com seu próprio grafo.

**Streaming de eventos.** Ao invés de `.stream()` com eventos de nó, use `.astream_events()` para receber cada token conforme o LLM gera. Dá uma experiência de tempo real para o usuário.

**Expandir ferramentas.** Adicione leitura/escrita de arquivos, execução de código, chamadas a APIs, navegação web. Cada capacidade é um novo nó no grafo.

O padrão fundamental — estado, nós, arestas, decisão — se mantém independente da complexidade. Você define a estrutura, o LangGraph cuida do resto. Se quer entender como frameworks como esse se comparam a coding agents como Claude Code e Cursor, o artigo sobre [agent frameworks vs coding agents](/blog/agent-frameworks-vs-coding-agents) faz essa distinção com clareza.

Bom building.

-->
