diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e836e3..e317d01d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,17 +10,18 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The - **README and docs homepage refreshed around reasons-to-choose.** Replaced the 10-bullet "Why OpenArmature" feature inventory in `README.md` with 5 differentiating reasons (LLM-infused workflows to agents on one engine; crash-safe resume by contract; destination-pluggable observability with OTel + Langfuse, no SaaS lock-in; compile-time topology checks; spec + conformance). The docs homepage (`docs/index.md`) card grid carries the same five plus a sixth card retained from the previous grid for async-first / LLM-agnostic: workflows-to-agents, crash-safe, pluggable observability, bad-graphs-don't-compile, parallelism (fan-out + parallel-branches + nested correctness), async-first. - **Docs sweep: stale references and em-dash normalization.** Fixed three definite stale references (`spec_version='0.26.0'` in the Langfuse example output now reads `'0.38.0'`; the dangling `v0.16.1` qualifier dropped from the parallel-branches concept page; `compiled.attach_observer` corrected to `graph.attach_observer` in `non-obvious-shapes.md` for variable-name consistency with the rest of the docs). Swept em dashes out of the user-facing docs (130 instances across 17 files) per the convention set during the patterns expansion. mkdocs strict build clean; no broken intra-docs links. -- **Example 08 (checkpointing-and-migration) grows a crash-and-resume drama.** The first invoke of the v1 graph now hits a simulated transient failure inside `size_crew` (raises a `RuntimeError` on its first attempt only). The example catches `NodeException` at the `invoke()` boundary, prints what's saved on disk (`define_objective`'s position is already in `completed_positions`), then re-invokes with `resume_invocation=`. The retried `size_crew` succeeds, `draft_timeline` runs, and the pipeline finishes - dramatizing the synchronous-checkpoint-by-contract reliability claim from the README pitch. The existing v1->v2 migration phase rides on top of the crash-survived checkpoint, so both reliability stories compose in one demo. Walk-through doc rewritten to cover both phases. +- **The checkpointing-and-migration example grows a crash-and-resume drama.** The first invoke of the v1 graph now hits a simulated transient failure inside `size_crew` (raises a `RuntimeError` on its first attempt only). The example catches `NodeException` at the `invoke()` boundary, prints what's saved on disk (`define_objective`'s position is already in `completed_positions`), then re-invokes with `resume_invocation=`. The retried `size_crew` succeeds, `draft_timeline` runs, and the pipeline finishes - dramatizing the synchronous-checkpoint-by-contract reliability claim from the README pitch. The existing v1->v2 migration phase rides on top of the crash-survived checkpoint, so both reliability stories compose in one demo. Walk-through doc rewritten to cover both phases. +- **Examples renamed and catalog reorganized by topic.** All 13 example directories drop their numeric prefixes (`examples/00-hello-world/` -> `examples/hello-world/`, etc.); the corresponding `docs/examples/NN-name.md` files do the same. The catalog at `docs/examples/index.md` and the mkdocs nav are regrouped into seven topical sections: Foundations, Composition, Concurrency, Prompts, Tool use, Reliability, Observability. Catalog entries and walk-through H1s drop the number prefix; cross-references between examples are rewritten by-name (no more "Example 03"). The `examples/README.md` is brought up to date with the post-v0.11.0 catalog (entries for chat-with-multimodal, langfuse-observability, production-observability now present) under the same grouping. `tests/test_examples_smoke.py`'s DEMOS list updated; the pytest parametrize IDs follow the new names. Em dashes scrubbed from the example sources and `examples/README.md` to match the convention established in the v0.11.0-cycle docs sweep. ### Added -- **Production observability example.** `examples/12-production-observability/` demonstrates the production-grade observability stack end-to-end: `OTelObserver` + `LangfuseObserver` attached to the same graph (proposal 0031), `trace_input_from_state` / `trace_output_from_state` caller hooks on the Langfuse observer (proposal 0043 §8.4.1) deriving domain dicts from State, the built-in `TimingMiddleware` recording per-node duration via an `on_complete` callback, and `invoke(metadata={...})` carrying multi-tenant identifiers (tenantId / requestId / featureFlag) that both observers pick up at once. `InMemoryLangfuseClient` + `InMemorySpanExporter` capture in-process so the demo prints what each backend would have ingested without needing real production credentials. -- **Chat-with-multimodal example.** `examples/11-chat-with-multimodal/` demonstrates `ChatPrompt` + `PlaceholderSegment` (proposal 0046) end-to-end: a four-turn lunar-mission Q&A conversation with conversation memory threaded through state, one mid-conversation turn attaching a photograph via `ImageURLBlockTemplate`, the agent processing the multimodal turn naturally without changing the chat-history shape. Complementary to example 09 (tool use); chat history threading and tool calling are separate primitives. -- **`docs/examples/index.md` catalog now lists example 10.** A pre-existing gap (the Langfuse-observability example was missing from the catalog) caught and fixed alongside the example 11 entry. +- **Production observability example.** `examples/production-observability/` demonstrates the production-grade observability stack end-to-end: `OTelObserver` + `LangfuseObserver` attached to the same graph (proposal 0031), `trace_input_from_state` / `trace_output_from_state` caller hooks on the Langfuse observer (proposal 0043 §8.4.1) deriving domain dicts from State, the built-in `TimingMiddleware` recording per-node duration via an `on_complete` callback, and `invoke(metadata={...})` carrying multi-tenant identifiers (tenantId / requestId / featureFlag) that both observers pick up at once. `InMemoryLangfuseClient` + `InMemorySpanExporter` capture in-process so the demo prints what each backend would have ingested without needing real production credentials. +- **Chat-with-multimodal example.** `examples/chat-with-multimodal/` demonstrates `ChatPrompt` + `PlaceholderSegment` (proposal 0046) end-to-end: a four-turn lunar-mission Q&A conversation with conversation memory threaded through state, one mid-conversation turn attaching a photograph via `ImageURLBlockTemplate`, the agent processing the multimodal turn naturally without changing the chat-history shape. Complementary to the tool-use example; chat history threading and tool calling are separate primitives. +- **`docs/examples/index.md` catalog now lists the langfuse-observability example.** A pre-existing gap (it was missing from the catalog) caught and fixed alongside the chat-with-multimodal entry. - **PyPI + spec-version shields on the docs homepage.** `docs/index.md` now carries dynamic shields for the published PyPI version and the pinned spec version, sourced from `img.shields.io`. Both auto-update on every publish or spec bump; no maintenance burden. Mirrors the same shield URLs the README already uses. - **vLLM production deployment notes.** `docs/model-providers/vllm.md` grows a "Production deployment" section covering the `VLLM_HTTP_TIMEOUT_KEEP_ALIVE` gotcha (vLLM's stock 5s uvicorn keep-alive lapses pooled OA-side httpx connections and surfaces as `ProviderUnavailable`; widen to roughly 300s), a systemd unit skeleton, and the three throughput knobs that interact with OA's shared connection pool (`--max-model-len`, `--max-num-seqs`, `--gpu-memory-utilization`). The existing "Tool calling" section grows a `--tool-call-parser` family table verified against vLLM's docs (Llama 3.x / Llama 4 / Mistral / Hermes / Qwen3 / DeepSeek V3 / GPT-OSS), plus explicit "not supported here" callouts for Anthropic / Gemini (proprietary cloud) and mainstream Gemma (no vLLM parser). - **Three new patterns docs.** `docs/patterns/state-migration-on-resume.md`, `docs/patterns/caller-supplied-trace-identifiers.md`, and `docs/patterns/observer-state-reconciliation.md` graduate the corresponding entries from `docs/agent/non-obvious-shapes.md` into full pattern recipes with code snippets and "when this is right / when it isn't" guidance. The programmatic patterns API (`openarmature.patterns.list()` / `get(name)`) grows from 4 to 7 entries. -- **HyperDX OTel integration test path and "Production swap" docs in example 03.** `examples/03-observer-hooks/main.py`'s module docstring grows a "Production swap" section showing how to substitute the demo's `SimpleSpanProcessor` + `ConsoleSpanExporter` for `BatchSpanProcessor` + `OTLPSpanExporter` pointed at HyperDX (or any other OTLP-HTTP collector). A new opt-in integration test (`tests/integration/test_otel_hyperdx_export.py`, gated by `HYPERDX_API_KEY` + `HYPERDX_OTLP_ENDPOINT` env vars and `@pytest.mark.integration`) drives the same production export path end-to-end against a live endpoint. `opentelemetry-exporter-otlp-proto-http` lands as a dev-only dep; not promoted to a public extras group yet. +- **HyperDX OTel integration test path and "Production swap" docs in the observer-hooks example.** `examples/observer-hooks/main.py`'s module docstring grows a "Production swap" section showing how to substitute the demo's `SimpleSpanProcessor` + `ConsoleSpanExporter` for `BatchSpanProcessor` + `OTLPSpanExporter` pointed at HyperDX (or any other OTLP-HTTP collector). A new opt-in integration test (`tests/integration/test_otel_hyperdx_export.py`, gated by `HYPERDX_API_KEY` + `HYPERDX_OTLP_ENDPOINT` env vars and `@pytest.mark.integration`) drives the same production export path end-to-end against a live endpoint. `opentelemetry-exporter-otlp-proto-http` lands as a dev-only dep; not promoted to a public extras group yet. ### Changed (breaking) diff --git a/README.md b/README.md index c4fcf3d5..9f43e211 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ async def main() -> None: asyncio.run(main()) ``` -Set `LLM_API_KEY=sk-...` and run. To swap providers, point `LLM_BASE_URL` and `LLM_MODEL` at OpenRouter, vLLM, LM Studio, llama.cpp, or anything else that speaks the OpenAI Chat Completions wire format. The example also lives at [`examples/00-hello-world/main.py`](./examples/00-hello-world/main.py); see [`examples/`](./examples/) for more runnable demos. +Set `LLM_API_KEY=sk-...` and run. To swap providers, point `LLM_BASE_URL` and `LLM_MODEL` at OpenRouter, vLLM, LM Studio, llama.cpp, or anything else that speaks the OpenAI Chat Completions wire format. The example also lives at [`examples/hello-world/main.py`](./examples/hello-world/main.py); see [`examples/`](./examples/) for more runnable demos. A few things to notice: diff --git a/RELEASING.md b/RELEASING.md index 4a336c7b..faaa1e44 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -112,7 +112,7 @@ Minimum smoke set: - [ ] Version string matches the rc tag. - [ ] At least one example runs to completion against a real LLM - endpoint (`examples/00-hello-world/main.py` is the quickest). + endpoint (`examples/hello-world/main.py` is the quickest). - [ ] The optional `[otel]` extra installs cleanly and `import openarmature.observability.otel` succeeds. - [ ] If any docs changed in this release, the live docs site diff --git a/docs/concepts/llms.md b/docs/concepts/llms.md index 4118df9f..af8c67cf 100644 --- a/docs/concepts/llms.md +++ b/docs/concepts/llms.md @@ -334,8 +334,8 @@ Wiring the loop as a graph cycle: a `call_llm` node, a to `call_llm` when `tool_calls` are present and forward to a termination node when they aren't. A turn cap on the routing function prevents runaway loops on a model that stays in tool-calling forever. -See [`09 - Tool use`](../examples/09-tool-use.md) for the runnable -shape. +See [the tool-use example](../examples/tool-use.md) for the +runnable shape. ### Controlling tool-call behavior with `tool_choice` @@ -591,10 +591,10 @@ classifier won't do this for them. - [API reference: `openarmature.llm`](../reference/llm.md) for the full surface: message types, `Response`, `RuntimeConfig`, every error class, validation helpers. -- [Examples: 00 - Hello, world](../examples/00-hello-world.md) for a +- [Examples: Hello, world](../examples/hello-world.md) for a runnable graph exercising both `response_schema` forms in one pipeline. -- [Examples: 09 - Tool use](../examples/09-tool-use.md) for the +- [Examples: Tool use](../examples/tool-use.md) for the agent-loop pattern with two local tools. -- [Examples: 07 - Multimodal prompt](../examples/07-multimodal-prompt.md) +- [Examples: Multimodal prompt](../examples/multimodal-prompt.md) for content blocks alongside versioned prompts. diff --git a/docs/concepts/observability.md b/docs/concepts/observability.md index bedd6a67..1314027e 100644 --- a/docs/concepts/observability.md +++ b/docs/concepts/observability.md @@ -732,7 +732,7 @@ observer = LangfuseObserver( The adapter bridges `langfuse>=4.6`'s unified `start_observation` API onto our `LangfuseClient` Protocol; the observer code is the same in tests and production. See -[`examples/10-langfuse-observability`](../examples/10-langfuse-observability.md) +[`examples/langfuse-observability`](../examples/langfuse-observability.md) for a runnable demo. !!! note "Langfuse SDK version compatibility" diff --git a/docs/examples/11-chat-with-multimodal.md b/docs/examples/chat-with-multimodal.md similarity index 89% rename from docs/examples/11-chat-with-multimodal.md rename to docs/examples/chat-with-multimodal.md index ae8eb133..489dbf40 100644 --- a/docs/examples/11-chat-with-multimodal.md +++ b/docs/examples/chat-with-multimodal.md @@ -1,4 +1,4 @@ -# 11 - Chat with multi-turn memory and a multimodal turn +# Chat with multi-turn memory and a multimodal turn A lunar-mission Q&A assistant that maintains conversation context across four turns. One mid-conversation turn includes an attached @@ -56,12 +56,12 @@ turn's `render()` injects the grown history into the placeholder. loops back to itself until the script-supplied user turns are exhausted, then routes to `END`. The cycle is [`respond → respond → respond → … → END`](../concepts/graphs.md). -- Complementary to [example 09 (tool use)](09-tool-use.md): chat +- Complementary to [the tool-use example](tool-use.md): chat history threading and tool calling are separate primitives. - Example 09 shows the LLM emitting tool calls and the framework - dispatching them; this example shows how the prompt-management - layer composes a multi-turn conversation. A production chat agent - often combines both. + The tool-use example shows the LLM emitting tool calls and the + framework dispatching them; this example shows how the + prompt-management layer composes a multi-turn conversation. A + production chat agent often combines both. ## How to run @@ -69,10 +69,10 @@ turn's `render()` injects the grown history into the placeholder. uv sync --group examples --all-extras # Clean conversation output only (default). -LLM_API_KEY=sk-... uv run python examples/11-chat-with-multimodal/main.py +LLM_API_KEY=sk-... uv run python examples/chat-with-multimodal/main.py # With OTel JSON spans streaming to stderr alongside the chat. -LLM_API_KEY=sk-... uv run python examples/11-chat-with-multimodal/main.py --traces +LLM_API_KEY=sk-... uv run python examples/chat-with-multimodal/main.py --traces ``` `LLM_MODEL` must point at a vision-capable model. The default @@ -83,14 +83,15 @@ The conversation streams to stdout as each turn completes (a small visual delay between turns lets the human reader follow along). The `--traces` flag opts in to the OTel observer with a console exporter; without it the chat runs without any observer attached. -Example 03 owns the observer-hooks story end-to-end; this example's -headline is the chat shape, not the observability wiring. +The observer-hooks example owns that story end-to-end; this +example's headline is the chat shape, not the observability +wiring. The demo is illustrative only: it runs four pre-scripted user turns sequentially in one process. A real chat-server runtime would manage one invocation per turn with the chat history persisted across sessions (e.g., via a checkpointer keyed on session_id); -that's [example 08 (checkpointing)](08-checkpointing-and-migration.md)'s +that's [the checkpointing-and-migration example](checkpointing-and-migration.md)'s territory, combined with this one's chat shape. ## The graph diff --git a/docs/examples/08-checkpointing-and-migration.md b/docs/examples/checkpointing-and-migration.md similarity index 98% rename from docs/examples/08-checkpointing-and-migration.md rename to docs/examples/checkpointing-and-migration.md index b449d40b..f477f953 100644 --- a/docs/examples/08-checkpointing-and-migration.md +++ b/docs/examples/checkpointing-and-migration.md @@ -1,4 +1,4 @@ -# 08 - Checkpointing and migration +# Checkpointing and migration A lunar-mission planning pipeline that writes a SQLite checkpoint after every step, survives a simulated mid-pipeline crash, and then @@ -90,7 +90,7 @@ migration runs once on load, and execution continues at ```bash uv sync --group examples -LLM_API_KEY=sk-... uv run python examples/08-checkpointing-and-migration/main.py +LLM_API_KEY=sk-... uv run python examples/checkpointing-and-migration/main.py ``` The SQLite database is created in a `TemporaryDirectory` that's diff --git a/docs/examples/02-explicit-subgraph-mapping.md b/docs/examples/explicit-subgraph-mapping.md similarity index 89% rename from docs/examples/02-explicit-subgraph-mapping.md rename to docs/examples/explicit-subgraph-mapping.md index d3addf06..aad6f717 100644 --- a/docs/examples/02-explicit-subgraph-mapping.md +++ b/docs/examples/explicit-subgraph-mapping.md @@ -1,4 +1,4 @@ -# 02 - Explicit subgraph mapping +# Explicit subgraph mapping Compare two topics by running the *same* compiled analysis subgraph on each, with each call site writing into disjoint parent fields. @@ -29,16 +29,16 @@ call would clobber the first. has no idea which side of the comparison it's running for. The mapping at each call site is what wires the subgraph's neutral names to the parent's per-side fields. -- The contrast with example 01: there a custom - `ProjectionStrategy` carried one field in. Here the two sites - need to be similar-but-different, and `ExplicitMapping` is the - zero-boilerplate way to express that. +- The contrast with the routing-and-subgraphs example: there a + custom `ProjectionStrategy` carried one field in. Here the two + sites need to be similar-but-different, and `ExplicitMapping` + is the zero-boilerplate way to express that. ## How to run ```bash uv sync --group examples -LLM_API_KEY=sk-... uv run python examples/02-explicit-subgraph-mapping/main.py "Apollo 11" "Apollo 17" +LLM_API_KEY=sk-... uv run python examples/explicit-subgraph-mapping/main.py "Apollo 11" "Apollo 17" ``` Or pass a single `"X vs Y"` arg, or no args (defaults to diff --git a/docs/examples/05-fan-out-with-retry.md b/docs/examples/fan-out-with-retry.md similarity index 97% rename from docs/examples/05-fan-out-with-retry.md rename to docs/examples/fan-out-with-retry.md index 7fffe0b7..d4b91fd9 100644 --- a/docs/examples/05-fan-out-with-retry.md +++ b/docs/examples/fan-out-with-retry.md @@ -1,4 +1,4 @@ -# 05 - Fan-out with retry +# Fan-out with retry Summarize a batch of lunar-mission headlines in parallel, with per-headline retry and timing middleware wrapping each instance's @@ -72,14 +72,14 @@ for the full contract. ```bash uv sync --group examples -LLM_API_KEY=sk-... uv run python examples/05-fan-out-with-retry/main.py +LLM_API_KEY=sk-... uv run python examples/fan-out-with-retry/main.py ``` To exercise the collect path with a synthetic failure: ```bash COLLECT_MODE=1 LLM_API_KEY=sk-... \ - uv run python examples/05-fan-out-with-retry/main.py + uv run python examples/fan-out-with-retry/main.py ``` ## The graph diff --git a/docs/examples/00-hello-world.md b/docs/examples/hello-world.md similarity index 96% rename from docs/examples/00-hello-world.md rename to docs/examples/hello-world.md index 246bf340..e17dbd60 100644 --- a/docs/examples/00-hello-world.md +++ b/docs/examples/hello-world.md @@ -1,4 +1,4 @@ -# 00 - Hello, world +# Hello, world The smallest possible LLM-routed pipeline: classify a query, then either plan research on it or summarize it in one sentence. Sets the @@ -39,7 +39,7 @@ well-established. ```bash uv sync --group examples -LLM_API_KEY=sk-... uv run python examples/00-hello-world/main.py +LLM_API_KEY=sk-... uv run python examples/hello-world/main.py ``` To point at a local OpenAI-compatible server, override `LLM_BASE_URL` @@ -48,7 +48,7 @@ and (often) `LLM_MODEL`: ```bash LLM_BASE_URL=http://localhost:8000 LLM_MODEL=Qwen2.5-7B-Instruct \ LLM_API_KEY= \ - uv run python examples/00-hello-world/main.py + uv run python examples/hello-world/main.py ``` ## The graph diff --git a/docs/examples/index.md b/docs/examples/index.md index ed0f55f9..3290eb64 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -12,51 +12,77 @@ in the repo. ## Catalog -- [**00 - Hello, world**](00-hello-world.md). Classify a query and - route to one of two follow-up nodes. Smallest possible LLM-routed - pipeline; introduces typed state, reducers, conditional edges, and - structured output. -- [**01 - Routing and subgraphs**](01-routing-and-subgraphs.md). - Question-answering assistant that branches into a short-answer node - or a research subgraph, then copy-edits the result. -- [**02 - Explicit subgraph mapping**](02-explicit-subgraph-mapping.md). - Run the same analysis subgraph against two parent fields by mapping - them explicitly at each call site. -- [**03 - Observer hooks**](03-observer-hooks.md). Attach - observability to a `draft → review → finalize` pipeline without - touching any node code, including OpenTelemetry spans. -- [**04 - Nested subgraphs**](04-nested-subgraphs.md). Question - answering against a small baked-in document corpus with two levels - of subgraph nesting. -- [**05 - Fan-out with retry**](05-fan-out-with-retry.md). Summarize a - batch of news headlines in parallel, with retry + timing middleware - wrapping each per-instance subgraph run. -- [**06 - Parallel branches**](06-parallel-branches.md). Enrich an - article with three independent analyses (summary, sentiment, tags) - running concurrently, each with its own state schema. -- [**07 - Multimodal prompt**](07-multimodal-prompt.md). Two analyses - of a lunar-mission photograph using versioned prompt templates, +Grouped by what they teach. New to OpenArmature? Read the +foundations entry first, then jump to whichever section maps to +the problem you're trying to solve. + +### Foundations + +- [**Hello, world**](hello-world.md). Classify a query and route + to one of two follow-up nodes. Smallest possible LLM-routed + pipeline; introduces typed state, reducers, conditional edges, + and structured output. + +### Composition + +- [**Routing and subgraphs**](routing-and-subgraphs.md). Question- + answering assistant that branches into a short-answer node or a + research subgraph, then copy-edits the result. +- [**Explicit subgraph mapping**](explicit-subgraph-mapping.md). + Run the same analysis subgraph against two parent fields by + mapping them explicitly at each call site. +- [**Nested subgraphs**](nested-subgraphs.md). Question answering + against a small baked-in document corpus with two levels of + subgraph nesting. + +### Concurrency + +- [**Fan-out with retry**](fan-out-with-retry.md). Summarize a + batch of news headlines in parallel, with retry + timing + middleware wrapping each per-instance subgraph run. +- [**Parallel branches**](parallel-branches.md). Enrich an + article with three independent analyses (summary, sentiment, + tags) running concurrently, each with its own state schema. + +### Prompts + +- [**Multimodal prompt**](multimodal-prompt.md). Two analyses of + a lunar-mission photograph using versioned prompt templates, multimodal user messages, and a prompt-group trace. -- [**08 - Checkpointing and migration**](08-checkpointing-and-migration.md). - Resume a saved invocation under an upgraded state schema, with a - v1→v2 migration backfilling new fields. -- [**09 - Tool use**](09-tool-use.md). Lunar-mission assistant that - calls local Python tools to answer questions mixing fact recall and - physics arithmetic. -- [**10 - Langfuse observability**](10-langfuse-observability.md). - Send LLM-call observability natively to Langfuse with a prompt- - linkage demonstration on a mission-briefing Q&A pipeline. -- [**11 - Chat with multimodal**](11-chat-with-multimodal.md). Four- - turn lunar-mission conversation with conversation memory threaded - through `ChatPrompt` + `PlaceholderSegment`. One turn attaches a - photograph; the agent processes it without changing the chat +- [**Chat with multimodal**](chat-with-multimodal.md). Four-turn + lunar-mission conversation with conversation memory threaded + through `ChatPrompt` + `PlaceholderSegment`. One turn attaches + a photograph; the agent processes it without changing the chat shape. -- [**12 - Production observability**](12-production-observability.md). + +### Tool use + +- [**Tool use**](tool-use.md). Lunar-mission assistant that calls + local Python tools to answer questions mixing fact recall and + physics arithmetic. + +### Reliability + +- [**Checkpointing and migration**](checkpointing-and-migration.md). + Survive a simulated mid-pipeline crash and resume from the saved + checkpoint, then re-resume under an upgraded state schema with + a v1->v2 migration backfilling new fields. + +### Observability + +- [**Observer hooks**](observer-hooks.md). Attach observability + to a `draft -> review -> finalize` pipeline without touching + any node code, including OpenTelemetry spans. +- [**Langfuse observability**](langfuse-observability.md). Send + LLM-call observability natively to Langfuse with a + prompt-linkage demonstration on a mission-briefing Q&A + pipeline. +- [**Production observability**](production-observability.md). Dual OTel + Langfuse observers attached to one graph, caller - hooks deriving domain-shaped `trace.input` / `trace.output` from - State, built-in `TimingMiddleware` recording per-node duration, - multi-tenant caller-supplied metadata propagating to both - observers in one `invoke()` call. + hooks deriving domain-shaped `trace.input` / `trace.output` + from State, built-in `TimingMiddleware` recording per-node + duration, multi-tenant caller-supplied metadata propagating to + both observers in one `invoke()` call. ## Configuration @@ -91,10 +117,11 @@ server expects. # Install the examples dep group. uv sync --group examples -# Demos 03 and 07 also want the OTel SDK for their OTelObserver. +# The observer-hooks and multimodal-prompt demos also want the +# OTel SDK for their OTelObserver. uv sync --group examples --all-extras # Run any demo. -LLM_API_KEY=sk-... uv run python examples/00-hello-world/main.py -LLM_API_KEY=sk-... uv run python examples/01-routing-and-subgraphs/main.py "what year did the moon landing happen" +LLM_API_KEY=sk-... uv run python examples/hello-world/main.py +LLM_API_KEY=sk-... uv run python examples/routing-and-subgraphs/main.py "what year did the moon landing happen" ``` diff --git a/docs/examples/10-langfuse-observability.md b/docs/examples/langfuse-observability.md similarity index 98% rename from docs/examples/10-langfuse-observability.md rename to docs/examples/langfuse-observability.md index 5899a791..1a5c4ccb 100644 --- a/docs/examples/10-langfuse-observability.md +++ b/docs/examples/langfuse-observability.md @@ -1,4 +1,4 @@ -# 10 - Langfuse observability +# Langfuse observability Send LLM call observability to Langfuse natively: Trace at the top, Span observations for graph nodes, Generation observations with input, @@ -47,7 +47,7 @@ manual wiring at the call site. ```bash uv sync --group examples -LLM_API_KEY=sk-... uv run python examples/10-langfuse-observability/main.py \ +LLM_API_KEY=sk-... uv run python examples/langfuse-observability/main.py \ "what year did Apollo 11 land" ``` diff --git a/docs/examples/07-multimodal-prompt.md b/docs/examples/multimodal-prompt.md similarity index 96% rename from docs/examples/07-multimodal-prompt.md rename to docs/examples/multimodal-prompt.md index 48ccdbd0..fb9cd661 100644 --- a/docs/examples/07-multimodal-prompt.md +++ b/docs/examples/multimodal-prompt.md @@ -1,4 +1,4 @@ -# 07 - Multimodal prompt +# Multimodal prompt Two independent analyses of a lunar-mission photograph using versioned prompt templates, a fallback prompt backend, and a @@ -51,7 +51,7 @@ The image source can be a URL (default) or a local file. Setting ```bash uv sync --group examples --all-extras -LLM_API_KEY=sk-... uv run python examples/07-multimodal-prompt/main.py +LLM_API_KEY=sk-... uv run python examples/multimodal-prompt/main.py ``` (`--all-extras` pulls in `opentelemetry-sdk` for the OTel observer @@ -61,7 +61,7 @@ To use a local file instead of the default URL: ```bash IMAGE_PATH=./photo.jpg LLM_API_KEY=sk-... \ - uv run python examples/07-multimodal-prompt/main.py + uv run python examples/multimodal-prompt/main.py ``` Override `MISSION` to set the prompt variable for a different @@ -69,7 +69,7 @@ mission name: ```bash MISSION="Apollo 17" LLM_API_KEY=sk-... \ - uv run python examples/07-multimodal-prompt/main.py + uv run python examples/multimodal-prompt/main.py ``` The model must be vision-capable. `gpt-4o-mini` (the default) is. diff --git a/docs/examples/04-nested-subgraphs.md b/docs/examples/nested-subgraphs.md similarity index 98% rename from docs/examples/04-nested-subgraphs.md rename to docs/examples/nested-subgraphs.md index c839ab77..8f6641cc 100644 --- a/docs/examples/04-nested-subgraphs.md +++ b/docs/examples/nested-subgraphs.md @@ -1,4 +1,4 @@ -# 04 - Nested subgraphs +# Nested subgraphs Question answering against a tiny baked-in document corpus, with two levels of subgraph nesting: outer coordinator, middle doc-QA, @@ -44,7 +44,7 @@ extracts a span. ```bash uv sync --group examples -LLM_API_KEY=sk-... uv run python examples/04-nested-subgraphs/main.py \ +LLM_API_KEY=sk-... uv run python examples/nested-subgraphs/main.py \ "what happened on Apollo 13?" ``` diff --git a/docs/examples/03-observer-hooks.md b/docs/examples/observer-hooks.md similarity index 98% rename from docs/examples/03-observer-hooks.md rename to docs/examples/observer-hooks.md index ca74567f..e9707bd5 100644 --- a/docs/examples/03-observer-hooks.md +++ b/docs/examples/observer-hooks.md @@ -1,4 +1,4 @@ -# 03 - Observer hooks +# Observer hooks Add observability to a `draft → review → finalize` pipeline without touching any node code. Three observer flavors run side-by-side: a @@ -48,7 +48,7 @@ bodies knows or cares that they're attached. ```bash uv sync --group examples --all-extras -LLM_API_KEY=sk-... uv run python examples/03-observer-hooks/main.py \ +LLM_API_KEY=sk-... uv run python examples/observer-hooks/main.py \ "what year did the moon landing happen" ``` diff --git a/docs/examples/06-parallel-branches.md b/docs/examples/parallel-branches.md similarity index 94% rename from docs/examples/06-parallel-branches.md rename to docs/examples/parallel-branches.md index 41500124..618dd5ca 100644 --- a/docs/examples/06-parallel-branches.md +++ b/docs/examples/parallel-branches.md @@ -1,4 +1,4 @@ -# 06 - Parallel branches +# Parallel branches Enrich a lunar-mission news article with three independent analyses (one-sentence summary, sentiment label, topic tags) @@ -6,9 +6,9 @@ running concurrently as separate subgraphs. ## Overview -Where fan-out (example 05) runs N copies of *one* subgraph against -different inputs, parallel-branches runs M *heterogeneous* -subgraphs against the same input. Different state schemas, +Where fan-out (the fan-out-with-retry example) runs N copies of +*one* subgraph against different inputs, parallel-branches runs +M *heterogeneous* subgraphs against the same input. Different state schemas, different middleware, different topologies per branch, one dispatch. @@ -56,7 +56,7 @@ and the parent fans in once all three complete. ```bash uv sync --group examples -LLM_API_KEY=sk-... uv run python examples/06-parallel-branches/main.py +LLM_API_KEY=sk-... uv run python examples/parallel-branches/main.py ``` The article is baked into the example. diff --git a/docs/examples/12-production-observability.md b/docs/examples/production-observability.md similarity index 95% rename from docs/examples/12-production-observability.md rename to docs/examples/production-observability.md index b77dc83a..cc504f33 100644 --- a/docs/examples/12-production-observability.md +++ b/docs/examples/production-observability.md @@ -1,4 +1,4 @@ -# 12 - Production observability with dual observers and timing middleware +# Production observability with dual observers and timing middleware A single-turn lunar-mission Q&A endpoint instrumented the way you'd ship it: BOTH OTel and Langfuse observers attached to the same @@ -79,7 +79,7 @@ sees the same logical events represented two ways. ```bash uv sync --group examples --all-extras -LLM_API_KEY=sk-... uv run python examples/12-production-observability/main.py +LLM_API_KEY=sk-... uv run python examples/production-observability/main.py ``` `LLM_MODEL` defaults to `gpt-4o-mini`. The pipeline is single-turn @@ -184,7 +184,7 @@ langfuse_observer = LangfuseObserver( ``` Same observer call surface, real exporters underneath. Node and -graph code don't change. Example 03 shows the OTel-only side at -finer granularity (`force_flush`, log bridging, error handling); -example 10 shows the Langfuse + `LangfusePromptBackend` prompt-linkage -side. +graph code don't change. The observer-hooks example shows the +OTel-only side at finer granularity (`force_flush`, log bridging, +error handling); the langfuse-observability example shows the +Langfuse + `LangfusePromptBackend` prompt-linkage side. diff --git a/docs/examples/01-routing-and-subgraphs.md b/docs/examples/routing-and-subgraphs.md similarity index 97% rename from docs/examples/01-routing-and-subgraphs.md rename to docs/examples/routing-and-subgraphs.md index b3064bdd..2721b3e8 100644 --- a/docs/examples/01-routing-and-subgraphs.md +++ b/docs/examples/routing-and-subgraphs.md @@ -1,4 +1,4 @@ -# 01 - Routing and subgraphs +# Routing and subgraphs A question-answering assistant. Classify the question, then either give a one-shot quick answer or run a multi-step research @@ -40,7 +40,7 @@ strategically important?"* (usually routes to research). ```bash uv sync --group examples -LLM_API_KEY=sk-... uv run python examples/01-routing-and-subgraphs/main.py \ +LLM_API_KEY=sk-... uv run python examples/routing-and-subgraphs/main.py \ "why is the lunar south pole strategically important?" ``` diff --git a/docs/examples/09-tool-use.md b/docs/examples/tool-use.md similarity index 97% rename from docs/examples/09-tool-use.md rename to docs/examples/tool-use.md index e3c5f9bc..c507f516 100644 --- a/docs/examples/09-tool-use.md +++ b/docs/examples/tool-use.md @@ -1,4 +1,4 @@ -# 09 - Tool use +# Tool use A lunar-mission assistant that calls local Python tools to answer questions mixing fact recall and physics arithmetic about Apollo @@ -54,13 +54,13 @@ A `MAX_TURNS` cap (5 here) prevents runaway loops. ```bash uv sync --group examples -LLM_API_KEY=sk-... uv run python examples/09-tool-use/main.py +LLM_API_KEY=sk-... uv run python examples/tool-use/main.py ``` Pass a question on the command line: ```bash -LLM_API_KEY=sk-... uv run python examples/09-tool-use/main.py \ +LLM_API_KEY=sk-... uv run python examples/tool-use/main.py \ "When was Apollo 17 launched?" ``` diff --git a/docs/index.md b/docs/index.md index c18138dc..2873c475 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,6 +11,7 @@ buy-in from every node. [![PyPI](https://img.shields.io/pypi/v/openarmature.svg?color=blue)](https://pypi.org/project/openarmature/){target="_blank" rel="noopener"} [![spec](https://img.shields.io/badge/dynamic/toml?url=https://raw.githubusercontent.com/LunarCommand/openarmature-python/main/pyproject.toml&query=%24.tool.openarmature.spec_version&label=spec&color=9D4EDD)](https://github.com/LunarCommand/openarmature-spec){target="_blank" rel="noopener"} +[![python](https://img.shields.io/python/required-version-toml?tomlFilePath=https://raw.githubusercontent.com/LunarCommand/openarmature-python/main/pyproject.toml&label=python&color=blue)](https://pypi.org/project/openarmature/){target="_blank" rel="noopener"} [Get started](getting-started/index.md){ .md-button .md-button--primary } [View on GitHub](https://github.com/LunarCommand/openarmature-python){ .md-button target="_blank" rel="noopener" } diff --git a/docs/patterns/caller-supplied-trace-identifiers.md b/docs/patterns/caller-supplied-trace-identifiers.md index 4b440e1f..bf21a120 100644 --- a/docs/patterns/caller-supplied-trace-identifiers.md +++ b/docs/patterns/caller-supplied-trace-identifiers.md @@ -132,7 +132,7 @@ spec-reserved key in flat Langfuse `trace.metadata`. - [Observability concept page](../concepts/observability.md): how OTel attributes and Langfuse metadata propagate. -- [`examples/10-langfuse-observability`](../examples/10-langfuse-observability.md): +- [`examples/langfuse-observability`](../examples/langfuse-observability.md): runnable example exercising the metadata propagation path. - Spec: [observability](https://openarmature.org/capabilities/observability/), the propagation contract for caller-supplied metadata. diff --git a/docs/patterns/session-as-checkpoint-resume.md b/docs/patterns/session-as-checkpoint-resume.md index 13705347..74b36f9d 100644 --- a/docs/patterns/session-as-checkpoint-resume.md +++ b/docs/patterns/session-as-checkpoint-resume.md @@ -109,6 +109,6 @@ state and the session table holds the join keys. `resume_invocation`, schema migration. - [State and reducers](../concepts/state-and-reducers.md): `merge` and `append` reducer strategies. -- [`examples/08-checkpointing-and-migration`](../examples/08-checkpointing-and-migration.md): +- [`examples/checkpointing-and-migration`](../examples/checkpointing-and-migration.md): single-resume baseline. - Spec: [pipeline-utilities](https://openarmature.org/capabilities/pipeline-utilities/) diff --git a/docs/patterns/tool-dispatch-as-node.md b/docs/patterns/tool-dispatch-as-node.md index a852174d..5b12f080 100644 --- a/docs/patterns/tool-dispatch-as-node.md +++ b/docs/patterns/tool-dispatch-as-node.md @@ -91,7 +91,7 @@ The `MAX_TURNS` cap prevents runaway loops; the conditional edge short-circuits to `present` when the cap is hit or when the model returns no `tool_calls`. -See [`examples/09-tool-use`](../examples/09-tool-use.md) for a +See [`examples/tool-use`](../examples/tool-use.md) for a runnable version with full tool definitions, defensive handling for malformed `ToolCall.arguments`, and trace output. @@ -125,6 +125,6 @@ for malformed `ToolCall.arguments`, and trace output. contract. - [State and reducers](../concepts/state-and-reducers.md): `append` reducer semantics. -- [`examples/09-tool-use`](../examples/09-tool-use.md): runnable +- [`examples/tool-use`](../examples/tool-use.md): runnable reference implementation. - Spec: [llm-provider](https://openarmature.org/capabilities/llm-provider/) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 3eda4f72..be908956 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -200,14 +200,110 @@ /* Indent nested nav items so section hierarchy reads at a glance. * Material's ``navigation.sections`` layout renders section labels * and their children flush-left at the same horizontal position; - * fine for short sections but hard to scan when one runs long (the - * Examples section has 11 entries). A small left-pad on the nested - * links shows the hierarchy without altering typography. Scoped to - * the primary nav so the right-hand TOC sidebar is untouched. */ + * a small left-pad on the nested links shows the hierarchy without + * altering typography. Scoped to the primary nav so the right-hand + * TOC sidebar is untouched. */ .md-nav--primary .md-nav .md-nav__link { padding-left: 0.375rem; } +/* Hide the active tab's title inside the sidebar. The top tabs + * (Documentation / Examples / Patterns) are shown above the + * content via ``navigation.tabs``; Material also renders the + * active tab's label as a ``.md-nav__title`` at the top of the + * sidebar, which is redundant. The level-1 nav is the tab's + * content; hiding its ``.md-nav__title`` drops the repeated + * label cleanly. */ +.md-nav--primary .md-nav[data-md-level="1"] > .md-nav__title { + display: none; +} + +/* Vertical guide line + extra indent next to the *children* of an + * expandable sub-section group (Concepts / Model Providers / + * Reference under Documentation; Foundations / Composition / + * etc. under Examples). Material renders the section's toggle in + * two different shapes: + * + * - ```` when the + * section has no ``index.md`` of its own (Examples' topical + * groups are like this). + * - ``