from pathlib import Path from typing import TypedDict from langgraph.graph import StateGraph, START, END from langgraph.checkpoint.memory import InMemorySaver from langgraph.types import interrupt, Command class Job(TypedDict, total=False): note: str report: str approved: bool verified: bool def prepare(state: Job): text = state["note"].strip() if not text: raise ValueError("The note must not be empty") return {"report": "Reviewed lab note: " + text + "\n"} def approve(state: Job): decision = interrupt({"proposed_report": state["report"]}) return {"approved": decision is True} def route(state: Job): return "write" if state["approved"] else "stop" def write(state: Job): Path("output").mkdir(exist_ok=True) Path("output/report.txt").write_text(state["report"], encoding="utf-8") return {} def verify(state: Job): actual = Path("output/report.txt").read_text(encoding="utf-8") if actual != state["report"]: raise RuntimeError("Verification failed") return {"verified": True} builder = StateGraph(Job) builder.add_node("prepare", prepare) builder.add_node("approve", approve) builder.add_node("write", write) builder.add_node("verify", verify) builder.add_edge(START, "prepare") builder.add_edge("prepare", "approve") builder.add_conditional_edges("approve", route, {"write": "write", "stop": END}) builder.add_edge("write", "verify") builder.add_edge("verify", END) graph = builder.compile(checkpointer=InMemorySaver()) if __name__ == "__main__": config = {"configurable": {"thread_id": "lab-1"}} pending = graph.invoke({"note": "Backups belong in ./backups."}, config) print("Proposal:", pending["__interrupt__"][0].value) approved = input("Write this local report? [y/N] ").strip().lower() == "y" result = graph.invoke(Command(resume=approved), config) print("Approved:", result.get("approved", False)) print("Verified:", result.get("verified", False))