LangGraph
Implemente uma proposta de compra que exige revisão antes de executar. Você começará com preparar e retomar locais e depois representará consulta, decisão, revisão e execução como nós no LangGraph. O preço e a compra são fictícios. O laboratório exige uma pausa observável, uma retomada com decisão e a prova de que repetir a retomada não duplica o efeito.
A evidência do framework precisa ser produzida com uma versão fixa, o checkpointer escolhido e um identificador estável da execução. O exemplo JavaScript da leitura é uma referência de comportamento, não uma implementação de LangGraph. A entrega deverá separar a validação local do teste integrado e declarar se a persistência usada suporta apenas a sessão corrente ou também reinício de processo.
BashLangGraphAgentesAo terminar esta aula
- Contadores ajudam a identificar reexecução na retomada.
- Retry de leitura e reconciliação de efeito são estratégias diferentes.
- O ramo recusado precisa terminar sem executar a operação.
Antes de continuar: Leitura: LangGraph
Defina o estado e percorra os dois caminhos
FundamentosSalve o simulador da leitura como compra.cjs e execute-o primeiro. Depois salve o código da resolução integrada deste laboratório como graph.mjs, seguindo o setup da seção final. Observe que preparar não emite compra: ele produz uma proposta aguardando. Escreva os campos do estado e marque quais são criados em cada etapa. Acrescente uma regra de preço para decidir quando a revisão será necessária e crie casos com valor abaixo, acima e exatamente no limiar. A saída deve identificar o caminho seguido. Evite testar somente valores muito distantes do limite, pois eles não detectam a troca de operador que pode alterar a política.
Execute uma decisão negativa em uma proposta nova e confira que o mapa não cresce. Execute uma decisão com revisão antiga e capture o erro. Esses cenários mostram duas interrupções diferentes: recusa é uma decisão legítima que termina o caminho; revisão inválida é uma tentativa de aplicar a decisão à proposta errada. O usuário precisa receber mensagens diferentes, e o registro operacional deve preservar a razão. Não transforme ambas em uma exceção genérica chamada falha do agente.
Transfira a semântica ao grafo real
FundamentosInstale a versão de LangGraph escolhida e consulte a API de grafo, persistência e interrupts dessa versão. Crie nós pequenos para consultar, decidir, revisar e executar, com aresta condicional após decidir. Configure o checkpointer e a identidade da execução. Use uma ferramenta de efeito que só grava um recibo fictício. Ao interromper, apresente a proposta completa e guarde o identificador necessário à retomada. Não inicie uma nova execução com a mesma mensagem como substituto da continuação, pois isso pode recriar a proposta.
Adicione um contador a cada nó e retome com aprovação. O contador mostra qual código foi reexecutado, incluindo a parte anterior à interrupção quando aplicável. Se existir um efeito antes da pausa, remova-o ou torne-o idempotente antes de continuar. A posição do contador serve para entender execução; a posição de um pagamento mudaria a segurança. Compare seu trace com a documentação de interrupts e explique o caminho observado, em vez de presumir que o runtime conservou uma pilha de execução congelada.
Diferencie retry de reconciliação
FundamentosFaça consultar falhar uma vez com erro transitório e depois funcionar. Configure tentativas limitadas e registre a sequência. Depois, faça executar gravar o recibo e lançar um timeout. A nova tentativa precisa consultar pela chave original e retornar o recibo existente. O primeiro caso é retry de leitura; o segundo é reconciliação de resultado incerto. Uma política que repete automaticamente todas as exceções pode resolver o primeiro e duplicar o segundo, apesar de usar o mesmo mecanismo de tratamento de falhas.
Reinicie o processo entre a pausa e a aprovação. Com armazenamento em memória, documente a perda; com checkpointer durável, recupere o estado usando a mesma identidade. Repita também depois do efeito para verificar que o registro de efeitos não foi perdido. Se o checkpointer persistir e o ledger permanecer em memória, você terá um grafo recuperável que ainda pode repetir uma compra. A evidência deve mostrar as duas persistências, porque elas protegem informações diferentes.
Revise a arquitetura a partir dos eventos
InfraestruturaApresente o desenho do grafo e uma tabela com nó, entrada relevante, atualização e possibilidade de efeito. Inclua os históricos de aprovação, rejeição e retomada repetida. Declare as condições de retry e o destino de erros permanentes. Sua decisão de projeto pode preferir a API de grafo por clareza de branching ou a funcional por proximidade com código sequencial, mas precisa conservar as mesmas condições de segurança. A sintaxe escolhida não reduz a necessidade de avaliar caminhos e efeitos.
Resolução integrada, ambiente e evidência
FundamentosSalve como graph.mjs e use uma pasta nova, pois checkpoints.sqlite e efeitos.sqlite conservam os dados entre comandos. A resolução implementa quatro nós e arestas condicionais no LangGraph1.4.19, com SqliteSaver1.0.4, interrupt e Command. preparar falha uma vez na consulta e faz retry, depois grava a pausa. retomar ocorre em um processo novo: recupera a proposta, aplica aprovação e executa. O executor grava um recibo SQLite e lança TIMEOUT_APOS_RECIBO; seu retry consulta a mesma chave e reconcilia com um único efeito. A tarefa C8 recusada não grava recibo. consultar roda em um terceiro processo e recupera checkpoint e ledger. Os três comandos foram executados com sucesso. SQLite é a persistência local deste laboratório; o recibo não é pagamento remoto e uma API financeira ainda precisaria de idempotência ou reconciliação própria. Para refazer desde zero, use outra pasta ou outros ids e thread_ids, sem apagar dados de um projeto real.
↗ LangGraph JavaScript — Graph API overview↗ LangGraph JavaScript — Interrupts↗ Checkpointers
npm init -y
npm install --save-exact @langchain/langgraph@1.4.19 @langchain/langgraph-checkpoint-sqlite@1.0.4 better-sqlite3@12.11.1 zod@4.6.5
node graph.mjs preparar
node graph.mjs retomar
node graph.mjs consultarExercício aplicado
Uma compra C7 espera revisão e poderá ser retomada duas vezes. Crie um grafo com decisão condicional, pausa humana e execução idempotente simulada, além de um caso de falha transitória.
- Execute o simulador com aprovação, recusa e revisão antiga.
- Implemente os quatro nós no LangGraph com versão e checkpointer registrados.
- Retome a mesma execução e conte os efeitos.
- Injete timeout depois do recibo e mostre a reconciliação pela chave.
Abrir resolução comentada
Salve como graph.mjs e use uma pasta nova, pois checkpoints.sqlite e efeitos.sqlite conservam os dados entre comandos. A resolução implementa quatro nós e arestas condicionais no LangGraph1.4.19, com SqliteSaver1.0.4, interrupt e Command. preparar falha uma vez na consulta e faz retry, depois grava a pausa. retomar ocorre em um processo novo: recupera a proposta, aplica aprovação e executa. O executor grava um recibo SQLite e lança TIMEOUT_APOS_RECIBO; seu retry consulta a mesma chave e reconcilia com um único efeito. A tarefa C8 recusada não grava recibo. consultar roda em um terceiro processo e recupera checkpoint e ledger. Os três comandos foram executados com sucesso. SQLite é a persistência local deste laboratório; o recibo não é pagamento remoto e uma API financeira ainda precisaria de idempotência ou reconciliação própria. Para refazer desde zero, use outra pasta ou outros ids e thread_ids, sem apagar dados de um projeto real.
Uma proposta aguardando passa à execução somente depois de uma decisão válida. O ramo recusado devolve status próprio e não modifica o ledger. A retomada repetida usa C7:1 e recupera o resultado já gravado. O teste de revisão antiga falha antes dessa consulta, demonstrando que idempotência não equivale a autorização.
No framework real, o relatório deve identificar o checkpointer e mostrar um reinício efetivo, quando suportado. Caso a execução integrada não esteja disponível, apresente o resultado local como preparação e não como prova de durable execution. A conclusão aceita depende de eventos observados e contagem de efeitos.
import { StateGraph,StateSchema,START,END,interrupt,Command } from '@langchain/langgraph';
import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
import Database from 'better-sqlite3';
import { z } from 'zod';
import assert from 'node:assert/strict';
const State=new StateSchema({id:z.string(),preco:z.number().default(0),revisao:z.number().default(1),aprovado:z.boolean().default(false),recibo:z.string().default('')});
const db=new Database('efeitos.sqlite');db.exec('CREATE TABLE IF NOT EXISTS efeitos(chave TEXT PRIMARY KEY,recibo TEXT NOT NULL)');
const checkpointer=SqliteSaver.fromConnString('checkpoints.sqlite');let consultas=0,efeitos=0;
const grafo=new StateGraph(State)
.addNode('consultar',async()=>{if(++consultas===1)throw new Error('TRANSITORIO');return {preco:300};},{retryPolicy:{maxAttempts:2,initialInterval:1,jitter:false,retryOn:e=>e.message==='TRANSITORIO'}})
.addNode('decidir',s=>({aprovado:s.preco<200}))
.addNode('revisar',s=>{const d=interrupt({id:s.id,revisao:s.revisao,preco:s.preco});if(d.revisao!==s.revisao)throw new Error('revisao incorreta');return {aprovado:d.aprovado};})
.addNode('executar',s=>{
const key=s.id+':'+s.revisao;
if(!s.aprovado)throw new Error('nao aprovado');
const anterior=db.prepare('SELECT recibo FROM efeitos WHERE chave=?').get(key);
if(anterior)return {recibo:anterior.recibo}; // Reconcilia a primeira tentativa.
db.prepare('INSERT INTO efeitos(chave,recibo) VALUES(?,?)').run(key,'REC-'+key);efeitos++;
throw new Error('TIMEOUT_APOS_RECIBO');
},{retryPolicy:{maxAttempts:2,initialInterval:1,jitter:false,retryOn:e=>e.message==='TIMEOUT_APOS_RECIBO'}})
.addEdge(START,'consultar').addEdge('consultar','decidir')
.addConditionalEdges('decidir',s=>s.aprovado?'executar':'revisar')
.addConditionalEdges('revisar',s=>s.aprovado?'executar':END)
.addEdge('executar',END).compile({checkpointer});
const fase=process.argv[2]||'preparar',cfg={configurable:{thread_id:'T7'}};
if(fase==='preparar'){
const pausa=await grafo.invoke({id:'C7'},cfg);assert(pausa.__interrupt__?.length);console.log('pausa',pausa);
}else if(fase==='retomar'){
const final=await grafo.invoke(new Command({resume:{revisao:1,aprovado:true}}),cfg);
assert.equal(final.recibo,'REC-C7:1');assert.equal(db.prepare('SELECT count(*) n FROM efeitos').get().n,1);
console.log('retomada',final,'efeitos',efeitos);
const cfg2={configurable:{thread_id:'T8'}};
await grafo.invoke({id:'C8'},cfg2);
const recusa=await grafo.invoke(new Command({resume:{revisao:1,aprovado:false}}),cfg2);
assert.equal(recusa.aprovado,false);assert.equal(efeitos,1);console.log('recusa',recusa);
console.log('checkpoint', (await grafo.getState(cfg)).values);
}else if(fase==='consultar'){
console.log('checkpoint recuperado em novo processo',(await grafo.getState(cfg)).values);
console.log('efeitos',db.prepare('SELECT * FROM efeitos').all());
}
checkpointer.db.close();db.close();
Como conferir seu resultado
- Recusa não emite compra.
- Retomada repetida devolve o mesmo recibo.
- A evidência distingue persistência do grafo e do registro de efeitos.
Aplique em um problema novo
Primeiro resolva sem consultar a resposta. Explique suas decisões e guarde a evidência. A conclusão de leitura é independente desta autoavaliação.
Confira seus pré-requisitos
- Modelar nós/arestas/estado.
- Separar checkpoint e efeito idempotente.
Grafo: compras >=500 exigem revisão. A compra P7=501 é pausada na thread T7; checkpoint durável é recuperado em processo novo e a aprovação corresponde a P7. Serviço grava recibo R7 e perde resposta. Uma segunda compra P8 é recusada. Resolva retomadas e efeitos.
Conferir raciocínio e critérios de domínio
P7 entra na aresta de revisão porque 501>=500. O processo novo retoma T7 com a proposta P7, e a aprovação autoriza somente essa versão/argumentos.
Após o timeout, P7 fica desconhecida até a consulta da intenção estável retornar R7; a retomada usa esse recibo, sem emitir outra compra.
O ledger registra um efeito para P7 e zero para P8 recusada. Nós anteriores podem ser reexecutados, mas checkpoint de posição não substitui reconciliação do efeito externo.
Evidências para autoavaliação ou revisão por pares
- Aresta e aprovação: P7 entra na aresta de revisão porque 501>=500. O processo novo retoma T7 com a proposta P7, e a aprovação autoriza somente essa versão/argumentos.
- Reconciliação na retomada: Após o timeout, P7 fica desconhecida até a consulta da intenção estável retornar R7; a retomada usa esse recibo, sem emitir outra compra.
- Efeitos e reexecução: O ledger registra um efeito para P7 e zero para P8 recusada. Nós anteriores podem ser reexecutados, mas checkpoint de posição não substitui reconciliação do efeito externo.
Um erro frequente
Interrupt congela pilha inteira para sempre.
O código do nó pode ser reexecutado ao retomar.
Teste sua compreensão
Responda com suas palavras antes de abrir o comentário. Saber explicar uma decisão é parte do domínio.
1. Por que efeitos antes de interrupt são perigosos?
O nó pode ser reexecutado na retomada.
A pausa não garante congelamento permanente da função em memória.
2. Acesso negado deve receber retry?
Normalmente não, pois é uma condição permanente para aquela identidade.
Tentativas adicionais não concedem a permissão ausente.
3. Checkpointer substitui chave de idempotência?
Não. Um guarda posição de execução; a outra identifica o efeito.
A queda pode separar o efeito remoto do checkpoint local.
Seu progresso fica salvo neste navegador. Concluir a leitura não substitui demonstrar o domínio nos exercícios.
Referências e aprofundamento
Documentação oficial e trabalhos originais. As referências registram o escopo e as limitações para você conferir o que sustentam.
- Graph API overview
LangChain • consulta: 2026-10-06
APIsStateGraph, nós, arestas, condicionais e reducers para combinar atualizações.
Limites: Reducers resolvem combinação de dados; conflitos semânticos de agentes precisam de regra editorial/negocial explícita.
- Thinking in LangGraph
LangChain • consulta: 2026-10-06
LangGraphAgentesDecompor passos, representar estado, roteamento, recuperação e escolhas entre loops e grafos.
Limites: Tutorial de desenho; ajustar o exemplo ao caso de uso e à versão instalada.
- Persistence
LangChain • consulta: 2026-10-06
FundamentosCheckpointer de estado por thread versus store de memória entre threads.
Limites: InMemorySaver é demonstrativo; usar backend durável para sobreviver a reinícios.
- Checkpointers
LangChain • consulta: 2026-10-06
FundamentosCheckpoints, threads, retomada e tolerância a falhas.
Limites: Checkpoint de estado não torna ações externas exatamente uma vez; planejar idempotência.
- Fault tolerance
LangChain • consulta: 2026-10-06
FundamentosRetries de nós, backoff, timeouts e tratamento de erros.
Limites: Time-out e retry não desfazem efeitos já executados; APIs avançadas exigem versão mínima.
- Interrupts
LangChain • consulta: 2026-10-06
FundamentosPausa human-in-the-loop e retomada com estado persistido.
Limites: Código anterior a interrupt pode reexecutar; efeitos devem ser idempotentes e decisão humana vinculada à ação.
- Use the functional API
LangChain • consulta: 2026-10-06
APIsTasks, cache de resultados, retomada e operações externas isoladas.
Limites: Retomada exige sequência estável de tasks/interrupções; usar idempotência para efeitos.
- LangGraph JavaScript — Interrupts
LangChain • consulta: 2026-10-06
JavaScriptLangGraphAgentesPausa com interrupt, thread_id, checkpoint e retomada com Command; o nó reinicia e efeitos precisam de idempotência.
Limites: Retomada exige o mesmo thread_id e configuração compatível. Exemplo com MemorySaver é local; aprovação do workflow não substitui autorização no executor.
- LangGraph JavaScript — Graph API overview
LangChain • consulta: 2026-10-06
JavaScriptLangGraphAPIsAgentesAPI JavaScript para StateGraph, schemas, nodes, edges condicionais, reducers e compilação com checkpoint.
Limites: Documentação acompanha o SDK atual; fixar e testar a versão usada. Checkpointer em memória não demonstra persistência após reinício.