Back to Projects
๐Ÿ’พ
llmintermediate

Instrumenting the Crash-Safe Pipeline With Real Tracing and Evaluation

This project adds real LangSmith tracing to LangGraph's crash-safe research pipeline, and builds a real evaluator that checks whether the pipeline reaches its complete, correct result โ€” whether or not a real crash and resume happened along the way.

2-3 hours
ยทLangSmith

Problem Statement

LangGraph's Crash-Safe Pipeline project (Module 5-7) already proved, through real, direct testing, a genuinely important, honest nuance: completed steps never re-run after a crash, but an interrupted step restarts from its own beginning. That real finding was confirmed once, by hand, in a controlled test. A real production team needs a repeatable, real way to keep confirming this โ€” not just trust it was true the one time it was tested. This project adds real tracing to the exact, unmodified pipeline nodes, and builds a real evaluator checking that the pipeline reaches its complete, correct set of results, regardless of whether a crash and resume genuinely happened during that specific run.

Dataset

Pipeline Completeness Real Outcome Dataset

A real, small dataset covering both a normal, uninterrupted pipeline run and a resumed-after-crash run, both pairing real input with the same, real expected final set of completed steps โ€” since a resumed pipeline should reach the identical, correct final result as an uninterrupted one.

2 real scenarios: normal run, resumed-after-crash runDerived directly from LangGraph Module 7's own, already-verified crash-and-resume test

Architecture Decisions

This project adds @traceable directly to the three exact, unmodified pipeline node functions from the original LangGraph project, confirmed directly through real execution to change nothing about their actual behavior. The real evaluator deliberately checks the final SET of completed steps rather than their exact order or count, since a resumed pipeline's full execution log genuinely differs from an uninterrupted one (the interrupted step appears twice in the raw log) โ€” but the real, correct FINAL RESULT should be identical either way, which is the real, meaningful thing worth actually measuring.

Built On

  • โ€ขLangGraph Project 2 โ€” Crash-Safe Research Pipeline With Guaranteed Resume, whose exact, unmodified code this project instruments directly
  • โ€ขLangSmith Module 1 โ€” Tracing (@traceable)
  • โ€ขLangSmith Module 2 โ€” Datasets (curated vs production-derived)
  • โ€ขLangSmith Module 3 โ€” Evaluators (rule-based/custom/LLM-as-judge)

Tracing the Exact, Unmodified Pipeline Nodes

The three real node functions from the original crash-safe pipeline project โ€” gather_sources, extract_facts, synthesize โ€” each get a real @traceable decorator added directly above their existing definition. Running this traced pipeline through Module 7's exact real checkpointing setup confirms directly: the pipeline produces the identical, correct real result, with tracing changing nothing about its actual behavior โ€” exactly the same real confirmation already proven for the support router in Project 1.

01_tracing_the_pipeline.py
python
1from langsmith import traceable
2from typing_extensions import TypedDict, Annotated
3from langgraph.graph import StateGraph, START, END
4from langgraph.checkpoint.sqlite import SqliteSaver
5import sqlite3, json
6import warnings
7warnings.filterwarnings("ignore")
8
9print("=== TRACING THE EXACT, UNMODIFIED CRASH-SAFE PIPELINE ===\n")
10
11def add_list(a: list, b: list) -> list:
12    return a + b
13
14class ResearchState(TypedDict):
15    completed_steps: Annotated[list, add_list]
16    crash_now: bool
17
18LOG_FILE = "execution_log_eval.json"
19
20def log_step(name: str):
21    try:
22        with open(LOG_FILE) as f:
23            log = json.load(f)
24    except FileNotFoundError:
25        log = []
26    log.append(name)
27    with open(LOG_FILE, "w") as f:
28        json.dump(log, f)
29
30@traceable(name="gather_sources", run_type="chain")
31def gather_sources(state: ResearchState) -> dict:
32    log_step("gather_sources")
33    return {"completed_steps": ["gather_sources"]}
34
35@traceable(name="extract_facts", run_type="chain")
36def extract_facts(state: ResearchState) -> dict:
37    log_step("extract_facts")
38    return {"completed_steps": ["extract_facts"]}
39
40@traceable(name="synthesize", run_type="chain")
41def synthesize(state: ResearchState) -> dict:
42    log_step("synthesize")
43    return {"completed_steps": ["synthesize"]}
44
45graph_builder = StateGraph(ResearchState)
46graph_builder.add_node("gather_sources", gather_sources)
47graph_builder.add_node("extract_facts", extract_facts)
48graph_builder.add_node("synthesize", synthesize)
49graph_builder.add_edge(START, "gather_sources")
50graph_builder.add_edge("gather_sources", "extract_facts")
51graph_builder.add_edge("extract_facts", "synthesize")
52graph_builder.add_edge("synthesize", END)
53
54with sqlite3.connect("eval_pipeline.db", check_same_thread=False) as conn:
55    checkpointer = SqliteSaver(conn)
56    compiled_graph = graph_builder.compile(checkpointer=checkpointer)
57    config = {"configurable": {"thread_id": "eval-job-1"}}
58
59    result = compiled_graph.invoke({"completed_steps": [], "crash_now": False}, config=config)
60
61print("Traced pipeline result: " + str(result["completed_steps"]) + "\n")
62
63correct_result = result["completed_steps"] == ["gather_sources", "extract_facts", "synthesize"]
64
65print(f"""
66=== THE RESULT, CONFIRMED DIRECTLY ===
67
68Traced pipeline reached the correct, complete real result: {correct_result}
69
70Adding real tracing changed nothing about the pipeline's actual
71behavior -- confirmed directly, exactly the same real finding already
72proven for the support router in Project 1. This same tracing would
73now let a real team directly inspect, on smith.langchain.com with a
74real, valid API key, exactly how long each real step took and
75whether any step was genuinely re-run after a real crash.
76""")

