You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: upgrade hello-world to demo structured output
Replaces the no-LLM hello-world in README.md with a version that
makes a real LLM call via OpenAIProvider and uses a Pydantic class
as the response_schema. The resulting Response.parsed flows through
state as a typed Classification instance and drives the conditional
edge that routes between research and summarize.
Defaults to OpenAI public API (gpt-4o-mini) with env-var config:
LLM_BASE_URL, LLM_MODEL, LLM_API_KEY. A trailing line in the README
calls out OpenRouter, vLLM, LM Studio, llama.cpp as drop-in swaps
via base_url/model.
The example also lands as a runnable file at
examples/00-hello-world/main.py and is added to the smoke test
suite. examples/README.md gets a corresponding entry.
Copy file name to clipboardExpand all lines: README.md
+41-35Lines changed: 41 additions & 35 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -55,26 +55,27 @@ The OpenTelemetry mapping mandates a private `TracerProvider`. That prevents the
55
55
56
56
## Hello World
57
57
58
-
About fifty lines that show the engine in action. Three reducer policies declared on one state class. Routing as a pure function of state, not a hidden state machine. An observer attached at compile time that sees every node boundary the engine emits. No LLM, no API key, no boilerplate. Copy it, run it, watch the events fire. Requires Python 3.12 or later.
58
+
About sixty lines that show the engine in action. Three reducer policies declared on one state class. An LLM call that returns a typed object, not a string. Conditional routing as a pure function of state, not a hidden state machine. An observer attached at compile time that sees every node boundary the engine emits. Requires Python 3.12 or later and an OpenAI-compatible endpoint (defaults to OpenAI public API; works against any local server too).
59
59
60
60
```python
61
61
import asyncio
62
-
from typing import Annotated
63
-
64
-
from openarmature.graph import (
65
-
END,
66
-
GraphBuilder,
67
-
NodeEvent,
68
-
State,
69
-
append,
70
-
merge,
71
-
)
72
-
from pydantic import Field
62
+
import os
63
+
from collections.abc import Mapping
64
+
from typing import Annotated, Any, Literal
65
+
66
+
from openarmature.graph importEND, GraphBuilder, NodeEvent, State, append, merge
67
+
from openarmature.llm import OpenAIProvider, UserMessage
Set `LLM_API_KEY=sk-...` and run. To swap providers, point `LLM_BASE_URL` and `LLM_MODEL` at OpenRouter, vLLM, LM Studio, llama.cpp — anything 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.
146
+
147
+
A few things to notice:
143
148
144
149
-**Three reducer policies on one state schema.**`query` and `classification` get the default `last_write_wins`. `sources` is `Annotated[list[str], append]`, so successive writes concatenate. `metadata` is `Annotated[dict[str, str], merge]`, so successive writes shallow-merge. The merge policy lives on the schema, once.
145
-
-**Conditional routing as a state function.**`route` reads `state.classification` and returns a node name. The graph engine doesn't care that this happens to be deterministic; it would accept an LLM-driven router with the same shape.
150
+
-**Structured output as a typed object.**`provider.complete(..., response_schema=Classification)` returns `Response.parsed` as a validated `Classification` instance, not a string the caller has to JSON-parse and re-validate. Pass a JSON Schema dict instead of a class for the raw form.
151
+
-**Conditional routing on a parsed field.**`route` reads `state.classification.intent` and returns the next node's name. The graph engine doesn't care the discriminator came from an LLM; it would accept a deterministic rule with the same shape.
146
152
-**Observer sees both phases.**`trace` filters to `completed` events for brevity; the engine also delivers `started` events.
147
153
-**The graph either compiles or it doesn't.** Remove `.set_entry()` and `.compile()` raises `NoDeclaredEntry` before `invoke()` runs.
0 commit comments