Olha só o problema: 15 minutos de conversa pra fechar um agendamento
Imagine a cena: você manda mensagem pra uma empresa de limpeza. Eles perguntam o tamanho do sofá, o material, pedem foto, seu endereço. Aí você pergunta sobre limpeza de apartamento e começa OUTRA rodada de perguntas. Preço, negociação, horário… quando você vê, passaram 15 minutos.
Isso é o tipo de fluxo que grita por automação. Mas um chatbot comum não resolve — ele não lembra do contexto entre mensagens, não extrai dados estruturados e não orquestra etapas. É aí que entra o LangGraph: um grafo com estado compartilhado, onde cada nó lê e atualiza o estado, e arestas condicionais decidem o próximo passo.
Neste tutorial a gente vai construir um agente que:
- Entende a query em linguagem natural e extrai os dados do agendamento.
- Calcula o preço a partir dos dados estruturados.
- Lida com aceite ou recusa do cliente.
- Propõe horários e confirma o agendamento.
E claro: vamos plugar o Langfuse pra ter observabilidade total. Porque em produção, se você não enxerga o que o agente está fazendo, você tá no escuro. Quer ver como function calling on-device está evoluindo? Dá uma olhada no nosso post sobre Google AI Edge e function calling cross-platform.
O código-fonte completo tá no GitHub — clona lá e testa! 🚀

Mão na massa: estado, nós e roteamento condicional
O coração do agente é um TypedDict de estado que flui pelo grafo. Cada nó retorna uma atualização parcial, e funções de roteamento inspecionam o estado pra decidir o próximo nó.
from typing import TypedDict, Optional, List
from langgraph.graph import StateGraph, END
class BookingDetails(TypedDict, total=False):
service_type: str # ex: "sofá", "apartamento"
size: Optional[str]
material: Optional[str]
address: Optional[str]
preferred_days: Optional[List[str]]
price: Optional[float]
confirmed: Optional[bool]
class AgentState(TypedDict):
messages: List[dict]
booking: BookingDetails
next_action: Optional[str]
# --- Nó: extrai detalhes da conversa ---
def extract_details(state: AgentState) -> dict:
# Na prática, chame um LLM com structured output aqui
# Pra demo, simulamos a extração
last_msg = state["messages"][-1]["content"].lower()
booking = state.get("booking", {})
if "sofá" in last_msg or "sofa" in last_msg:
booking["service_type"] = "sofá"
if "apartamento" in last_msg:
booking["service_type"] = "apartamento"
return {"booking": booking}
# --- Nó: calcula preço a partir dos dados estruturados ---
def calculate_price(state: AgentState) -> dict:
booking = state["booking"]
base = 250.0 if booking.get("service_type") == "sofá" else 600.0 # R$
if booking.get("material") == "couro":
base *= 1.2
return {"booking": {**booking, "price": base}}
# --- Roteamento: temos info suficiente pra precificar? ---
def route_after_extract(state: AgentState) -> str:
b = state["booking"]
required = ["service_type", "address"]
if all(b.get(k) for k in required):
return "calculate_price"
return END # espera a próxima mensagem do cliente
# --- Monta o grafo ---
builder = StateGraph(AgentState)
builder.add_node("extract_details", extract_details)
builder.add_node("calculate_price", calculate_price)
builder.set_entry_point("extract_details")
builder.add_conditional_edges("extract_details", route_after_extract, {
"calculate_price": "calculate_price",
END: END,
})
builder.add_edge("calculate_price", END)
# Compila com checkpointer pra persistir entre turnos
from langgraph.checkpoint.memory import MemorySaver
graph = builder.compile(checkpointer=MemorySaver())
Pontos-chave:
- O
checkpointeré o que torna o agente stateful entre mensagens — essencial pra um fluxo que dura minutos ou horas. - A função de roteamento é onde você codifica a regra de negócio: "só pergunta de novo se faltar algo".
- Nós são funções puras de estado → estado parcial, fáceis de testar e raciocinar.
Quer ver mais sobre features modernas de web platform que combinam com esses backends de agente? Confere nosso apanhado sobre CSS Quake, Gap Decorations e o futuro das features de web platform.

