Add advanced LangGraph learning examples and guides
This commit is contained in:
112
17_state_history.py
Normal file
112
17_state_history.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""17 - Checkpoint state history, updates, replay, and forks.
|
||||
|
||||
This example is completely local: ``InMemorySaver`` stores checkpoints in RAM.
|
||||
A checkpoint config (thread_id + checkpoint_id) identifies one exact point in time.
|
||||
|
||||
Run: python 17_state_history.py
|
||||
"""
|
||||
|
||||
from typing import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
value: int
|
||||
note: str
|
||||
|
||||
|
||||
def increment(state: State) -> dict:
|
||||
"""First deterministic step."""
|
||||
return {"value": state["value"] + 1, "note": "incremented"}
|
||||
|
||||
|
||||
def double(state: State) -> dict:
|
||||
"""Second deterministic step."""
|
||||
return {"value": state["value"] * 2, "note": "doubled"}
|
||||
|
||||
|
||||
def build_graph():
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("increment", increment)
|
||||
builder.add_node("double", double)
|
||||
builder.add_edge(START, "increment")
|
||||
builder.add_edge("increment", "double")
|
||||
builder.add_edge("double", END)
|
||||
return builder.compile(checkpointer=InMemorySaver())
|
||||
|
||||
|
||||
def short_config(config: dict) -> dict:
|
||||
"""Only display the portable checkpoint identity, not internal metadata."""
|
||||
configurable = config["configurable"]
|
||||
return {
|
||||
"thread_id": configurable["thread_id"],
|
||||
"checkpoint_id": configurable.get("checkpoint_id"),
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
graph = build_graph()
|
||||
thread_config = {"configurable": {"thread_id": "history-demo"}}
|
||||
|
||||
print("=== Initial run ===")
|
||||
result = graph.invoke({"value": 3, "note": "input"}, thread_config)
|
||||
print("result:", result)
|
||||
assert result["value"] == 8
|
||||
|
||||
# get_state with only a thread_id returns that thread's latest snapshot.
|
||||
latest = graph.get_state(thread_config)
|
||||
print("latest values:", latest.values)
|
||||
print("latest next:", latest.next)
|
||||
print("latest config:", short_config(latest.config))
|
||||
|
||||
# History is returned newest first. Every StateSnapshot has values, next,
|
||||
# config, metadata, created_at, parent_config, and tasks.
|
||||
history = list(graph.get_state_history(thread_config))
|
||||
print("\n=== State history (newest first) ===")
|
||||
for index, snapshot in enumerate(history):
|
||||
print(
|
||||
index,
|
||||
"values=", snapshot.values,
|
||||
"next=", snapshot.next,
|
||||
"checkpoint=", short_config(snapshot.config),
|
||||
)
|
||||
|
||||
# Select the checkpoint immediately after `increment`: `double` is pending.
|
||||
before_double = next(snapshot for snapshot in history if snapshot.next == ("double",))
|
||||
checkpoint_config = before_double.config
|
||||
assert before_double.values["value"] == 4
|
||||
|
||||
print("\n=== Replay from an historical checkpoint ===")
|
||||
# Passing the snapshot's config and None resumes its pending work. Earlier
|
||||
# nodes are not rerun, so only `double` executes.
|
||||
replayed = graph.invoke(None, checkpoint_config)
|
||||
print("replayed:", replayed)
|
||||
assert replayed["value"] == 8
|
||||
|
||||
print("\n=== Fork by updating an historical checkpoint ===")
|
||||
# update_state does not mutate history. It creates a new checkpoint derived
|
||||
# from checkpoint_config. The returned config identifies the new branch.
|
||||
fork_config = graph.update_state(
|
||||
checkpoint_config,
|
||||
{"value": 10, "note": "human correction"},
|
||||
)
|
||||
fork_snapshot = graph.get_state(fork_config)
|
||||
print("fork before resume:", fork_snapshot.values, "next=", fork_snapshot.next)
|
||||
print("fork config:", short_config(fork_config))
|
||||
|
||||
forked = graph.invoke(None, fork_config)
|
||||
print("forked result:", forked)
|
||||
assert forked["value"] == 20
|
||||
|
||||
# The old exact checkpoint still contains 4; checkpoint configs make
|
||||
# time-travel explicit even after the thread acquires newer branches.
|
||||
old_snapshot = graph.get_state(checkpoint_config)
|
||||
assert old_snapshot.values["value"] == 4
|
||||
print("original historical value is still:", old_snapshot.values["value"])
|
||||
print("\nAll state-history demonstrations passed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user