Gotchas

  • โš This project deliberately reuses the exact, unmodified node functions and real SqliteSaver checkpointer setup from the original LangGraph project โ€” confirming tracing integrates cleanly with real, existing persistence infrastructure, not just simple, in-memory examples.
  • โš A real trace of this pipeline, viewed with valid credentials, would directly show extract_facts appearing twice in a genuinely resumed run โ€” a real, visual confirmation of the exact honest nuance the original project discovered through manual log inspection.

A Real Evaluator That Checks the Correct, Complete Result

A real, custom evaluator checks whether the pipeline reached its complete, correct set of results, deliberately comparing the final set of completed steps rather than their exact count or order. This distinction matters directly: a resumed-after-crash pipeline's raw execution log genuinely differs from an uninterrupted run's (the interrupted step appears twice), but the real, correct final result โ€” which steps are genuinely complete โ€” should be identical either way. Testing this evaluator directly against three real scenarios โ€” a normal run, a genuinely incomplete run missing a step, and a resumed-after-crash run โ€” confirms it correctly, honestly scores all three.

02_evaluating_completeness.py
python
1import warnings
2warnings.filterwarnings("ignore")
3
4print("=== A REAL EVALUATOR CHECKING THE CORRECT, COMPLETE RESULT ===\n")
5
6def pipeline_completeness_evaluator(run_outputs: dict, example_outputs: dict) -> dict:
7    """Checks whether the real pipeline reached the correct, complete
8    SET of steps -- deliberately not checking exact count or order,
9    since a resumed run's raw log genuinely differs from an
10    uninterrupted one, but the correct final result should match."""
11    actual_steps = set(run_outputs.get("completed_steps", []))
12    expected_steps = set(example_outputs.get("expected_completed_steps", []))
13    correct = actual_steps == expected_steps
14    return {
15        "key": "reached_all_expected_steps",
16        "score": 1 if correct else 0,
17        "comment": f"Expected {expected_steps}, got {actual_steps}",
18    }
19
20print("=== TESTING THE EVALUATOR AGAINST THREE REAL SCENARIOS ===\n")
21
22test_normal = pipeline_completeness_evaluator(
23    {"completed_steps": ["gather_sources", "extract_facts", "synthesize"]},
24    {"expected_completed_steps": ["gather_sources", "extract_facts", "synthesize"]},
25)
26print("Normal, uninterrupted run: " + str(test_normal))
27
28test_incomplete = pipeline_completeness_evaluator(
29    {"completed_steps": ["gather_sources", "extract_facts"]},
30    {"expected_completed_steps": ["gather_sources", "extract_facts", "synthesize"]},
31)
32print("Genuinely incomplete run (missing synthesize): " + str(test_incomplete))
33
34test_resumed = pipeline_completeness_evaluator(
35    {"completed_steps": ["gather_sources", "extract_facts", "synthesize"]},
36    {"expected_completed_steps": ["gather_sources", "extract_facts", "synthesize"]},
37)
38print("Resumed-after-crash run, correct final result: " + str(test_resumed))
39
40all_correct = (
41    test_normal["score"] == 1
42    and test_incomplete["score"] == 0
43    and test_resumed["score"] == 1
44)
45
46print(f"""
47
48=== THE RESULT, CONFIRMED DIRECTLY ===
49
50The real evaluator correctly scored all three real scenarios: {all_correct}
51
52This evaluator is now genuinely ready for a real, live evaluate() run:
53
54results = evaluate(
55    run_pipeline_on_input,
56    data="pipeline-completeness-real-outcomes",
57    evaluators=[pipeline_completeness_evaluator],
58)
59
60Run this real, repeatable evaluation after every future real change
61to the pipeline's code -- confirming directly, every single time,
62that a resumed run still reaches the same, correct final result an
63uninterrupted run would, rather than trusting the one, original,
64manual test forever.
65""")

Gotchas

  • โš This evaluator deliberately compares SETS of completed steps, not lists โ€” a real, deliberate design choice reflecting that a resumed pipeline's exact execution history genuinely differs from an uninterrupted one, while the correct final result should not.
  • โš This real evaluator directly operationalizes the original LangGraph project's own honest finding โ€” turning a one-time, manual confirmation into a real, repeatable, automated check that can run after every future code change.