Observabilidade com Langfuse (e os perrengues que ninguém te conta)
Com o agente rodando, a pergunta muda de "funciona?" pra "o que ele tá fazendo de verdade?". O Langfuse é uma plataforma open-source de engenharia de LLM pra tracing, monitoramento e avaliação. O plano free é mais que suficiente pra protótipo.
import os
from langfuse.callback import CallbackHandler
# Coloque no seu .env:
# LANGFUSE_PUBLIC_KEY=pk-lf-...
# LANGFUSE_SECRET_KEY=sk-lf-...
# LANGFUSE_HOST=https://cloud.langfuse.com
langfuse_handler = CallbackHandler(
public_key=os.getenv("LANGFUSE_PUBLIC_KEY"),
secret_key=os.getenv("LANGFUSE_SECRET_KEY"),
host=os.getenv("LANGFUSE_HOST"),
)
# Anexa em qualquer invocação LangChain/LangGraph
result = graph.invoke(
{"messages": [{"role": "user", "content": "Preciso limpar um sofá de couro, 3 lugares, na Rua X, 123."}]},
config={"callbacks": [langfuse_handler], "configurable": {"thread_id": "cliente-42"}},
)
No dashboard do Langfuse você vê cada prompt, input, output, contagem de tokens e custo por trace. Esse último item importa mais do que a galera admite — um agente descontrolado pode queimar seu orçamento de API sem você perceber. 💸
⚠️ Limitações e armadilhas
- Extração estruturada é frágil. LLMs às vezes esquecem campos ou alucinam. Sempre valide com schemas Pydantic antes de mandar pra lógica de preço.
- Drift no schema de estado. Conforme você adiciona features (ex: agendamentos recorrentes), o
TypedDictcresce. Versione ou migre com cuidado — threads antigas com schema velho quebram. - Preço determinístico ≠ agente determinístico. Mantenha a função de preço pura e separada do LLM. Nunca deixe o modelo "inventar" preço.
- Checkpointer importa.
MemorySaverserve pra demo, mas produção precisa de store persistente (Redis, Postgres) pra sobreviver a restarts. - Tracking de custo não é opcional. Sem Langfuse (ou equivalente), você descobre a fatura de tokens do jeito difícil.
🧭 Próximos passos
- Adicione uma UI real — Streamlit ou um frontend React leve pra substituir o CLI.
- Persista o estado no Postgres — troque
MemorySaverpor um checkpointer de produção. - Integre calendários — conecte com Google Calendar ou Calendly pra propor horários reais.
- Human-in-the-loop — pra casos exóticos (pedidos fora do padrão), escale pra um humano via interrupt do LangGraph.
- Avalie com datasets do Langfuse — monte uma suíte de regressão com conversas de exemplo pra pegar regressões de prompt.

Fechando a conta
Você viu como construir um agente stateful e multi-etapa que vai muito além de um chatbot. LangGraph cuidando do estado e da orquestração + Langfuse cuidando da observabilidade = uma base que você consegue realmente colocar em produção, não só fazer demo.
A ideia central é simples: deixe o LLM cuidar da linguagem, deixe o código determinístico cuidar das regras de negócio, e deixe o grafo cuidar da orquestração. Essa separação é o que torna agentes confiáveis o suficiente pra produção.
Comece pelo CLI de demo, instrumente com Langfuse, e itere pra uma UI real com storage persistente. A ligação de 15 minutos não tem a menor chance. 💪
Leitura complementar:
- Google AI Edge: Function Calling On-Device Cross-Platform
- CSS Quake, Gap Decorations e o Futuro das Features de Web Platform
Fonte: Towards Data Science — Replacing a 15-Minute Booking Process with a LangGraph AI Agent