Build LangGraph graphs from JSON configuration
Project description
dynamic-langgraph — DynamicLangGraph Engine
Build LangGraph graphs from JSON configuration — no hardcoded topologies.
dynamic-langgraph is a Python library that converts declarative JSON config documents into fully compiled, executable LangGraph graphs. Define your nodes, edges, state schema, and execution settings in JSON. The engine validates, compiles, and hands you back an async-ready graph.
Features
- JSON-driven graph construction — declare nodes, edges, and state in config; skip the boilerplate
- Pre-compilation validation — catches unreachable nodes, static cycles, bad module paths, and signature mismatches before any node runs
- Async-first — sync handlers are auto-wrapped in
run_in_executor; no event loop blocking - Human-in-the-loop —
interrupt_before/interrupt_afterbreakpoints withCommand(resume=...)support - Map-reduce fan-out —
SendAPI for parallel branch execution and aggregation - Nested subgraphs — register compiled
CompiledStateGraphinstances as nodes in a parent graph - Streaming — all 5 LangGraph stream modes (
values,updates,debug,messages,custom) - Persistence —
InMemorySaver,SqliteSaver, or any customBaseCheckpointSaver - Per-node caching —
CachePolicywith configurable TTL - LangSmith telemetry — structured trace attributes emitted automatically
- Graph visualization — export to Mermaid (
.mmd) and images (PNG, SVG, JPG, PDF) - Strictly typed — fully annotated public API; passes
mypy --strict
Requirements
- Python 3.11+
langgraph >= 1.0.6jsonschema >= 4.23
Installation
pip install dynamic-langgraph
From source (development):
git clone https://github.com/your-org/dynamic-langgraph.git
cd dynamic-langgraph
uv sync --dev
Quickstart
1. Write a JSON config
{
"graph_id": "echo-graph",
"version": "1.0.0",
"state_definition": {
"type": "typed_dict",
"module_path": "myapp.state",
"class_name": "EchoState"
},
"nodes": [
{
"name": "echo",
"handler": {
"module_path": "myapp.nodes",
"function_name": "echo_node"
}
}
],
"edges": {
"entry_point": "echo",
"static_edges": [
{"from": "echo", "to": "__end__"}
]
}
}
2. Compile and run
import asyncio
from dlg import DLGEngine
graph = DLGEngine.from_file("graph_config.json")
result = asyncio.run(graph.ainvoke({"message": "hello"}))
print(result)
Or validate and compile separately:
from dlg import DLGValidator, DLGCompiler
config = {"graph_id": "echo-graph", "version": "1.0.0", ...}
DLGValidator(config).validate()
graph = DLGCompiler(config).compile()
Human-in-the-Loop
Add "interrupt_before": true to a node and provide a checkpointer:
{
"nodes": [
{
"name": "review",
"handler": {"module_path": "myapp.nodes", "function_name": "review_node"},
"interrupt_before": true
}
],
"checkpointer": {"type": "memory"}
}
import asyncio
from langgraph.types import Command
from dlg import DLGEngine
graph = DLGEngine.from_file("graph_config.json")
thread = {"configurable": {"thread_id": "session-1"}}
# Pauses at the interrupt_before node
await graph.ainvoke({"task": "review this"}, config=thread)
# Resume after human decision
result = await graph.ainvoke(Command(resume="approved"), config=thread)
Map-Reduce Fan-Out
{
"edges": {
"entry_point": "fan_out",
"conditional_edges": [
{
"from": "fan_out",
"send_router": {
"module_path": "myapp.routers",
"function_name": "fan_out_router"
}
}
]
}
}
from langgraph.types import Send
def fan_out_router(state):
return [Send("process_item", {"item": x}) for x in state["items"]]
Streaming
async for event in graph.astream(inputs, stream_mode="updates"):
print(event)
Supported modes: "values", "updates", "debug", "messages", "custom".
Graph Visualization
Python API
from dlg.mermaid import json_to_mermaid, convert_json_to_mmd, convert_mmd_to_image
# Get Mermaid diagram text from a config dict
config = {"graph_id": "my-graph", "version": "1.0.0", ...}
print(json_to_mermaid(config))
# flowchart TD
# __start__((__start__)) --> echo
# echo --> __end__((__end__))
# Write .mmd beside the JSON file
mmd_path = convert_json_to_mmd("graph_config.json")
# -> graph_config.mmd
# Convert .mmd to PNG (requires mmdc or npx)
img_path = convert_mmd_to_image(mmd_path, "png")
# -> graph_config.png
# Write image to a specific output directory
img_path = convert_mmd_to_image(mmd_path, "png", output_dir="graph-previews/")
# -> graph-previews/graph_config.png
CLI
# Generate .mmd only
python -m dlg graph_config.json
# Generate .mmd and a PNG
python -m dlg graph_config.json --format png
# Generate .mmd, PNG and SVG into a target folder
python -m dlg graph_config.json -f png -f svg --output-dir graph-previews/
# After pip install dynamic-langgraph:
dlg graph_config.json -f png --output-dir graph-previews/
mmdc from @mermaid-js/mermaid-cli is required for image export.
Install globally with npm i -g @mermaid-js/mermaid-cli, or ensure npx is available (falls back automatically).
Error Hierarchy
All validation errors are raised before any node executes.
| Exception | Trigger |
|---|---|
GraphValidationError |
Schema violation, missing field, bad module path |
UnreachableNodeException |
Node with no path from entry point |
StaticLoopException |
Cycle in static-only edges |
MixedRoutingConflict |
Node has both static edge and Command(goto=...) |
SendTargetError |
Send targets an undeclared node |
SubgraphCompatibilityError |
Subgraph state schema conflicts with parent |
CheckpointerMissingError |
Interrupt/resume attempted without checkpointer |
SchemaVersionMismatch |
Checkpoint config version differs from current config |
Testing Utilities
from dlg.testing import DLGTestHarness
harness = DLGTestHarness(config)
result = await harness.invoke_node("echo", state={"message": "hi"})
assert result["message"] == "hi"
Configuration Reference
A full JSON config supports the following top-level keys:
| Key | Required | Description |
|---|---|---|
graph_id |
Yes | Unique graph identifier |
version |
Yes | Semantic version string (e.g. "1.0.0") |
state_definition |
Yes | State schema — type (typed_dict, pydantic, dataclass), module_path, class_name |
nodes |
Yes | List of node descriptors (each with name and handler or subgraph) |
edges |
Yes | entry_point, static_edges, conditional_edges |
checkpointer |
No | {"type": "memory"}, {"type": "sqlite", "db_path": "..."}, or custom |
settings |
No | recursion_limit, max_concurrency |
global_defaults |
No | Default retry_policy, timeout_seconds |
cache |
No | Global cache policy |
security |
No | allowed_module_prefixes allowlist |
See docs/concepts/json-schema.md for the full schema reference.
Project Structure
dlg/
├── __init__.py # Public API exports
├── __main__.py # CLI entry point (python -m dlg / dlg command)
├── compiler.py # DLGCompiler — StateGraph assembly
├── mermaid.py # Mermaid/image export (json_to_mermaid, convert_*)
├── validator.py # DLGValidator — all pre-compilation checks
├── exceptions.py # Typed exception hierarchy
├── testing.py # DLGTestHarness utility
├── _config.py # Pydantic config models (GraphConfig, NodeConfig, …)
├── _schema.py # JSON metaschema
├── _wrappers.py # Async node wrapper + sync adapter
└── _telemetry.py # LangSmith integration
tests/
├── unit/ # DLGValidator, DLGCompiler, mermaid, models
└── integration/ # Full graph execution, streaming, checkpointing
docs/ # MkDocs site (concepts, API reference, examples)
Development
# Install dev dependencies
uv sync --dev
# Run tests
uv run python -m pytest
# Run tests with coverage
uv run python -m pytest --cov=dlg --cov-report=term-missing
# Type check
uv run mypy dlg --strict
# Build docs locally
uv run mkdocs serve
Contributing
- Fork the repo and create a feature branch
- Add tests for new behavior (coverage target: 90%)
- Ensure
mypy --strictpasses - Open a pull request — CI runs on Python 3.11, 3.12, and 3.13
License
Apache License 2.0 — see LICENSE.
Acknowledgements
Built on top of LangGraph by LangChain, Inc.
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 dynamic_langgraph-0.1.1.tar.gz.
File metadata
- Download URL: dynamic_langgraph-0.1.1.tar.gz
- Upload date:
- Size: 172.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":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 |
b54aeea300d5724fb6371898746b84ba9aeff31ead4ad8845d70d752ebbe7351
|
|
| MD5 |
c4510ec28a60dfc587b5e3fa2d479187
|
|
| BLAKE2b-256 |
7764b8fc48298151e2987f400a45b5b235b57f48bc52e090b2c73f63e48f3cad
|
File details
Details for the file dynamic_langgraph-0.1.1-py3-none-any.whl.
File metadata
- Download URL: dynamic_langgraph-0.1.1-py3-none-any.whl
- Upload date:
- Size: 20.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":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 |
dfda58ff8cc78d87e2eef3b2c137077ea0ebe1bd6c177aed3d29ca62847f21cc
|
|
| MD5 |
70775576ec14a66a235625320e1e7682
|
|
| BLAKE2b-256 |
f3debaefdbb56e81e7814be1ccbcacec065bd9b0d71705f4248fa05644023d4b
|