feat(examples): add 05-08 (fan-out, parallel-branches, multimodal-prompt, checkpointing)#51
Merged
Merged
Conversation
Realistic fan-out shape: summarize and classify a batch of news headlines in parallel. - ``GraphBuilder.add_fan_out_node`` with ``items_field`` mode and ``extra_outputs`` collecting two parallel per-instance lists (summary + topic). - ``instance_middleware=(RetryMiddleware(3 attempts, deterministic backoff), TimingMiddleware(on_complete=...))`` wraps each instance's whole subgraph invocation — retries are per-instance, timings are captured per-instance. - ``concurrency=3`` caps how many instances run in flight at once. - Final printout shows per-instance durations in completion order alongside a wall-clock total vs sum-of-durations comparison, so the speedup from concurrency is visible. Per-instance subgraph is ``summarize → classify``; both nodes hit the LLM via the shared ``OpenAIProvider`` pattern the rest of the demos use. Smoke test list grows to six demos.
Enrich a lunar-mission news article with three independent analyses (summary, sentiment, topic tags) running concurrently. - ``GraphBuilder.add_parallel_branches_node`` registers M ``BranchSpec``s under named keys (``summary`` / ``sentiment`` / ``topics``). Each spec carries its own compiled subgraph, its own input/output projection, and optionally its own middleware. - The three branches have DIFFERENT state schemas — each is scoped to its analysis's inputs and outputs. The projection mapping translates the parent's ``article`` field into each branch's input field name. - The sentiment branch wraps its subgraph in ``RetryMiddleware`` to show per-branch middleware composition. The other two run bare. - Wall-clock total prints alongside the results so the parallelism benefit is visible. Sample article is a narrative of Artemis II's splashdown on April 10, 2026 — the first crewed flight beyond low Earth orbit since Apollo 17. Also tightens the 05 entry in examples/README.md to drop a stale mention of fan-out-index correlation (the demo doesn't claim that anymore; the timing record carries no index field). Smoke test list grows to seven demos.
Sweep across 00-05 so every example's queries, articles, baked-in corpora, and headlines are moon-related. The company is Lunar Command; consistent lunar framing makes the demo set feel like a coherent surface rather than a grab bag. - 00 hello-world: query → "why did Apollo 13 abort its lunar landing?" - 01 routing-and-subgraphs: default question → "why is the lunar south pole strategically important?" (the moon-landing-year question stays) - 02 explicit-subgraph-mapping: default topic pair → Apollo 11 vs Apollo 17; docstring and Run-with section updated - 03 observer-hooks: second default question → "explain why NASA is returning to the moon with Artemis" (the moon-landing-year question stays) - 04 nested-subgraphs: corpus → Apollo 11 (kept), Apollo 13 (new), Artemis II (new — narrative of the April 2026 splashdown); default questions updated to match - 05 fan-out-with-retry: five headlines all swapped for accurate lunar-mission news (Artemis II splashdown, Lunar Gateway pause, IM-3 prep, LRO crater find, south-pole water-ice confirmation); classify tag set tightened to lunar-relevant categories (crew / lander / orbiter / science / hardware / policy / other) Factual accuracy checked as of 2026-05-17: Artemis II splashed down 2026-04-10; the Lunar Gateway program was paused 2026-03-24 in favor of a lunar surface base; Intuitive Machines IM-2 ended on its side in March 2025 and IM-3 is scheduled for second half of 2026.
Caption a historical lunar photograph using a versioned prompt
template plus a multimodal user message.
- ``FilesystemPromptBackend`` loads
``prompts/production/caption-lunar-image.j2`` from disk. The
layout is ``<root>/<label>/<name>.j2`` so prompts can live next to
the code, be diffed in PRs, and version off their template hash.
- ``PromptManager(backend).get(name, variables={...})`` fetches and
renders in one call. The returned ``PromptResult`` carries the
rendered text in ``messages[0]`` plus identifiers
(``template_hash``, ``rendered_hash``, ``version``) for downstream
attribution.
- The node pulls the rendered text out of the ``PromptResult``,
composes a multimodal ``UserMessage(content=[TextBlock(text=...),
ImageBlock(source=ImageSourceURL(url=...))])``, and passes it to
``OpenAIProvider.complete`` — one call carries both the instructions
and the image.
- ``with_active_prompt(rendered)`` wraps the LLM call so OTel
observers (none attached in the demo, but the pattern is the same
in production) stamp ``openarmature.prompt.*`` attributes onto the
LLM-call span.
Sample: the iconic Apollo 11 photograph of Buzz Aldrin on the lunar
surface, hosted on Wikimedia Commons. ``IMAGE_URL`` env var overrides
the default for users who want to point at their own image.
Smoke test list grows to eight demos.
A lunar-mission planning pipeline that checkpoints after every step,
then resumes the saved record under an upgraded state schema with a
v1→v2 migration.
Phase 1 — v1 invoke:
- ``MissionPlanStateV1`` declares ``schema_version: ClassVar[str] =
"v1"`` and has four user-facing fields (destination, objective,
crew_size, timeline) plus trace.
- ``SQLiteCheckpointer(path, serialization="json")`` writes records
to a temp DB. JSON mode is the migration-eligible serialization —
pickle mode can't bridge schemas.
- v1 graph: define_objective → size_crew → draft_timeline → END.
Each completed node fires a save stamped with schema_version="v1".
- Invoked with a deterministic correlation_id so phase 2 can look
up the invocation by ``CheckpointFilter(correlation_id=...)``.
Phase 2 — v2 resume:
- ``MissionPlanStateV2`` adds a ``risk_assessment`` field and
``schema_version = "v2"``.
- ``GraphBuilder.with_state_migration("v1", "v2",
migrate_v1_to_v2)`` registers the migration. ``migrate_v1_to_v2``
is a pure function that backfills ``risk_assessment=""`` for v1
records.
- v2 graph adds an ``assess_risks`` node at the end of the v1
topology.
- ``invoke(state, resume_invocation=<v1 id>)`` loads the v1 record,
applies the migration, re-deserializes as ``MissionPlanStateV2``,
and continues at ``assess_risks`` (the first node not in
completed_positions).
Final state has the v1 work (objective, crew_size, timeline) AND
the v2 risk_assessment, all under the upgraded schema. Smoke test
list grows to nine demos.
There was a problem hiding this comment.
Pull request overview
Adds four new runnable demos under examples/ to cover additional v0.6.0 surfaces (fan-out, parallel branches, prompt management + multimodal messages, and checkpointing + state migration), and updates existing demos’ sample subject matter to lunar themes.
Changes:
- Add new examples
05–08and document them inexamples/README.md. - Extend
tests/test_examples_smoke.pyto compile-load the new demos. - Update existing examples (00–04) with moon-themed default prompts/content.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_examples_smoke.py | Adds new demo folders to the compile-load smoke test list. |
| examples/README.md | Documents demos 05–08 and their key concepts. |
| examples/00-hello-world/main.py | Updates default query to lunar-themed prompt. |
| examples/01-routing-and-subgraphs/main.py | Updates default question + usage examples to lunar-themed prompts. |
| examples/02-explicit-subgraph-mapping/main.py | Updates use case text + default topics + CLI examples to lunar-themed prompts. |
| examples/03-observer-hooks/main.py | Updates CLI example prompt to lunar-themed content. |
| examples/04-nested-subgraphs/main.py | Updates CLI examples + embedded corpus docs to lunar-themed content. |
| examples/05-fan-out-with-retry/main.py | New fan-out demo showing per-instance retry + timing middleware and concurrency cap. |
| examples/06-parallel-branches/main.py | New parallel-branches demo showing heterogeneous branch schemas + per-branch middleware. |
| examples/07-multimodal-prompt/main.py | New prompt-management + multimodal message demo using filesystem prompts and with_active_prompt. |
| examples/07-multimodal-prompt/prompts/production/caption-lunar-image.j2 | New Jinja2 prompt template used by the multimodal prompt demo. |
| examples/08-checkpointing-and-migration/main.py | New checkpoint + resume + schema migration demo using SQLiteCheckpointer(serialization="json"). |
Comments suppressed due to low confidence (2)
examples/08-checkpointing-and-migration/main.py:290
- Using
assert summariesfor required runtime control flow is unsafe because asserts are stripped underpython -O, and it turns a user-facing error into a laterIndexError. Raise a clear exception (e.g.,RuntimeError) when no checkpoints are found, and include the correlation_id/run_id in the message.
summaries = list(await checkpointer.list(CheckpointFilter(correlation_id=run_id)))
assert summaries, "expected at least one saved checkpoint"
invocation_id = summaries[-1].invocation_id
examples/08-checkpointing-and-migration/main.py:283
graph_v1.invoke()/graph_v1.drain()(and the v2 equivalents) aren’t protected bytry/finally. If an exception occurs during invoke/printing, the graph may not be drained and the provider may not be closed, which can leave background tasks or sockets open. Wrap each phase (or the wholemain) intry/finallyto guaranteedrain()andaclose()run.
graph_v1 = build_graph_v1(checkpointer)
initial_v1 = MissionPlanStateV1(destination=destination)
final_v1 = await graph_v1.invoke(initial_v1, correlation_id=run_id)
await graph_v1.drain()
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- 05 fan-out-with-retry: _timings.clear() at the top of main() so a REPL or repeated-main() driver doesn't accumulate timings across invocations. Module-level retention was an oversight. - 07 multimodal-prompt: replace the two type/shape asserts in caption() with an explicit isinstance check that raises RuntimeError. Asserts strip under python -O; the new shape narrows for pyright AND fails loudly if PromptManager's return contract drifts. - 08 checkpointing-and-migration: switch main() from tempfile.mkdtemp to tempfile.TemporaryDirectory wrapping the phase 1/2 logic. The SQLite DB + temp folder are now cleaned up on both the happy path and any raised exception, instead of leaving /tmp/oa-checkpoint-demo-* behind across runs.
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
Second wave of the Examples Review phase. After #50 scrubbed and re-themed the existing examples, this PR fills in the remaining gaps so every major v0.6.0 surface has a home in
examples/. Four new demos plus a moon-themed sweep across the existing ones.New examples
fan-out-with-retryitems_fieldmode),extra_outputscollecting a parallel per-instance list,instance_middleware=(RetryMiddleware, TimingMiddleware), concurrency capparallel-branchesBranchSpecper branch with input/output projection, per-branch middleware (sentiment branch has retry, others run bare)multimodal-promptPromptManager+FilesystemPromptBackendloading a Jinja2 template from disk;with_active_promptcontext-var propagation; multimodalUserMessage(content=[TextBlock, ImageBlock])checkpointing-and-migrationSQLiteCheckpointer(serialization="json"), save-on-completed-event,State.schema_version,with_state_migration,invoke(resume_invocation=...)All four are LLM-using against the same
OpenAIProviderpattern the other demos use. Each carries abuild_graph()factory; smoke test list grows to nine demos.Moon-themed subject matter (across 00-06)
After the scrub PR, Chris flagged that all subject matter should align with the company name (Lunar Command). One commit in this PR sweeps 00-05 to moon topics and 06's article was authored as a moon topic from the start. Specifically:
Factual accuracy as of 2026-05-17: Artemis II splashed down 2026-04-10; the Lunar Gateway program was paused 2026-03-24 in favor of a lunar surface base; Intuitive Machines IM-2 ended on its side in March 2025 and IM-3 is scheduled for second half of 2026. Web-searched and double-checked before commit so the moon-themed content doesn't ship stale.
Commits
feat(examples): add 05-fan-out-with-retryfeat(examples): add 06-parallel-brancheschore(examples): moon-themed subject matter(sweep across 00-05)feat(examples): add 07-multimodal-promptfeat(examples): add 08-checkpointing-and-migrationConcept coverage after this PR
ExplicitMappingProjectionStrategyschema_version,with_state_migration)Every major v0.6.0 surface has a home.
Test plan
uv run pytest tests/ -q— 9 example smoke tests included; full suite green.uv run pyright examples/— clean.uv run ruff check examples/ tests/— clean.Next phase
After this merges, the Examples Review is wrapped. Next phase per the standing plan is the docs-site review (
mkdocs.ymlnav, concepts pages, reference pages re-read against the v0.6.0 surface). Sessions-capability scoping is captured in the local coord directory for a future spec proposal.