LangChain & LangGraph
Guia Completo
prompt.pipe(model).pipe(parser)LANGCHAIN_TRACING_V2, LANGCHAIN_API_KEY, LANGCHAIN_PROJECTLangChain: O Que É e De Onde Veio
Origem
LangChain foi criado por Harrison Chase em outubro de 2022 como side project na Robust Intelligence. A ideia: eliminar o boilerplate de conectar LLMs com ferramentas externas, memória e pipelines de dados. O projeto viralizou no GitHub, Harrison largou o emprego e fundou a LangChain, Inc. em 2023 — US$25M seed (Benchmark) + US$35M Série A.
Evolução de Versões
| Versão | Data | Marco Principal |
|---|---|---|
| 0.0.x | Out 2022 – Jan 2024 | Fase experimental, API instável |
| 0.1.0 | Jan 2024 | Primeira versão "estável", início de deprecações |
| 0.2.x | Mai 2024 | LangGraph promovido como padrão, AgentExecutor depreciado |
| 0.3.x | Out 2024 | Remove Pydantic v1, callbacks assíncronos por padrão |
| 1.0.x | 2025/2026 | Python: migração completa; JS: em andamento |
O Ecossistema
┌─────────────────────────────────────────────────────────┐ │ ECOSSISTEMA LANGCHAIN │ ├──────────────┬──────────────┬──────────────┬────────────┤ │ LangChain │ LangGraph │ LangSmith │ LangServe │ │ (framework) │ (orquestrador│(observabilid.│(depreciado)│ │ chains,LCEL │ de agentes) │ e evals) │ │ └──────────────┴──────────────┴──────────────┴────────────┘
Arquitetura Core do LangChain
┌─────────────────────────────────────┐ │ SEU CÓDIGO │ ├─────────────────────────────────────┤ │ LCEL / Chains / Agents │ ├──────────┬──────────┬───────────────┤ │ Model │ Prompts │ Tools/Memory │ │ I/O │ │ │ ├──────────┴──────────┴───────────────┤ │ @langchain/core (Runnable API) │ ├──────────┬──────────┬───────────────┤ │ OpenAI │Anthropic │ ...outros │ └──────────┴──────────┴───────────────┘
Os 6 Componentes Core
Interface padronizada para qualquer LLM. Troca de OpenAI para Anthropic sem mudar o resto do código.
import { ChatOpenAI } from "@langchain/openai";
import { ChatAnthropic } from "@langchain/anthropic";
// Mesma interface, provider diferente
const model = new ChatOpenAI({ model: "gpt-4o" });
// const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20241022" });
const response = await model.invoke("Olá, mundo!");
import { ChatPromptTemplate } from "@langchain/core/prompts";
const prompt = ChatPromptTemplate.fromMessages([
["system", "Você é um especialista em {dominio}."],
["human", "{pergunta}"],
]);
const formatted = await prompt.format({
dominio: "marketing digital",
pergunta: "Como calcular CAC?",
});
Tipos: PromptTemplate (legado), ChatPromptTemplate (padrão), FewShotPromptTemplate, PipelinePromptTemplate
import { StructuredOutputParser } from "@langchain/core/output_parsers";
import { z } from "zod";
const parser = StructuredOutputParser.fromZodSchema(
z.object({
nome: z.string(),
score: z.number().min(0).max(10),
justificativa: z.string(),
})
);
const formatInstructions = parser.getFormatInstructions();
Tipos legados: BufferMemory, BufferWindowMemory, SummaryMemory, VectorStoreRetrieverMemory
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const buscarProdutos = tool(
async ({ categoria, limite }) => {
const produtos = await db.produtos.findMany({
where: { categoria }, take: limite
});
return JSON.stringify(produtos);
},
{
name: "buscar_produtos",
description: "Busca produtos por categoria no catálogo",
schema: z.object({
categoria: z.string().describe("Categoria do produto"),
limite: z.number().default(10),
}),
}
);
Implementava o loop ReAct: Pergunta → LLM decide → Executa tool → LLM processa → ... — tudo como caixa preta sem controle de fluxo.
Como Funciona Por Dentro
Loop de Execução LCEL
invoke(input)
│
▼
[Runnable 1].invoke(input) → output_1
│
▼
[Runnable 2].invoke(output_1) → output_2
│
▼
[Runnable 3].invoke(output_2) → final_output
Como Tool Calling Funciona
1. Você envia: mensagens + definições de tools (schemas JSON)
│
▼
2. LLM retorna: AIMessage com tool_calls[] (não texto)
{ tool_calls: [{ name: "buscar_produtos", args: {...} }] }
│
▼
3. Seu código executa a tool e captura o resultado
│
▼
4. Você envia: ToolMessage com o resultado
│
▼
5. LLM processa e gera resposta final (ou chama mais tools)
Streaming
const chain = prompt.pipe(model).pipe(parser);
for await (const chunk of await chain.stream({ pergunta: "O que é CAC?" })) {
process.stdout.write(chunk); // imprime token por token
}
Callbacks
const handler = {
handleLLMStart: async (llm, messages) => {
console.log("LLM starting with", messages);
},
handleLLMEnd: async (output) => {
console.log("LLM finished:", output);
},
handleToolStart: async (tool, input) => {
console.log(`Tool ${tool.name} called with:`, input);
},
};
await chain.invoke(input, { callbacks: [handler] });
LangChain Expression Language (LCEL)
Introduzido em agosto 2023. Substitui as classes pesadas (LLMChain, SequentialChain, RetrievalQA) por um sistema de pipe operator. Ideia central: qualquer dois componentes que implementem Runnable podem ser conectados com .pipe().
Pipe Syntax
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
const prompt = ChatPromptTemplate.fromTemplate(
"Explique {conceito} em uma frase para um CEO"
);
const model = new ChatOpenAI({ model: "gpt-4o-mini" });
const parser = new StringOutputParser();
const chain = prompt.pipe(model).pipe(parser);
const resultado = await chain.invoke({ conceito: "NRR" });
A Interface Runnable
interface Runnable<Input, Output> {
invoke(input: Input, config?: RunnableConfig): Promise<Output>;
stream(input: Input, config?: RunnableConfig): AsyncGenerator<Output>;
batch(inputs: Input[], config?: RunnableConfig): Promise<Output[]>;
pipe<NewOutput>(next: Runnable<Output, NewOutput>): Runnable<Input, NewOutput>;
}
Componentes Utilitários
import { RunnableLambda } from "@langchain/core/runnables";
const formataContexto = RunnableLambda.from(async (docs: Document[]) => {
return docs.map(d => d.pageContent).join("\n\n");
});
const chain = retriever.pipe(formataContexto).pipe(prompt).pipe(model);
import { RunnablePassthrough } from "@langchain/core/runnables";
const chain = RunnablePassthrough.assign({
context: retriever.pipe(formatDocs),
}).pipe(prompt).pipe(model).pipe(parser);
// Input: { question: "O que é CAC?" }
// Antes do prompt: { question: "...", context: "...docs..." }
import { RunnableParallel } from "@langchain/core/runnables";
const parallelChain = new RunnableParallel({
resumo: chainDeResumo,
sentimento: chainDeSentimento,
topicos: chainDeTopicos,
});
const resultado = await parallelChain.invoke(texto);
// → { resumo: "...", sentimento: "positivo", topicos: [...] }
Quando Usar vs. Não Usar LCEL
| Use LCEL quando... | NÃO use LCEL quando... |
|---|---|
| Pipelines lineares simples | Lógica condicional complexa |
| RAG com retrieval + geração | Fluxos cíclicos |
| Precisa de streaming built-in | Human-in-the-loop |
| Batch processing automático | Estado persistente entre chamadas |
| Compatibilidade LangSmith | Múltiplos agentes cooperando |
Exemplo Completo: RAG com LCEL
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnablePassthrough } from "@langchain/core/runnables";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
const embeddings = new OpenAIEmbeddings();
const vectorStore = await MemoryVectorStore.fromTexts(
["CAC é o Custo de Aquisição de Cliente...", "NRR mede retenção..."],
[{}, {}],
embeddings
);
const retriever = vectorStore.asRetriever();
const prompt = ChatPromptTemplate.fromMessages([
["system", "Use o contexto abaixo para responder.\nContexto:\n{context}"],
["human", "{question}"],
]);
const model = new ChatOpenAI({ model: "gpt-4o-mini" });
const parser = new StringOutputParser();
const ragChain = RunnablePassthrough.assign({
context: ({ question }: { question: string }) =>
retriever.invoke(question).then(docs => docs.map(d => d.pageContent).join("\n")),
}).pipe(prompt).pipe(model).pipe(parser);
const resposta = await ragChain.invoke({ question: "O que é CAC?" });
// Streaming automático
for await (const token of await ragChain.stream({ question: "O que é NRR?" })) {
process.stdout.write(token);
}
Módulos Principais em Detalhe
import { ChatOpenAI } from "@langchain/openai";
import { ChatAnthropic } from "@langchain/anthropic";
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
// Todos têm a mesma interface
const openai = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const anthropic = new ChatAnthropic({ model: "claude-3-5-sonnet-20241022" });
const gemini = new ChatGoogleGenerativeAI({ model: "gemini-1.5-pro" });
// Utilitários de mensagens (novo no 0.3)
import { trimMessages, filterMessages } from "@langchain/core/messages";
const mensagensOtimizadas = trimMessages(historico, {
maxTokens: 4096,
tokenCounter: model.getNumTokens.bind(model),
strategy: "last", // mantém as mais recentes
});
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { OpenAIEmbeddings } from "@langchain/openai";
import { PineconeStore } from "@langchain/pinecone";
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
// 1. Load
const loader = new PDFLoader("relatorio-vendas.pdf");
const docs = await loader.load();
// 2. Split
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000, chunkOverlap: 200,
});
const chunks = await splitter.splitDocuments(docs);
// 3. Embed & Store
const vectorStore = await PineconeStore.fromDocuments(
chunks,
new OpenAIEmbeddings(),
{ pineconeIndex }
);
// 4. Retrieve com MMR (evita repetição)
const retriever = vectorStore.asRetriever({
k: 5,
searchType: "mmr",
});
Tipos de retriever: VectorStoreRetriever, MultiQueryRetriever, ContextualCompressionRetriever, ParentDocumentRetriever, EnsembleRetriever
| Chain (legado) | Equivalente LCEL moderno |
|---|---|
LLMChain | prompt.pipe(model).pipe(parser) |
SequentialChain | chain1.pipe(chain2).pipe(chain3) |
RetrievalQAChain | RAG com RunnablePassthrough.assign() |
ConversationChain | LangGraph com checkpointing |
MapReduceDocumentsChain | LangGraph com Send() |
LangChain em TypeScript
Estrutura de Pacotes
# Core — obrigatório sempre
npm install @langchain/core
# Providers — instale só os que usar
npm install @langchain/openai
npm install @langchain/anthropic
npm install @langchain/google-genai
# Utilitários, loaders, splitters
npm install langchain
# LangGraph — para agentes
npm install @langchain/langgraph
# LangSmith — tracing
npm install langsmith
@langchain/core passou a ser peer dependency. Instale explicitamente para evitar conflitos de tipo.Python vs. TypeScript
| Aspecto | Python | TypeScript/JS |
|---|---|---|
| Pipe operator | prompt | model | parser | prompt.pipe(model).pipe(parser) |
| Typing | Pydantic (runtime) | Zod + TypeScript (compile time) |
| State schema | TypedDict / Pydantic | Annotation.Root / Zod |
| Tool decorator | @tool decorator | tool() helper function |
Integração com Fastify (Streaming)
import Fastify from "fastify";
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
const fastify = Fastify();
// Instancia uma vez — modelos são stateless, seguro compartilhar
const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
const analisarCampanha = ChatPromptTemplate.fromMessages([
["system", "Você é especialista em marketing. Analise campanhas com metodologia TOC."],
["human", "Analise: {dados_campanha}"],
]).pipe(model).pipe(new StringOutputParser());
fastify.post("/analisar-campanha", async (request, reply) => {
const { dadosCampanha } = request.body as { dadosCampanha: string };
reply.raw.setHeader("Content-Type", "text/event-stream");
reply.raw.setHeader("Cache-Control", "no-cache");
for await (const token of await analisarCampanha.stream({
dados_campanha: dadosCampanha,
})) {
reply.raw.write(`data: ${JSON.stringify({ token })}\n\n`);
}
reply.raw.end();
});
Variáveis de Ambiente
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
# LangSmith (tracing automático)
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=ls__...
LANGCHAIN_PROJECT=v4mos-ai-prod
# LangGraph Cloud (se usar)
LANGGRAPH_API_KEY=...
Quando LangChain Ajuda vs. Quando Atrapalha
- Troca de providers sem reescrever código: A interface unificada de
ChatModelé genuinamente útil — trocar OpenAI por Anthropic é uma linha. - RAG padronizado: O pipeline loader → splitter → embeddings → vector store está bem testado e documentado.
- Integração com LangSmith: Tracing automático de todas as chamadas sem instrumentação manual.
- Prototipagem rápida: Para POCs, LCEL é expressivo e rápido de escrever.
- Equipe que já conhece o framework: Reduz curva de aprendizado em onboarding.
1. Abstrações Leaky
O team da OctoClaw reportou em 2024:
"Passamos mais tempo entendendo e debugando o LangChain do que construindo features. Quando precisávamos de comportamento de baixo nível, o framework intencionalmente abstrai tantos detalhes que frequentemente não era possível escrever o código de baixo nível necessário."
2. Latência das Abstrações
O stack de callbacks pode adicionar 100–500ms por chamada. Para pipelines de produção com SLA apertado, isso é relevante.
3. Breaking Changes Frequentes
De 0.0 para 0.1 para 0.2 para 0.3: breaking changes significativas a cada versão. Equipes sem dependências pinadas tiveram surpresas desagradáveis.
4. Documentação Fragmentada
Com tantos pacotes separados e versões diferentes, encontrar a documentação correta para a versão em uso é trabalhoso.
Guia de Decisão Rápida
Tarefa simples (prompt + resposta)?
→ OpenAI SDK direto ou LCEL simples
RAG com pipeline padronizado?
→ LangChain (LCEL + Retrieval)
Agente com tools e estado?
→ LangGraph (não AgentExecutor)
Multi-agentes ou workflows complexos?
→ LangGraph
Precisa de observabilidade completa?
→ LangSmith + LangGraph
Microserviço com latência crítica?
→ Considere SDK direto + instrumentação manual
Use Cases com Código
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnablePassthrough } from "@langchain/core/runnables";
import { HumanMessage, AIMessage } from "@langchain/core/messages";
const model = new ChatOpenAI({ model: "gpt-4o-mini" });
const retriever = vectorStore.asRetriever({ k: 4 });
const contextualizeQPrompt = ChatPromptTemplate.fromMessages([
["system", `Dado o histórico de chat, reformule a pergunta para ser standalone.
Se já for standalone, retorne como está.`],
new MessagesPlaceholder("chat_history"),
["human", "{input}"],
]);
const historyAwareRetriever = contextualizeQPrompt
.pipe(model)
.pipe(new StringOutputParser())
.pipe(retriever);
const qaPrompt = ChatPromptTemplate.fromMessages([
["system", "Responda usando o contexto abaixo.\nContexto: {context}"],
new MessagesPlaceholder("chat_history"),
["human", "{input}"],
]);
const ragChain = RunnablePassthrough.assign({
context: ({ input, chat_history }: any) =>
historyAwareRetriever
.invoke({ input, chat_history })
.then(docs => docs.map((d: any) => d.pageContent).join("\n")),
}).pipe(qaPrompt).pipe(model).pipe(new StringOutputParser());
// Uso com histórico
const chatHistory: (HumanMessage | AIMessage)[] = [];
const resposta = await ragChain.invoke({
input: "O que é a trava de ICP?",
chat_history: chatHistory,
});
import { ChatAnthropic } from "@langchain/anthropic";
import { z } from "zod";
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20241022" });
const LeadSchema = z.object({
nome: z.string().describe("Nome da empresa"),
setor: z.enum(["tech", "varejo", "saúde", "educação", "outro"]),
funcionarios: z.number().nullable(),
problemas: z.array(z.string()).describe("Problemas mencionados"),
urgencia: z.enum(["alta", "media", "baixa"]),
proximo_passo: z.string(),
});
// withStructuredOutput força output no schema
const extractor = model.withStructuredOutput(LeadSchema, {
name: "extrair_lead",
});
const lead = await extractor.invoke([{
role: "user",
content: `Extraia as informações: TechCorp, e-commerce, ~200 pessoas,
dificuldade séria em medir ROAS, quer resolver no próximo trimestre...`,
}]);
// → { nome: "TechCorp", setor: "tech", funcionarios: 200, urgencia: "alta", ... }
const calcularROAS = tool(
async ({ receita, gasto }) => {
return `ROAS: ${(receita/gasto).toFixed(2)}x`;
},
{
name: "calcular_roas",
description: "Calcula o ROAS (Return on Ad Spend)",
schema: z.object({
receita: z.number(),
gasto: z.number(),
}),
}
);
const modelWithTools = model.bindTools([calcularROAS]);
const messages = [new HumanMessage(
"Qual o ROAS com R$50k de receita e R$15k de gasto?"
)];
let response = await modelWithTools.invoke(messages);
while (response.tool_calls?.length > 0) {
messages.push(response);
for (const toolCall of response.tool_calls) {
const result = await calcularROAS.invoke(toolCall.args);
messages.push({ role: "tool", content: result, tool_call_id: toolCall.id } as any);
}
response = await modelWithTools.invoke(messages);
}
// → "O ROAS é de 3.33x..."
LangGraph: O Que É e Por Que Foi Criado
O Problema que o LangGraph Resolve
- Caixa preta: Não havia como observar ou modificar o estado do agente durante execução
- Sem controle de fluxo: Impossível criar lógica condicional complexa ou loops controlados
- Sem persistência nativa: Cada invocação era independente — conversas multi-turn exigiam gambiarras
- Single-agent apenas: Não suportava múltiplos agentes cooperando
- Sem human-in-the-loop: Impossível pausar e esperar aprovação humana no meio de uma execução
A Ideia Central
Agentes são grafos dirigidos onde:
- Nós (nodes) são funções de processamento
- Arestas (edges) definem o fluxo de execução
- Estado é compartilhado entre todos os nós
- Checkpoints persistem o estado a qualquer momento
Empresas usando em Produção
Conceitos Fundamentais do LangGraph
StateGraph — O Coração
import { StateGraph, START, END } from "@langchain/langgraph";
const grafo = new StateGraph(MeuSchema)
.addNode("processar", funcaoProcessar)
.addNode("validar", funcaoValidar)
.addEdge(START, "processar")
.addEdge("processar", "validar")
.addEdge("validar", END)
.compile();
const resultado = await grafo.invoke({ input: "dados" });
Nodes — O Trabalho Real
// Assinatura de um node
async function meuNode(
state: typeof MeuSchema.State,
config?: RunnableConfig
): Promise<Partial<typeof MeuSchema.State>> {
const resultado = await model.invoke(state.messages);
// Retorna apenas as keys que mudaram
return { messages: [resultado] };
}
Edges — Tipos
// Edge normal — transição fixa
graph.addEdge("nodeA", "nodeB");
// Conditional edge — decide com base no estado
function roteador(state: typeof MeuSchema.State): string {
if (state.precisaAprovacao) return "solicitar_aprovacao";
if (state.erros.length > 0) return "corrigir_erros";
return "finalizar";
}
graph.addConditionalEdges("processar", roteador);
// Send — paralelismo dinâmico (map-reduce)
import { Send } from "@langchain/langgraph";
function distribuirTarefas(state: typeof GraphState.State) {
return state.itens.map(item =>
new Send("processar_item", { item })
);
}
graph.addConditionalEdges("gerar_itens", distribuirTarefas, ["processar_item"]);
Como Funciona o Estado no LangGraph
O estado é composto de channels. Cada channel tem um tipo TypeScript, um reducer (define como novos valores se fundem) e um valor padrão.
Definindo Estado com Annotation.Root (padrão atual)
import { Annotation } from "@langchain/langgraph";
import { BaseMessage } from "@langchain/core/messages";
const EstadoAgente = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (x, y) => x.concat(y), // APPEND — cada node adiciona ao array
default: () => [],
}),
etapa: Annotation<string>({
reducer: (_, y) => y, // LAST WRITE WINS — substitui
default: () => "inicio",
}),
aprovado: Annotation<boolean>({
reducer: (_, y) => y,
default: () => false,
}),
});
Forma Moderna com StateSchema + Zod
import { StateSchema, MessagesValue, ReducedValue } from "@langchain/langgraph";
import { z } from "zod";
const EstadoCampanha = new StateSchema({
messages: MessagesValue, // reducer built-in para mensagens
errosEncontrados: new ReducedValue(z.array(z.string()), {
reducer: (atual, novo) => [...atual, ...novo], // append
default: () => [],
}),
etapaAtual: z.string().default("inicio"), // last write wins
aprovado: z.boolean().default(false),
});
Como o Estado Flui
Node A executa → retorna { messages: [nova_msg] }
│
▼
Reducer: estado.messages = concat(atual, [nova_msg])
│
▼
Node B executa → retorna { etapa: "processando" }
│
▼
Reducer: estado.etapa = "processando" (last write wins)
│
▼
... e assim por diante
Super-Steps (Modelo Pregel)
LangGraph usa o modelo Pregel do Google. Cada "super-step" é uma rodada onde múltiplos nós podem executar em paralelo. Após o super-step, o estado é consolidado via reducers — isso habilita o paralelismo com Send().
Patterns Essenciais do LangGraph
import { StateGraph, START, END, MessagesAnnotation } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { AIMessage } from "@langchain/core/messages";
const tools = [buscarMetricas];
const model = new ChatOpenAI({ model: "gpt-4o" }).bindTools(tools);
async function agente(state: typeof MessagesAnnotation.State) {
const resposta = await model.invoke(state.messages);
return { messages: [resposta] };
}
function deveContiunar(state: typeof MessagesAnnotation.State) {
const ultimaMensagem = state.messages[state.messages.length - 1] as AIMessage;
if (ultimaMensagem.tool_calls?.length > 0) return "tools";
return END;
}
const grafo = new StateGraph(MessagesAnnotation)
.addNode("agente", agente)
.addNode("tools", new ToolNode(tools))
.addEdge(START, "agente")
.addConditionalEdges("agente", deveContiunar)
.addEdge("tools", "agente") // após tools, volta pro agente
.compile();
import { MemorySaver, interrupt, Command } from "@langchain/langgraph";
async function solicitarAprovacao(state: typeof EstadoCampanha.State) {
// interrupt() pausa a execução e retorna ao chamador
const resposta = await interrupt({
mensagem: `Campanha gerada:\n${JSON.stringify(state.campanha)}\n\nAprova?`,
tipo: "aprovacao_campanha",
});
return { aprovado: resposta === "sim" };
}
// Checkpointer é OBRIGATÓRIO para HITL
const checkpointer = new MemorySaver();
const grafo = new StateGraph(EstadoCampanha)
.addNode("gerar_campanha", gerarCampanha)
.addNode("solicitar_aprovacao", solicitarAprovacao)
.addNode("publicar", publicarCampanha)
.addEdge(START, "gerar_campanha")
.addEdge("gerar_campanha", "solicitar_aprovacao")
.addConditionalEdges("solicitar_aprovacao",
(state) => state.aprovado ? "publicar" : END
)
.compile({ checkpointer });
const config = { configurable: { thread_id: "campanha-123" } };
// Passo 1: executa até o interrupt
const resultado1 = await grafo.invoke(
{ messages: [{ role: "user", content: "Crie campanha para produto X" }] },
config
);
// Passo 2: após aprovação humana, retoma
const resultado2 = await grafo.invoke(
new Command({ resume: "sim" }),
config
);
import { createSupervisor } from "@langchain/langgraph-supervisor";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const model = new ChatOpenAI({ model: "gpt-4o" });
// Agentes especialistas
const agenteAnalise = createReactAgent({
llm: model, tools: [buscarMetricas, gerarGrafico],
name: "agente_analise",
prompt: "Você é especialista em análise de dados de marketing.",
});
const agenteCopy = createReactAgent({
llm: model, tools: [buscarBestPractices, gerarCopy],
name: "agente_copy",
prompt: "Você é copywriter especializado em marketing digital.",
});
const agenteMidia = createReactAgent({
llm: model, tools: [configurarBid, selecionarAudiencia],
name: "agente_midia",
prompt: "Você é especialista em mídia paga.",
});
const supervisorWorkflow = createSupervisor({
agents: [agenteAnalise, agenteCopy, agenteMidia],
llm: model,
prompt: `Coordene uma equipe de especialistas em marketing.
- Performance → agente_analise
- Conteúdo → agente_copy
- Mídia → agente_midia`,
outputMode: "last_message",
});
const app = supervisorWorkflow.compile({ checkpointer: new MemorySaver() });
// Sub-grafo encapsulado
const subgrafoEnriquecimento = new StateGraph(EnriquecimentoState)
.addNode("buscar_linkedin", buscarLinkedin)
.addNode("buscar_cnpj", buscarCNPJ)
.addNode("score_lead", calcularScore)
.addEdge(START, "buscar_linkedin")
.addEdge("buscar_linkedin", "buscar_cnpj")
.addEdge("buscar_cnpj", "score_lead")
.addEdge("score_lead", END)
.compile();
// Grafo principal usa subgrafo como nó
// + Send() para processar N leads em paralelo
function distribuirLeads(state: any) {
return state.leads.map((lead: any) =>
new Send("enriquecer_lead", { lead })
);
}
const grafoPrincipal = new StateGraph(GrafoPrincipalState)
.addNode("enriquecer_lead", enriquecerLead)
.addConditionalEdges(START, distribuirLeads, ["enriquecer_lead"])
.addEdge("enriquecer_lead", END)
.compile();
const MapReduceState = Annotation.Root({
topicos: Annotation<string[]>({ reducer: (_, y) => y, default: () => [] }),
// Reducer append — agrega resultados de múltiplos nós paralelos
analises: Annotation<string[]>({
reducer: (x, y) => x.concat(y),
default: () => [],
}),
relatorioFinal: Annotation<string>({ reducer: (_, y) => y, default: () => "" }),
});
// Executa para CADA tópico em paralelo
async function analisarTopico(state: { topico: string }) {
const analise = await model.invoke([
{ role: "user", content: `Analise a trava de receita: ${state.topico}` },
]);
return { analises: [analise.content as string] };
}
const grafoMapReduce = new StateGraph(MapReduceState)
.addNode("analisar_topico", analisarTopico)
.addNode("gerar_relatorio", gerarRelatorio)
.addConditionalEdges(START,
(state) => state.topicos.map(t => new Send("analisar_topico", { topico: t })),
["analisar_topico"]
)
.addEdge("analisar_topico", "gerar_relatorio")
.addEdge("gerar_relatorio", END)
.compile();
const resultado = await grafoMapReduce.invoke({
topicos: ["ICP mal definido", "Funil sem métricas", "Follow-up inexistente"],
});
Checkpointing e Persistência
No LangGraph, checkpointing não é feature opcional — é a base de human-in-the-loop, resiliência, conversas multi-turn e time travel para debug.
Escolhendo o Checkpointer
| Checkpointer | Quando Usar |
|---|---|
MemorySaver | Dev Desenvolvimento, testes, demos. Volátil — perde tudo ao reiniciar. |
SqliteSaver | Prod Low Produção de baixa escala, instância única. |
PostgresSaver | Prod Produção multi-instância, alta disponibilidade. |
| Custom | Redis, DynamoDB, ou storage proprietário. |
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
import pg from "pg";
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
});
const checkpointer = new PostgresSaver(pool);
await checkpointer.setup(); // cria as tabelas necessárias
const grafo = workflow.compile({ checkpointer });
// Listar checkpoints de um thread (histórico completo)
const historico = grafo.getStateHistory(config);
for await (const checkpoint of historico) {
console.log(checkpoint.metadata.step, checkpoint.values);
}
// Pegar o estado atual
const estadoAtual = await grafo.getState(config);
// "Time travel" — retomar de checkpoint anterior
const configAntigo = {
configurable: {
thread_id: "user-123",
checkpoint_id: "checkpoint-abc123",
},
};
const resultadoRetomado = await grafo.invoke(null, configAntigo);
LangGraph em TypeScript
Pacotes e Instalação
npm install @langchain/langgraph @langchain/core
npm install @langchain/openai @langchain/anthropic
npm install @langchain/langgraph-checkpoint-sqlite
npm install @langchain/langgraph-checkpoint-postgres
Streaming com LangGraph em Fastify
fastify.post("/chat/:threadId", async (request, reply) => {
const { threadId } = request.params as { threadId: string };
const { message } = request.body as { message: string };
const config = { configurable: { thread_id: threadId } };
reply.raw.setHeader("Content-Type", "text/event-stream");
reply.raw.setHeader("Cache-Control", "no-cache");
reply.raw.setHeader("Connection", "keep-alive");
const stream = await grafo.streamEvents(
{ messages: [{ role: "user", content: message }] },
{ ...config, version: "v2" }
);
for await (const event of stream) {
if (event.event === "on_chat_model_stream") {
const chunk = event.data?.chunk;
if (chunk?.content) {
reply.raw.write(
`data: ${JSON.stringify({ type: "token", content: chunk.content })}\n\n`
);
}
}
if (event.event === "on_tool_start") {
reply.raw.write(
`data: ${JSON.stringify({ type: "tool_start", tool: event.name })}\n\n`
);
}
}
reply.raw.write(`data: ${JSON.stringify({ type: "done" })}\n\n`);
reply.raw.end();
});
Python vs TypeScript no LangGraph
| Aspecto | Python | TypeScript |
|---|---|---|
| State schema | TypedDict / Pydantic | StateSchema + Zod / Annotation.Root |
| Interrupt | interrupt() / NodeInterrupt | interrupt() |
| Checkpointer async | AsyncSqliteSaver | SqliteSaver |
| Package | langgraph | @langchain/langgraph |
LangGraph Cloud / Platform
LangGraph Platform resolve problemas de infraestrutura: escalabilidade horizontal, checkpoints gerenciados, filas para agentes long-running, Studio para debug visual e APIs REST automáticas.
LangGraph CLI
npm install -g @langchain/langgraph-cli
langgraph new meu-agente # cria projeto
langgraph dev # desenvolvimento com hot reload
langgraph build # build para produção
langgraph deploy # deploy para LangGraph Cloud
Self-Hosted com Docker
docker run -p 8123:8123 \
-e DATABASE_URL=postgresql://... \
-e LANGCHAIN_API_KEY=... \
langchain/langgraph-api
LangGraph Studio
IDE visual (desktop app) para: visualizar o grafo, inspecionar estado a cada step, "time travel" (voltar checkpoints anteriores), editar estado manualmente e debugar com breakpoints visuais.
LangSmith: Observabilidade
Setup em 2 Minutos
# .env — com essas 3 vars, TODO código LangChain/LangGraph
# envia traces ao LangSmith automaticamente
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=ls__seu_api_key_aqui
LANGCHAIN_PROJECT=v4mos-ai-producao
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
import OpenAI from "openai";
const openai = wrapOpenAI(new OpenAI()); // wrapa para tracing automático
const minhaFuncao = traceable(
async (input: string) => {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: input }],
});
return response.choices[0].message.content;
},
{ name: "minha_funcao", project_name: "v4mos-ai" }
);
import { evaluate } from "langsmith/evaluation";
await evaluate(
async (input) => ragChain.invoke({ question: input.question }),
{
data: "dataset-perguntas-marketing",
evaluators: [
{
evaluatorType: "labeled_criteria",
criteria: "relevance",
feedbackKey: "relevancia",
},
async (run, example) => {
const score = calcularSeuScore(
run.outputs?.output, example.outputs?.answer
);
return { key: "accuracy", score };
},
],
experimentPrefix: "rag-v2",
}
);
Use Cases Reais com LangGraph
const SupportState = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (x, y) => x.concat(y), default: () => [],
}),
categoriaTicket: Annotation<string | null>({
reducer: (_, y) => y, default: () => null,
}),
escalonado: Annotation<boolean>({
reducer: (_, y) => y, default: () => false,
}),
});
function decidirFluxo(state: typeof SupportState.State) {
if (state.categoriaTicket === "reclamacao_grave") return "escalonar";
if (state.categoriaTicket === "cancelamento") return "escalonar";
return "responder";
}
const supportAgent = new StateGraph(SupportState)
.addNode("classificar", classificarTicket)
.addNode("responder", responderAutomaticamente)
.addNode("escalonar", escalonarHumano)
.addEdge(START, "classificar")
.addConditionalEdges("classificar", decidirFluxo)
.addEdge("responder", END)
.addEdge("escalonar", END)
.compile({ checkpointer: new PostgresSaver(pool) });
const ResearchState = Annotation.Root({
pergunta: Annotation<string>({ reducer: (_, y) => y, default: () => "" }),
subperguntas: Annotation<string[]>({ reducer: (_, y) => y, default: () => [] }),
resultadosPesquisa: Annotation<Record<string, string>>({
reducer: (x, y) => ({ ...x, ...y }),
default: () => ({}),
}),
relatorio: Annotation<string>({ reducer: (_, y) => y, default: () => "" }),
});
const researchAgent = new StateGraph(ResearchState)
.addNode("decompor", decomporPergunta) // decompõe em sub-perguntas
.addNode("pesquisar", pesquisarSubpergunta) // pesquisa cada uma
.addNode("sintetizar", sintetizarRelatorio) // sintetiza tudo
.addEdge(START, "decompor")
.addConditionalEdges("decompor", distribuirPesquisas, ["pesquisar"])
.addEdge("pesquisar", "sintetizar")
.addEdge("sintetizar", END)
.compile();
const marketingAgent = new StateGraph(CampaignState)
.addNode("diagnosticar", diagnosticarTravas) // identifica as 8 travas TOC
.addNode("gerar_campanha", gerarCampanha) // gera copy + audiência + bid
.addNode("aguardar_aprovacao", aguardarAprovacao) // HITL: interrupt()
.addNode("publicar", publicarCampanha) // publica no Meta + Google
.addEdge(START, "diagnosticar")
.addEdge("diagnosticar", "gerar_campanha")
.addEdge("gerar_campanha", "aguardar_aprovacao")
.addConditionalEdges("aguardar_aprovacao",
(state) => state.aprovada ? "publicar" : END
)
.addEdge("publicar", END)
.compile({ checkpointer: new PostgresSaver(pool) });
Migração: AgentExecutor → LangGraph
Antes vs Depois
import { AgentExecutor, createOpenAIToolsAgent } from "langchain/agents";
// Caixa preta — você não controla o loop
const agent = await createOpenAIToolsAgent({ llm: model, tools, prompt });
const agentExecutor = new AgentExecutor({ agent, tools });
const resultado = await agentExecutor.invoke({ input: "Analise o ROAS" });
import { StateGraph, START, END, MessagesAnnotation } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";
const model = new ChatOpenAI({ model: "gpt-4o" }).bindTools(tools);
async function chamarModelo(state: typeof MessagesAnnotation.State) {
const resposta = await model.invoke(state.messages);
return { messages: [resposta] };
}
function decidirProximoPasso(state: typeof MessagesAnnotation.State) {
const ultima = state.messages[state.messages.length - 1] as AIMessage;
return ultima.tool_calls?.length ? "tools" : END;
}
const agente = new StateGraph(MessagesAnnotation)
.addNode("agente", chamarModelo)
.addNode("tools", new ToolNode(tools))
.addEdge(START, "agente")
.addConditionalEdges("agente", decidirProximoPasso)
.addEdge("tools", "agente")
.compile({ checkpointer: new MemorySaver() }); // agora tem persistência!
Atalho: createReactAgent
import { createReactAgent } from "@langchain/langgraph/prebuilt";
// Drop-in replacement para AgentExecutor
const agente = createReactAgent({
llm: model,
tools: [buscarMetricas, calcularROAS],
checkpointSaver: new MemorySaver(),
});
const resultado = await agente.invoke({
messages: [{ role: "user", content: "Analise o ROAS" }],
});
Checklist de Migração
- Identificar todos os AgentExecutor no codebase
- Definir schema de estado para cada agente
- Extrair tools existentes (compatíveis sem mudança)
- Criar nó "agente" com o modelo + bindTools
- Usar ToolNode para execução das tools
- Implementar função de roteamento (continua vs. termina)
- Configurar checkpointer (MemorySaver dev, Postgres prod)
- Compilar e testar com os mesmos inputs
- Habilitar LANGCHAIN_TRACING_V2=true e comparar traces
- Canary deploy (10% do tráfego) e comparar latência/qualidade
Tabela de Comparação Final
| Aspecto | LCEL / Chains | AgentExecutor | LangGraph |
|---|---|---|---|
| Status | Ativo | Depreciado v0.2 | Ativo |
| Caso de uso ideal | Pipelines fixos / RAG | Evitar | Agentes e workflows complexos |
| Fluxo de controle | Linear / paralelo fixo | Loop automático (caixa preta) | Grafo dirigido — controle total |
| Estado | Stateless por padrão | Implícito | Explícito e persistente |
| Persistência | Manual | Manual e limitada | Nativa via checkpointing |
| Human-in-the-loop | Não suportado | Não suportado | Nativo com interrupt() |
| Multi-agent | Não | Não | Nativo (supervisor, swarm) |
| Paralelismo | RunnableParallel | Não | Send() para map-reduce |
| Debug | Difícil | Muito difícil | LangGraph Studio (visual) |
| Streaming | Nativo stream() | Limitado | Nativo streamEvents() |
| TypeScript typing | Boa | Limitada | Excelente |
| Complexidade de código | Baixa–Média | Baixa (opaca) | Média–Alta |
| Subgrafos | Não | Não | Sim |
Guia de Decisão Final
Sua task:
│
├── "Prompt + resposta simples"
│ → LCEL: prompt.pipe(model).pipe(parser)
│
├── "RAG com retrieval + geração"
│ → LCEL com Retrieval
│
├── "Extração de dados estruturados"
│ → LCEL com model.withStructuredOutput()
│
├── "Agente que decide quais tools chamar"
│ ├── Simples, sem persistência?
│ │ → createReactAgent (LangGraph prebuilt)
│ └── Com estado, HITL, ou multi-turn?
│ → StateGraph customizado
│
├── "Múltiplos agentes cooperando"
│ → LangGraph + createSupervisor
│
├── "Pausar e esperar aprovação humana"
│ → LangGraph com interrupt() + checkpointing
│
├── "Processar 100 documentos em paralelo e agregar"
│ → LangGraph com Send() (map-reduce)
│
└── "Tenho AgentExecutor legado"
→ Migrar: use createReactAgent como passo 1
Referências: Blog LangChain v0.3 · LangGraph JS Docs · DeepWiki LangGraph JS · Microsoft Educator Blog · Aurelio AI LCEL · OctoClaw/Octomind · Focused.io Migration Guide · DEV.to LangGraph Concepts · LangSmith Observability Docs
V4MOS.AI Research · Março 2026 · Versões: LangChain 0.3 / LangGraph 1.0+