Add Prompt Chaining RI; align Guardrail Sandwich via shared.py + root nbtools#2
Open
webup wants to merge 3 commits into
Open
Add Prompt Chaining RI; align Guardrail Sandwich via shared.py + root nbtools#2webup wants to merge 3 commits into
webup wants to merge 3 commits into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.pyfor cross-cutting notebook helpers.Three logical commits:
nbtools.py(root shared helper) — hoists the duplicatedshow_graph()(PNG viadraw_mermaid_png(), ASCII fallback viadraw_ascii()when the renderer is offline) into one module, the counterpart tomodel_config.py. Addsgrandalf>=0.8for the fallback.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 onehooks=list and decodes theToolMessageto a dict before post-hooks, sooutput_schema_hookvalidates the receipt (matching the langgraph path).rewrite → factcheckchain + ashared.pygate library. Teaching crux: fact-check reads both the original draft and the rewrite (information-starvation guard) with an anchoredstarts_with_gateso"unverified"can't pass a substring check. Plus the RI recipe + conventions inREFERENCE_IMPL.md.Patterns covered
StateGraph, explicit pre/tool/post nodesAgentMiddleware.wrap_tool_callStateGraph, one node/step + early-exit edgesprompt | 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.pynamed for role; root helpers innbtools.py/model_config.pysys.pathvia upward file search (no../..counting)show_graphwith offline ASCII fallback; avoid Mermaid reserved-word node ids (style→ HTTP 400)FakeListChatModelfor deterministic no-API-key mocks (cycles by call order; survives retries)(str, Enum)enums;from __future__ import annotations; no dead code orprint("X ready")noiseVerification
test_pattern.pyfor both patterns)ruff checkclean onnbtools.pyand bothshared.pyfiles.mdimage references resolve to tracked filesNotes
ernie:ernie-5.1via AI Studio), so outputs are embedded; mock sections run with no API key.create_agentdemos (unchanged from the original RI); a no-key mock path for the agent loop is deferred.