Declarative YAML workflow definitions for LangGraph — topology as config, logic in Python
Project description
Topology in YAML. Logic in Python. One line to compile.
Quickstart · Features · YAML reference · Examples · Roadmap
Describe a LangGraph workflow's structure in YAML, keep the behaviour in Python, and compile the two into a standard CompiledStateGraph. Nothing about how you run, stream, or deploy the graph changes.
[!TIP] Topology changes become config edits, not code rewrites. Workflow structure can be diffed in review, validated in your IDE, rendered as a diagram, stored and versioned in a database, and read by people who don't write Python.
Install
pip install langgraph-declarative # uv add langgraph-declarative
pip install "langgraph-declarative[anthropic]" # optional: llm: support ([openai] too)
Quickstart
1. Register your functions.
from langgraph_declarative import Registry, build_graph
registry = Registry()
@registry.node("greet")
def greet(state):
return {"messages": [{"role": "assistant", "content": "Hello! How can I help?"}]}
@registry.node("respond")
def respond(state):
return {"messages": [{"role": "assistant", "content": "Goodbye!"}]}
2. Declare the graph.
# workflow.yaml
nodes:
- name: "greeter"
function: "greet"
- name: "responder"
function: "respond"
edges:
- source: "START"
target: "greeter"
- source: "greeter"
target: "responder"
- source: "responder"
target: "END"
3. Build and run.
graph = build_graph("workflow.yaml", registry)
result = graph.invoke({"messages": [{"role": "user", "content": "Hi there"}]})
That's the whole surface area. build_graph() validates the YAML with Pydantic, cross-checks every function: and path: against the registry, and hands you a compiled LangGraph.
[!TIP] Every feature below has a commented, runnable example — see examples/.
How it fits together
flowchart LR
Y["workflow.yaml<br/><i>or a DB row</i>"] --> V["Schema validation<br/><i>Pydantic</i>"]
R["@registry.node<br/>@registry.router<br/>@registry.tool"] --> V
V --> B["GraphBuilder"]
B --> G["CompiledStateGraph<br/><i>.invoke() · .stream() · langgraph dev</i>"]
Three pieces: a registry of Python functions, a definition of the topology, and a builder that joins them. Validation happens before compilation, so a typo in the YAML gives you Unknown node function 'classfy'. Did you mean 'classify'? — not a stack trace at runtime.
Features
| Feature | YAML | Since | Example |
|---|---|---|---|
| Simple & parallel edges | target: "node" / target: [a, b] |
v1 | quickstart, fan_out |
| Conditional routing | path: + targets: |
v1 | conditional_routing |
Dynamic fan-out (Send) |
path: without targets |
v1 | dynamic_routing |
| Custom Python state class | build_graph(..., state_class=...) |
v1 | custom_state |
| State declared in YAML | state: with types & reducers |
v1.1 | declared_state |
| Match routing (no router fn) | match: + targets: |
v1.1 | match_routing |
| Mermaid diagrams | draw_mermaid() |
v1.1 | visualization |
| IDE autocomplete & validation | workflow.schema.json |
v1.1 | visualization |
| Subgraph composition | subgraph: "child.yaml" |
v2 | subgraph |
| Cross-file node imports | imports: |
v2 | cross_file_imports |
| LLM config & tool binding | llm: + tools: |
v2 | llm_and_tools |
| Database-stored workflows | SQLiteLoader, build_graph_from_db() |
v2 | db_workflow |
| LangGraph project template | — | v2 | template/ |
[!NOTE] v1 / v1.1 / v2 are milestone labels, not package versions. All three have shipped — see CHANGELOG.md for releases.
YAML reference
Only nodes is required. The simplest workflow is a list of nodes and edges.
Full schema — state, llm, imports, nodes, edges
state: # declare the state schema (default: MessagesState)
- name: "category"
type: "str" # str | int | float | bool | list | dict | list[str] | list[dict]
- name: "notes"
type: "list[str]"
reducer: "append" # append | add_messages | replace (default)
llm: # graph-level LLM default for opt-in nodes
provider: "anthropic" # anthropic | openai
model: "claude-opus-4-8"
imports: # merge node declarations from other files
- file: "shared_nodes.yaml"
nodes: ["error_handler"] # omit to import all nodes
nodes:
- name: "classifier"
function: "classify" # registered via @registry.node()
- name: "research"
subgraph: "child.yaml" # embed another workflow (function XOR subgraph)
- name: "agent"
function: "agent_fn" # function must accept an `llm` parameter to opt in
llm: { model: "claude-haiku-4-5" } # node-level override, merged over graph llm
tools: ["get_weather"] # @registry.tool() names or "module.path:attr"
edges:
- source: "START" # START, END, or a node name
target: "classifier" # simple edge
- source: "loader"
target: ["a", "b"] # parallel fan-out
- source: "classifier"
path: "router_name" # @registry.router(); omit targets for Send routing
targets: { key: "node" }
- source: "classifier"
match: "category" # route on a state field's value — no Python router
targets:
billing: "billing_agent"
default: "fallback" # reserved fallback key
[!TIP] Add this as the first line of any workflow file for autocomplete and inline validation in VS Code, JetBrains, and Neovim:
# yaml-language-server: $schema=path/to/workflow.schema.jsonThe schema ships at
schema/workflow.schema.json, or regenerate it withexport_json_schema().
API
| Function / Class | Description |
|---|---|
Registry() |
Holds node, router, and tool functions in separate namespaces |
@registry.node("name") |
Register a node function (transforms state) |
@registry.router("name") |
Register a router (returns a routing key or a list of Send) |
@registry.tool("name") |
Register a tool for tools: binding |
build_graph(path, registry, state_class=None) |
YAML file → compiled CompiledStateGraph |
build_graph_from_db(source, registry, db_path) |
DB-stored definition → compiled graph ("flow" or "flow@2") |
draw_mermaid(path, registry, output_path=None) |
Compile and render a Mermaid diagram (.md → fenced block) |
export_json_schema(output_path=None) |
Emit the JSON Schema for workflow YAML files |
GraphBuilder(registry, state_class=None) |
Power-user class behind build_graph() |
SQLiteLoader(db_path) |
Save/load versioned definitions; implements the pluggable Loader protocol |
Documentation
| Document | Contents |
|---|---|
| examples/ | 12 runnable examples, one per feature, organized by milestone |
| template/ | Starter project for langgraph dev using the declarative pattern |
| ROADMAP.md | What shipped per milestone, and unvalidated future ideas |
| CHANGELOG.md | Release history |
| DECISIONS.md | Why the API and the scope look the way they do |
| docs/02-requirements/REQUIREMENTS.md | Problem, users, success criteria |
| docs/03-design/DESIGN.md | Architecture, module responsibilities, trade-offs |
Project layout
src/langgraph_declarative/ # library (registry, schema, builder, state/llm factories, loaders)
schema/ # generated JSON Schema for workflow YAML
examples/ # runnable examples — one folder per feature
template/ # LangGraph starter template
tests/ # pytest suite + YAML fixtures
docs/ # requirements, design, roadmap, reviews
Requirements
- Python 3.10+
- LangGraph ≥ 0.2 · PyYAML ≥ 6.0 · Pydantic ≥ 2.0
- Optional:
[anthropic]/[openai]extras forllm:support
Contributing
Issues and pull requests welcome. Run the suite with uv run pytest before opening a PR.
License
MIT. Community project — not affiliated with or endorsed by LangChain. Built on top of LangGraph.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langgraph_declarative-0.2.0.tar.gz.
File metadata
- Download URL: langgraph_declarative-0.2.0.tar.gz
- Upload date:
- Size: 309.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7822af746fc5c2b5f6a18bcb981fb37cd456d22b3dc18138000bcacc88152416
|
|
| MD5 |
0960afc6bf907159226cbfd54dc6cb2a
|
|
| BLAKE2b-256 |
f3ee694549621bf583e9c7eef0be110d5a69a1e682b361248a5391a52e4a0a13
|
File details
Details for the file langgraph_declarative-0.2.0-py3-none-any.whl.
File metadata
- Download URL: langgraph_declarative-0.2.0-py3-none-any.whl
- Upload date:
- Size: 21.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96b81bf997205130fca0c37c44338642649159f1bb5c34f1f3efaac7ceffccb2
|
|
| MD5 |
ac4b6357c37c9b1c4e5d516aa2d0e6e7
|
|
| BLAKE2b-256 |
b920df4a4d8ce2b404f72b52348e37e276fc36a1c71391253eafdc8af05206af
|