Skip to content

Add Prompt Chaining RI; align Guardrail Sandwich via shared.py + root nbtools#2

Open
webup wants to merge 3 commits into
mainfrom
ri/prompt-chaining-and-shared-refactor
Open

Add Prompt Chaining RI; align Guardrail Sandwich via shared.py + root nbtools#2
webup wants to merge 3 commits into
mainfrom
ri/prompt-chaining-and-shared-refactor

Conversation

@webup

@webup webup commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the Prompt Chaining reference implementation and brings the existing Guardrail Sandwich RI up to a shared set of conventions, so a reader can flip between the LangGraph and LangChain notebooks of either pattern and see the same vocabulary, runner, and trace shape. Also introduces a root-level nbtools.py for cross-cutting notebook helpers.

Three logical commits:

  1. nbtools.py (root shared helper) — hoists the duplicated show_graph() (PNG via draw_mermaid_png(), ASCII fallback via draw_ascii() when the renderer is offline) into one module, the counterpart to model_config.py. Adds grandalf>=0.8 for the fallback.
  2. Guardrail Sandwich refactor — renames hooks.py → shared.py (named for its role, not contents), moves the hook runner + selector into it (run_single_hook, applicable_hooks), and reconciles both notebooks to honor the full hook config dict (phase + applies_to). The langchain middleware now takes one hooks= list and decodes the ToolMessage to a dict before post-hooks, so output_schema_hook validates the receipt (matching the langgraph path).
  3. Prompt Chaining RI — two notebooks teaching a 2-step rewrite → factcheck chain + a shared.py gate library. Teaching crux: fact-check reads both the original draft and the rewrite (information-starvation guard) with an anchored starts_with_gate so "unverified" can't pass a substring check. Plus the RI recipe + conventions in REFERENCE_IMPL.md.

Patterns covered

Pattern LangGraph LangChain
Guardrail Sandwich StateGraph, explicit pre/tool/post nodes AgentMiddleware.wrap_tool_call
Prompt Chaining StateGraph, one node/step + early-exit edges LCEL prompt | model | parser | gate + .with_retry()

Both patterns share a per-pattern shared.py (factories + runner) imported by both notebooks; the framework mechanism is the only thing that differs.

Conventions established (documented in REFERENCE_IMPL.md)

  • shared.py named for role; root helpers in nbtools.py / model_config.py
  • sys.path via upward file search (no ../.. counting)
  • show_graph with offline ASCII fallback; avoid Mermaid reserved-word node ids (style → HTTP 400)
  • FakeListChatModel for deterministic no-API-key mocks (cycles by call order; survives retries)
  • fail-closed gates/hooks; (str, Enum) enums; from __future__ import annotations; no dead code or print("X ready") noise

Verification

  • All 4 notebooks execute clean (0 errors); graphs render PNG with ASCII fallback
  • Pattern test suites pass: 38/38 (test_pattern.py for both patterns)
  • ruff check clean on nbtools.py and both shared.py files
  • HTML + Markdown exports regenerated; all .md image references resolve to tracked files
  • Two rounds of senior code review; all findings fixed and re-verified

Notes

  • Notebooks were executed against the configured model (ernie:ernie-5.1 via AI Studio), so outputs are embedded; mock sections run with no API key.
  • The langchain Guardrail notebook still requires a live model for its create_agent demos (unchanged from the original RI); a no-key mock path for the agent loop is deferred.

webup added 3 commits June 13, 2026 17:34
Hoist the PNG-with-ASCII-fallback graph renderer that was duplicated
across the pattern notebooks into a root-level module, the counterpart
to model_config.py. show_graph(graph, *, alt=...) works on any object
exposing .get_graph() (compiled StateGraph or LCEL Runnable), tries
draw_mermaid_png(), and falls back to draw_ascii() when the remote
Mermaid renderer is unreachable.

Add grandalf>=0.8 (langgraph extra) for the offline ASCII fallback.
Rename hooks.py -> shared.py (role, not contents) and move the hook
runner and selector into it as run_single_hook + applicable_hooks, so
both notebooks share the exact selection and execution logic instead of
each carrying a private copy.

Reconcile the two impls so each honors the full hook config dict:
- applicable_hooks filters by phase AND applies_to, sorts by priority
- the langchain middleware takes one hooks= list (was pre_hooks=/post_hooks=)
- langchain decodes the ToolMessage to a dict before post-hooks, so
  output_schema_hook validates the receipt (not its wrapper) — matching
  the langgraph decode_tool_message path
- unify the outcome-dict shape (phase, no elapsed_ms) and the tx_id literal

Polish to convention: upward-search sys.path (no ../.. counting),
shared show_graph with ASCII fallback, drop dead imports and
print('X ready') noise, surface the middleware audit via last_trace.
Two notebooks teaching the same 2-step rewrite -> factcheck chain, plus
a shared.py of gate factories imported by both. The teaching crux: the
factcheck step reads BOTH the original draft and the rewrite (the
information-starvation guard), and uses an anchored starts_with_gate so
a failure verdict like 'unverified' can't pass a substring check.

- langgraph: explicit StateGraph, one node per step, gate retry loop,
  early-exit conditional edges
- langchain: LCEL prompt | model | parser | gate, bounded gated retry
  via .with_retry(), RunnablePassthrough.assign to thread prior outputs
- shared.py: length_gate, keys_gate, starts_with_gate, json_gate,
  regex_gate, any_gate, all_gate + StepStatus (a gate library; the
  tutorials demo the first two)
- FakeListChatModel for deterministic no-API-key mock runs

Document the RI recipe and conventions in REFERENCE_IMPL.md (shared.py
naming, upward-search sys.path, nbtools.show_graph, FakeListChatModel
choice, Mermaid reserved-word trap, cross-impl alignment), and mark
Prompt Chaining done in the roadmap.
@webup webup requested a review from huangjia2019 June 13, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant