Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<id>`. 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=<id>`. 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)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions docs/concepts/llms.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down Expand Up @@ -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.
2 changes: 1 addition & 1 deletion docs/concepts/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -56,23 +56,23 @@ 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

```bash
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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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`
Expand All @@ -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
Expand Down
Loading