yamlgraph 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of yamlgraph might be problematic. Click here for more details.

Files changed (111) hide show
  1. examples/__init__.py +1 -0
  2. examples/storyboard/__init__.py +1 -0
  3. examples/storyboard/generate_videos.py +335 -0
  4. examples/storyboard/nodes/__init__.py +10 -0
  5. examples/storyboard/nodes/animated_character_node.py +248 -0
  6. examples/storyboard/nodes/animated_image_node.py +138 -0
  7. examples/storyboard/nodes/character_node.py +162 -0
  8. examples/storyboard/nodes/image_node.py +118 -0
  9. examples/storyboard/nodes/replicate_tool.py +238 -0
  10. examples/storyboard/retry_images.py +118 -0
  11. tests/__init__.py +1 -0
  12. tests/conftest.py +178 -0
  13. tests/integration/__init__.py +1 -0
  14. tests/integration/test_animated_storyboard.py +63 -0
  15. tests/integration/test_cli_commands.py +242 -0
  16. tests/integration/test_map_demo.py +50 -0
  17. tests/integration/test_memory_demo.py +281 -0
  18. tests/integration/test_pipeline_flow.py +105 -0
  19. tests/integration/test_providers.py +163 -0
  20. tests/integration/test_resume.py +75 -0
  21. tests/unit/__init__.py +1 -0
  22. tests/unit/test_agent_nodes.py +200 -0
  23. tests/unit/test_checkpointer.py +212 -0
  24. tests/unit/test_cli.py +121 -0
  25. tests/unit/test_cli_package.py +81 -0
  26. tests/unit/test_compile_graph_map.py +132 -0
  27. tests/unit/test_conditions_routing.py +253 -0
  28. tests/unit/test_config.py +93 -0
  29. tests/unit/test_conversation_memory.py +270 -0
  30. tests/unit/test_database.py +145 -0
  31. tests/unit/test_deprecation.py +104 -0
  32. tests/unit/test_executor.py +60 -0
  33. tests/unit/test_executor_async.py +179 -0
  34. tests/unit/test_export.py +150 -0
  35. tests/unit/test_expressions.py +178 -0
  36. tests/unit/test_format_prompt.py +145 -0
  37. tests/unit/test_generic_report.py +200 -0
  38. tests/unit/test_graph_commands.py +327 -0
  39. tests/unit/test_graph_loader.py +299 -0
  40. tests/unit/test_graph_schema.py +193 -0
  41. tests/unit/test_inline_schema.py +151 -0
  42. tests/unit/test_issues.py +164 -0
  43. tests/unit/test_jinja2_prompts.py +85 -0
  44. tests/unit/test_langsmith.py +319 -0
  45. tests/unit/test_llm_factory.py +109 -0
  46. tests/unit/test_llm_factory_async.py +118 -0
  47. tests/unit/test_loops.py +403 -0
  48. tests/unit/test_map_node.py +144 -0
  49. tests/unit/test_no_backward_compat.py +56 -0
  50. tests/unit/test_node_factory.py +225 -0
  51. tests/unit/test_prompts.py +166 -0
  52. tests/unit/test_python_nodes.py +198 -0
  53. tests/unit/test_reliability.py +298 -0
  54. tests/unit/test_result_export.py +234 -0
  55. tests/unit/test_router.py +296 -0
  56. tests/unit/test_sanitize.py +99 -0
  57. tests/unit/test_schema_loader.py +295 -0
  58. tests/unit/test_shell_tools.py +229 -0
  59. tests/unit/test_state_builder.py +331 -0
  60. tests/unit/test_state_builder_map.py +104 -0
  61. tests/unit/test_state_config.py +197 -0
  62. tests/unit/test_template.py +190 -0
  63. tests/unit/test_tool_nodes.py +129 -0
  64. yamlgraph/__init__.py +35 -0
  65. yamlgraph/builder.py +110 -0
  66. yamlgraph/cli/__init__.py +139 -0
  67. yamlgraph/cli/__main__.py +6 -0
  68. yamlgraph/cli/commands.py +232 -0
  69. yamlgraph/cli/deprecation.py +92 -0
  70. yamlgraph/cli/graph_commands.py +382 -0
  71. yamlgraph/cli/validators.py +37 -0
  72. yamlgraph/config.py +67 -0
  73. yamlgraph/constants.py +66 -0
  74. yamlgraph/error_handlers.py +226 -0
  75. yamlgraph/executor.py +275 -0
  76. yamlgraph/executor_async.py +122 -0
  77. yamlgraph/graph_loader.py +337 -0
  78. yamlgraph/map_compiler.py +138 -0
  79. yamlgraph/models/__init__.py +36 -0
  80. yamlgraph/models/graph_schema.py +141 -0
  81. yamlgraph/models/schemas.py +124 -0
  82. yamlgraph/models/state_builder.py +236 -0
  83. yamlgraph/node_factory.py +240 -0
  84. yamlgraph/routing.py +87 -0
  85. yamlgraph/schema_loader.py +160 -0
  86. yamlgraph/storage/__init__.py +17 -0
  87. yamlgraph/storage/checkpointer.py +72 -0
  88. yamlgraph/storage/database.py +320 -0
  89. yamlgraph/storage/export.py +269 -0
  90. yamlgraph/tools/__init__.py +1 -0
  91. yamlgraph/tools/agent.py +235 -0
  92. yamlgraph/tools/nodes.py +124 -0
  93. yamlgraph/tools/python_tool.py +178 -0
  94. yamlgraph/tools/shell.py +205 -0
  95. yamlgraph/utils/__init__.py +47 -0
  96. yamlgraph/utils/conditions.py +157 -0
  97. yamlgraph/utils/expressions.py +111 -0
  98. yamlgraph/utils/langsmith.py +308 -0
  99. yamlgraph/utils/llm_factory.py +118 -0
  100. yamlgraph/utils/llm_factory_async.py +105 -0
  101. yamlgraph/utils/logging.py +127 -0
  102. yamlgraph/utils/prompts.py +116 -0
  103. yamlgraph/utils/sanitize.py +98 -0
  104. yamlgraph/utils/template.py +102 -0
  105. yamlgraph/utils/validators.py +181 -0
  106. yamlgraph-0.1.1.dist-info/METADATA +854 -0
  107. yamlgraph-0.1.1.dist-info/RECORD +111 -0
  108. yamlgraph-0.1.1.dist-info/WHEEL +5 -0
  109. yamlgraph-0.1.1.dist-info/entry_points.txt +2 -0
  110. yamlgraph-0.1.1.dist-info/licenses/LICENSE +21 -0
  111. yamlgraph-0.1.1.dist-info/top_level.txt +3 -0
@@ -0,0 +1,854 @@
1
+ Metadata-Version: 2.4
2
+ Name: yamlgraph
3
+ Version: 0.1.1
4
+ Summary: YAML-first framework for building LLM pipelines with LangGraph
5
+ License: MIT
6
+ Requires-Python: <3.14,>=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: langchain-anthropic>=0.3.0
10
+ Requires-Dist: langchain-mistralai>=0.2.0
11
+ Requires-Dist: langchain-openai>=0.3.0
12
+ Requires-Dist: langgraph>=0.2.0
13
+ Requires-Dist: langgraph-checkpoint-sqlite>=2.0.0
14
+ Requires-Dist: pydantic>=2.0.0
15
+ Requires-Dist: python-dotenv>=1.0.0
16
+ Requires-Dist: pyyaml>=6.0
17
+ Requires-Dist: langsmith>=0.1.0
18
+ Requires-Dist: jinja2>=3.1.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
21
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
22
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
23
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
24
+ Provides-Extra: storyboard
25
+ Requires-Dist: replicate>=0.25.0; extra == "storyboard"
26
+ Dynamic: license-file
27
+
28
+ # YamlGraph
29
+
30
+ [![PyPI version](https://badge.fury.io/py/yamlgraph.svg)](https://pypi.org/project/yamlgraph/)
31
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
33
+
34
+ A YAML-first framework for building LLM pipelines using:
35
+
36
+ - **YAML Graph Configuration** - Declarative pipeline definition with schema validation
37
+ - **YAML Prompts** - Declarative prompt templates with Jinja2 support
38
+ - **Pydantic Models** - Structured LLM outputs
39
+ - **Multi-Provider LLMs** - Support for Anthropic, Mistral, and OpenAI
40
+ - **LangGraph** - Pipeline orchestration with resume support
41
+ - **SQLite** - State persistence
42
+ - **LangSmith** - Observability and tracing
43
+ - **JSON Export** - Result serialization
44
+
45
+ ## Installation
46
+
47
+ ### From PyPI
48
+
49
+ ```bash
50
+ pip install yamlgraph
51
+ ```
52
+
53
+ ### From Source
54
+
55
+ ```bash
56
+ git clone https://github.com/sheikkinen/yamlgraph.git
57
+ cd yamlgraph
58
+ pip install -e ".[dev]"
59
+ ```
60
+
61
+ ## Quick Start
62
+
63
+ ### 1. Create a Prompt
64
+
65
+ Create `prompts/greet.yaml`:
66
+
67
+ ```yaml
68
+ system: |
69
+ You are a friendly assistant.
70
+
71
+ user: |
72
+ Say hello to {name} in a {style} way.
73
+ ```
74
+
75
+ ### 2. Create a Graph
76
+
77
+ Create `graphs/hello.yaml`:
78
+
79
+ ```yaml
80
+ version: "1.0"
81
+ name: hello-world
82
+
83
+ nodes:
84
+ greet:
85
+ type: llm
86
+ prompt: greet
87
+ variables:
88
+ name: "{state.name}"
89
+ style: "{state.style}"
90
+ state_key: greeting
91
+
92
+ edges:
93
+ - from: START
94
+ to: greet
95
+ - from: greet
96
+ to: END
97
+ ```
98
+
99
+ ### 3. Set API Key
100
+
101
+ ```bash
102
+ export ANTHROPIC_API_KEY=your-key-here
103
+ # Or: export MISTRAL_API_KEY=... or OPENAI_API_KEY=...
104
+ ```
105
+
106
+ ### 4. Run It
107
+
108
+ ```bash
109
+ yamlgraph graph run graphs/hello.yaml --var name="World" --var style="enthusiastic"
110
+ ```
111
+
112
+ Or use the Python API:
113
+
114
+ ```python
115
+ from yamlgraph.graph_loader import load_and_compile
116
+
117
+ graph = load_and_compile("graphs/hello.yaml")
118
+ app = graph.compile()
119
+ result = app.invoke({"name": "World", "style": "enthusiastic"})
120
+ print(result["greeting"])
121
+ ```
122
+
123
+ ---
124
+
125
+ ## More Examples
126
+
127
+ ```bash
128
+ # Content generation pipeline
129
+ yamlgraph graph run graphs/yamlgraph.yaml --var topic="AI" --var style=casual
130
+
131
+ # Sentiment-based routing
132
+ yamlgraph graph run graphs/router-demo.yaml --var message="I love this!"
133
+
134
+ # Self-correction loop (Reflexion pattern)
135
+ yamlgraph graph run graphs/reflexion-demo.yaml --var topic="climate change"
136
+
137
+ # AI agent with shell tools
138
+ yamlgraph graph run graphs/git-report.yaml --var input="What changed recently?"
139
+
140
+ # Parallel fan-out with map nodes
141
+ yamlgraph graph run examples/storyboard/animated-character-graph.yaml \
142
+ --var concept="A brave mouse knight" --var model=hidream
143
+ ```
144
+
145
+ ### CLI Utilities
146
+
147
+ ```bash
148
+ yamlgraph graph list # List available graphs
149
+ yamlgraph graph info graphs/router-demo.yaml # Show graph structure
150
+ yamlgraph graph validate graphs/*.yaml # Validate graph schemas
151
+ yamlgraph list-runs # View recent runs
152
+ yamlgraph resume --thread-id abc123 # Resume a run
153
+ yamlgraph export --thread-id abc123 # Export run to JSON
154
+
155
+ # Observability (requires LangSmith)
156
+ yamlgraph trace --verbose # View execution trace
157
+ yamlgraph mermaid # Show pipeline as Mermaid diagram
158
+ ```
159
+
160
+ ## Documentation
161
+
162
+ See the [reference/](reference/) folder for comprehensive YAML configuration guides:
163
+
164
+ - [Quick Start](reference/quickstart.md) - Create your first pipeline in 5 minutes
165
+ - [Graph YAML Reference](reference/graph-yaml.md) - All graph configuration options
166
+ - [Prompt YAML Reference](reference/prompt-yaml.md) - Schema and template syntax
167
+ - [Map Nodes](reference/map-nodes.md) - Parallel fan-out/fan-in processing
168
+ - [Common Patterns](reference/patterns.md) - Router, loops, agents, and more
169
+
170
+ ## Architecture
171
+
172
+ ### Data Flow
173
+
174
+ ```mermaid
175
+ flowchart TB
176
+ subgraph Input["📥 Input Layer"]
177
+ CLI["CLI Command"]
178
+ YAML_G["graphs/*.yaml"]
179
+ YAML_P["prompts/*.yaml"]
180
+ end
181
+
182
+ subgraph Core["⚙️ Core Processing"]
183
+ GL["graph_loader.py<br/>YAML → StateGraph"]
184
+ NF["node_factory.py<br/>Create Node Functions"]
185
+ EH["error_handlers.py<br/>Skip/Retry/Fail/Fallback"]
186
+ EX["executor.py<br/>Prompt Execution"]
187
+ end
188
+
189
+ subgraph LLM["🤖 LLM Layer"]
190
+ LF["llm_factory.py"]
191
+ ANT["Anthropic"]
192
+ MIS["Mistral"]
193
+ OAI["OpenAI"]
194
+ end
195
+
196
+ subgraph State["💾 State Layer"]
197
+ SB["state_builder.py<br/>Dynamic TypedDict"]
198
+ CP["checkpointer.py<br/>SQLite Persistence"]
199
+ DB[(SQLite DB)]
200
+ end
201
+
202
+ subgraph Output["📤 Output Layer"]
203
+ EXP["export.py"]
204
+ JSON["JSON Export"]
205
+ LS["LangSmith Traces"]
206
+ end
207
+
208
+ CLI --> GL
209
+ YAML_G --> GL
210
+ YAML_P --> EX
211
+ GL --> NF
212
+ NF --> EH
213
+ EH --> EX
214
+ EX --> LF
215
+ LF --> ANT & MIS & OAI
216
+ GL --> SB
217
+ SB --> CP
218
+ CP --> DB
219
+ EX --> EXP
220
+ EXP --> JSON
221
+ EX --> LS
222
+ ```
223
+
224
+ ### Directory Structure
225
+
226
+ ```
227
+ yamlgraph/
228
+ ├── README.md
229
+ ├── pyproject.toml # Package definition with CLI entry point and dependencies
230
+ ├── .env.sample # Environment template
231
+
232
+ ├── graphs/ # YAML graph definitions
233
+ │ ├── yamlgraph.yaml # Main pipeline definition
234
+ │ ├── router-demo.yaml # Tone-based routing demo
235
+ │ ├── reflexion-demo.yaml # Self-refinement loop demo
236
+ │ └── git-report.yaml # AI agent demo with shell tools
237
+
238
+ ├── yamlgraph/ # Main package
239
+ │ ├── __init__.py # Package exports
240
+ │ ├── builder.py # Graph builders (loads from YAML)
241
+ │ ├── graph_loader.py # YAML → LangGraph compiler
242
+ │ ├── config.py # Centralized configuration
243
+ │ ├── executor.py # YAML prompt executor
244
+ │ ├── cli.py # CLI commands
245
+ │ │
246
+ │ ├── models/ # Pydantic models
247
+ │ │ ├── __init__.py
248
+ │ │ ├── schemas.py # Framework schemas (ErrorType, PipelineError, GenericReport)
249
+ │ │ ├── state_builder.py # Dynamic state generation from YAML
250
+ │ │ └── graph_schema.py # Pydantic schema validation
251
+ │ │
252
+ │ ├── tools/ # Tool execution
253
+ │ │ ├── __init__.py
254
+ │ │ ├── shell.py # Shell command executor
255
+ │ │ ├── nodes.py # Tool node factory
256
+ │ │ └── agent.py # Agent node factory
257
+ │ │
258
+ │ ├── storage/ # Persistence layer
259
+ │ │ ├── __init__.py
260
+ │ │ ├── database.py # SQLite wrapper
261
+ │ │ └── export.py # JSON export
262
+ │ │
263
+ │ └── utils/ # Utilities
264
+ │ ├── __init__.py
265
+ │ ├── llm_factory.py # Multi-provider LLM creation
266
+ │ └── langsmith.py # Tracing helpers
267
+
268
+ ├── prompts/ # YAML prompt templates
269
+ │ ├── greet.yaml
270
+ │ ├── analyze.yaml
271
+ │ ├── analyze_list.yaml # Jinja2 example with loops/filters
272
+ │ ├── generate.yaml
273
+ │ ├── summarize.yaml
274
+ │ └── router-demo/ # Tone routing prompts
275
+ │ ├── classify_tone.yaml
276
+ │ ├── respond_positive.yaml
277
+ │ ├── respond_negative.yaml
278
+ │ └── respond_neutral.yaml
279
+
280
+ ├── reference/ # YAML configuration reference docs
281
+ │ ├── README.md # Overview and key concepts
282
+ │ ├── quickstart.md # 5-minute getting started guide
283
+ │ ├── graph-yaml.md # Graph YAML reference
284
+ │ ├── prompt-yaml.md # Prompt YAML reference
285
+ │ └── patterns.md # Common patterns and examples
286
+
287
+ ├── tests/ # Test suite
288
+ │ ├── conftest.py # Shared fixtures
289
+ │ ├── unit/ # Unit tests
290
+ │ └── integration/ # Integration tests
291
+
292
+ └── outputs/ # Generated files (gitignored)
293
+ ```
294
+ ```
295
+
296
+ ## Pipeline Flow
297
+
298
+ ```mermaid
299
+ graph TD
300
+ A["📝 generate"] -->|content| B{should_continue}
301
+ B -->|"✓ content exists"| C["🔍 analyze"]
302
+ B -->|"✗ error/empty"| F["🛑 END"]
303
+ C -->|analysis| D["📊 summarize"]
304
+ D -->|final_summary| F
305
+
306
+ style A fill:#e1f5fe
307
+ style C fill:#fff3e0
308
+ style D fill:#e8f5e9
309
+ style F fill:#fce4ec
310
+ ```
311
+
312
+ ### Node Outputs
313
+
314
+ | Node | Output Type | Description |
315
+ |------|-------------|-------------|
316
+ | `generate` | Inline schema | Title, content, word_count, tags |
317
+ | `analyze` | Inline schema | Summary, key_points, sentiment, confidence |
318
+ | `summarize` | `str` | Final combined summary |
319
+
320
+ Output schemas are defined inline in YAML prompt files using the `schema:` block.
321
+
322
+ ### Resume Flow
323
+
324
+ Pipelines can be resumed from any checkpoint. The resume behavior uses `skip_if_exists`:
325
+ nodes check if their output already exists in state and skip LLM calls if so.
326
+
327
+ ```mermaid
328
+ graph LR
329
+ subgraph "Resume after 'analyze' completed"
330
+ A1["Load State"] --> B1["analyze (skipped)"] --> C1["summarize"] --> D1["END"]
331
+ end
332
+ ```
333
+
334
+ ```bash
335
+ # Resume an interrupted run
336
+ yamlgraph resume --thread-id abc123
337
+ ```
338
+
339
+ When resumed:
340
+ - Nodes with existing outputs are **skipped** (no duplicate LLM calls)
341
+ - Only nodes without outputs in state actually run
342
+ - State is preserved via SQLite checkpointing
343
+
344
+ ## Key Patterns
345
+
346
+ ### 1. YAML Prompt Templates
347
+
348
+ **Simple Templating (Basic Substitution)**:
349
+ ```yaml
350
+ # prompts/generate.yaml
351
+ system: |
352
+ You are a creative content writer...
353
+
354
+ user: |
355
+ Write about: {topic}
356
+ Target length: approximately {word_count} words
357
+ ```
358
+
359
+ **Advanced Templating (Jinja2)**:
360
+ ```yaml
361
+ # prompts/analyze_list.yaml
362
+ template: |
363
+ Analyze the following {{ items|length }} items:
364
+
365
+ {% for item in items %}
366
+ ### {{ loop.index }}. {{ item.title }}
367
+ Topic: {{ item.topic }}
368
+ {% if item.tags %}
369
+ Tags: {{ item.tags | join(", ") }}
370
+ {% endif %}
371
+ {% endfor %}
372
+ ```
373
+
374
+ **Template Features**:
375
+ - **Auto-detection**: Uses Jinja2 if `{{` or `{%` present, otherwise simple formatting
376
+ - **Loops**: `{% for item in items %}...{% endfor %}`
377
+ - **Conditionals**: `{% if condition %}...{% endif %}`
378
+ - **Filters**: `{{ text[:50] }}`, `{{ items | join(", ") }}`, `{{ name | upper }}`
379
+ - **Backward compatible**: Existing `{variable}` prompts work unchanged
380
+
381
+ ### 2. Structured Executor
382
+
383
+ ```python
384
+ from yamlgraph.executor import execute_prompt
385
+ from yamlgraph.models import GenericReport
386
+
387
+ result = execute_prompt(
388
+ "generate",
389
+ variables={"topic": "AI", "word_count": 300},
390
+ output_model=GenericReport,
391
+ )
392
+ print(result.title) # Typed access!
393
+ ```
394
+
395
+ ### 3. Multi-Provider LLM Support
396
+
397
+ ```python
398
+ from yamlgraph.executor import execute_prompt
399
+
400
+ # Use default provider (Anthropic)
401
+ result = execute_prompt(
402
+ "greet",
403
+ variables={"name": "Alice", "style": "formal"},
404
+ )
405
+
406
+ # Switch to Mistral
407
+ result = execute_prompt(
408
+ "greet",
409
+ variables={"name": "Bob", "style": "casual"},
410
+ provider="mistral",
411
+ )
412
+
413
+ # Or set via environment variable
414
+ # PROVIDER=openai yamlgraph graph run ...
415
+ ```
416
+
417
+ Supported providers:
418
+ - **Anthropic** (default): Claude models
419
+ - **Mistral**: Mistral Large and other models
420
+ - **OpenAI**: GPT-4 and other models
421
+
422
+ Provider selection priority:
423
+ 1. Function parameter: `execute_prompt(..., provider="mistral")`
424
+ 2. YAML metadata: `provider: mistral` in prompt file
425
+ 3. Environment variable: `PROVIDER=mistral`
426
+ 4. Default: `anthropic`
427
+
428
+ ### 4. YAML Graph Configuration
429
+
430
+ Pipelines are defined declaratively in YAML and compiled to LangGraph:
431
+
432
+ ```yaml
433
+ # graphs/yamlgraph.yaml
434
+ version: "1.0"
435
+ name: yamlgraph-demo
436
+ description: Content generation pipeline
437
+
438
+ defaults:
439
+ provider: mistral
440
+ temperature: 0.7
441
+
442
+ nodes:
443
+ generate:
444
+ type: llm
445
+ prompt: generate
446
+ output_schema: # Inline schema - no Python model needed!
447
+ title: str
448
+ content: str
449
+ word_count: int
450
+ tags: list[str]
451
+ temperature: 0.8
452
+ variables:
453
+ topic: "{state.topic}"
454
+ word_count: "{state.word_count}"
455
+ style: "{state.style}"
456
+ state_key: generated
457
+
458
+ analyze:
459
+ type: llm
460
+ prompt: analyze
461
+ output_schema: # Inline schema
462
+ summary: str
463
+ key_points: list[str]
464
+ sentiment: str
465
+ confidence: float
466
+ temperature: 0.3
467
+ variables:
468
+ content: "{state.generated.content}"
469
+ state_key: analysis
470
+ requires: [generated]
471
+
472
+ summarize:
473
+ type: llm
474
+ prompt: summarize
475
+ temperature: 0.5
476
+ state_key: final_summary
477
+ requires: [generated, analysis]
478
+
479
+ edges:
480
+ - from: START
481
+ to: generate
482
+ - from: generate
483
+ to: analyze
484
+ condition: continue
485
+ - from: generate
486
+ to: END
487
+ condition: end
488
+ - from: analyze
489
+ to: summarize
490
+ - from: summarize
491
+ to: END
492
+ ```
493
+
494
+ **Load and run**:
495
+ ```python
496
+ from yamlgraph.builder import build_graph
497
+
498
+ graph = build_graph().compile() # Loads from graphs/yamlgraph.yaml
499
+ result = graph.invoke(initial_state)
500
+ ```
501
+
502
+ ### 5. State Persistence
503
+
504
+ ```python
505
+ from yamlgraph.storage import YamlGraphDB
506
+
507
+ db = YamlGraphDB()
508
+ db.save_state("thread-123", state)
509
+ state = db.load_state("thread-123")
510
+ ```
511
+
512
+ ### 6. LangSmith Tracing
513
+
514
+ ```python
515
+ from yamlgraph.utils.langsmith import print_run_tree
516
+
517
+ print_run_tree(verbose=True)
518
+ # 📊 Execution Tree:
519
+ # └─ yamlgraph_pipeline (12.3s) ✅
520
+ # ├─ generate (5.2s) ✅
521
+ # ├─ analyze (3.1s) ✅
522
+ # └─ summarize (4.0s) ✅
523
+ ```
524
+
525
+ ### 7. Shell Tools & Agent Nodes
526
+
527
+ Define shell tools and let the LLM decide when to use them:
528
+
529
+ ```yaml
530
+ # graphs/git-report.yaml
531
+ tools:
532
+ recent_commits:
533
+ type: shell
534
+ command: git log --oneline -n {count}
535
+ description: "List recent commits"
536
+
537
+ changed_files:
538
+ type: shell
539
+ command: git diff --name-only HEAD~{n}
540
+ description: "List files changed in last n commits"
541
+
542
+ nodes:
543
+ analyze:
544
+ type: agent # LLM decides which tools to call
545
+ prompt: git_analyst
546
+ tools: [recent_commits, changed_files]
547
+ max_iterations: 8
548
+ state_key: analysis
549
+ ```
550
+
551
+ Run the git analysis agent:
552
+
553
+ ```bash
554
+ yamlgraph git-report -q "What changed recently?"
555
+ yamlgraph git-report -q "Summarize the test directory"
556
+ ```
557
+
558
+ **Node types:**
559
+ - `type: llm` - Standard LLM call with structured output
560
+ - `type: router` - Classify and route to different paths
561
+ - `type: map` - Parallel fan-out over lists with `Send()`
562
+ - `type: python` - Execute custom Python functions
563
+ - `type: agent` - LLM loop that autonomously calls tools
564
+
565
+ ## Environment Variables
566
+
567
+ | Variable | Required | Description |
568
+ |----------|----------|-------------|
569
+ | `ANTHROPIC_API_KEY` | Yes* | Anthropic API key (* if using Anthropic) |
570
+ | `MISTRAL_API_KEY` | No | Mistral API key (required if using Mistral) |
571
+ | `OPENAI_API_KEY` | No | OpenAI API key (required if using OpenAI) |
572
+ | `PROVIDER` | No | Default LLM provider (anthropic/mistral/openai) |
573
+ | `ANTHROPIC_MODEL` | No | Anthropic model (default: claude-sonnet-4-20250514) |
574
+ | `MISTRAL_MODEL` | No | Mistral model (default: mistral-large-latest) |
575
+ | `OPENAI_MODEL` | No | OpenAI model (default: gpt-4o) |
576
+ | `LANGCHAIN_TRACING` | No | Enable LangSmith tracing |
577
+ | `LANGCHAIN_API_KEY` | No | LangSmith API key |
578
+ | `LANGCHAIN_ENDPOINT` | No | LangSmith endpoint URL |
579
+ | `LANGCHAIN_PROJECT` | No | LangSmith project name |
580
+
581
+ ## Testing
582
+
583
+ Run the test suite:
584
+
585
+ ```bash
586
+ # Run all tests
587
+ pytest tests/ -v
588
+
589
+ # Run only unit tests
590
+ pytest tests/unit/ -v
591
+
592
+ # Run only integration tests
593
+ pytest tests/integration/ -v
594
+
595
+ # Run with coverage report
596
+ pytest tests/ --cov=yamlgraph --cov-report=term-missing
597
+
598
+ # Run with HTML coverage report
599
+ pytest tests/ --cov=yamlgraph --cov-report=html
600
+ # Then open htmlcov/index.html
601
+ ```
602
+
603
+ **Current coverage**: 60% overall, 98% on graph_loader, 100% on builder/llm_factory.
604
+
605
+ ## Extending the Pipeline
606
+
607
+ ### Adding a New Node (YAML-First Approach)
608
+
609
+ Let's add a "fact_check" node that verifies generated content:
610
+
611
+ **Step 1: Define the output schema** (`yamlgraph/models/schemas.py`):
612
+ ```python
613
+ class FactCheck(BaseModel):
614
+ """Structured fact-checking output."""
615
+
616
+ claims: list[str] = Field(description="Claims identified in content")
617
+ verified: bool = Field(description="Whether claims are verifiable")
618
+ confidence: float = Field(ge=0.0, le=1.0, description="Verification confidence")
619
+ notes: str = Field(description="Additional context")
620
+ ```
621
+
622
+ **Step 2: Create the prompt** (`prompts/fact_check.yaml`):
623
+ ```yaml
624
+ system: |
625
+ You are a fact-checker. Analyze the given content and identify
626
+ claims that can be verified. Assess the overall verifiability.
627
+
628
+ user: |
629
+ Content to fact-check:
630
+ {content}
631
+
632
+ Identify key claims and assess their verifiability.
633
+ ```
634
+
635
+ **Step 3: State is auto-generated**
636
+
637
+ State fields are now generated automatically from your YAML graph config.
638
+ The `state_key` in your node config determines where output is stored:
639
+ ```yaml
640
+ # Node output stored in state.fact_check automatically
641
+ fact_check:
642
+ type: llm
643
+ prompt: fact_check
644
+ state_key: fact_check # This creates the state field
645
+ ```
646
+
647
+ **Step 4: Add the node to your graph** (`graphs/yamlgraph.yaml`):
648
+ ```yaml
649
+ nodes:
650
+ generate:
651
+ type: prompt
652
+ prompt: generate
653
+ output_schema: # Inline schema - no Python model needed!
654
+ title: str
655
+ content: str
656
+ variables:
657
+ topic: topic
658
+ state_key: generated
659
+
660
+ fact_check: # ✨ New node - just YAML!
661
+ type: prompt
662
+ prompt: fact_check
663
+ output_schema: # Define schema inline
664
+ is_accurate: bool
665
+ issues: list[str]
666
+ requires: [generated]
667
+ variables:
668
+ content: generated.content
669
+ state_key: fact_check
670
+
671
+ analyze:
672
+ # ... existing config ...
673
+
674
+ edges:
675
+ - from: START
676
+ to: generate
677
+ - from: generate
678
+ to: fact_check
679
+ condition:
680
+ type: has_value
681
+ field: generated
682
+ - from: fact_check
683
+ to: analyze
684
+ # ... rest of edges ...
685
+ ```
686
+
687
+ That's it! No Python node code needed. The graph loader dynamically generates the node function.
688
+
689
+ Resulting pipeline:
690
+ ```mermaid
691
+ graph TD
692
+ A[generate] --> B{has generated?}
693
+ B -->|yes| C[fact_check]
694
+ C --> D[analyze]
695
+ D --> E[summarize]
696
+ E --> F[END]
697
+ B -->|no| F
698
+ ```
699
+
700
+ ### Adding Conditional Branching
701
+
702
+ Route to different nodes based on analysis results (all in YAML):
703
+
704
+ ```yaml
705
+ edges:
706
+ - from: analyze
707
+ to: rewrite_node
708
+ condition:
709
+ type: field_equals
710
+ field: analysis.sentiment
711
+ value: negative
712
+
713
+ - from: analyze
714
+ to: enhance_node
715
+ condition:
716
+ type: field_equals
717
+ field: analysis.sentiment
718
+ value: positive
719
+
720
+ - from: analyze
721
+ to: summarize # Default fallback
722
+ ```
723
+
724
+ ### Add a New Prompt
725
+
726
+ 1. Create `prompts/new_prompt.yaml`:
727
+ ```yaml
728
+ system: Your system prompt...
729
+ user: Your user prompt with {variables}...
730
+ ```
731
+
732
+ 2. Call it:
733
+ ```python
734
+ result = execute_prompt("new_prompt", variables={"var": "value"})
735
+ ```
736
+
737
+ ### Add Structured Output
738
+
739
+ 1. Define model in `yamlgraph/models/schemas.py`:
740
+ ```python
741
+ class MyOutput(BaseModel):
742
+ field: str = Field(description="...")
743
+ ```
744
+
745
+ 2. Use with executor:
746
+ ```python
747
+ result = execute_prompt("prompt", output_model=MyOutput)
748
+ ```
749
+
750
+ ## Known Issues & Future Improvements
751
+
752
+ This project demonstrates solid production patterns with declarative YAML-based configuration.
753
+
754
+ ### Completed Features
755
+
756
+ | Feature | Status | Notes |
757
+ |---------|--------|-------|
758
+ | YAML Graph Configuration | ✅ | Declarative pipeline definition in `graphs/yamlgraph.yaml` |
759
+ | Jinja2 Templating | ✅ | Hybrid auto-detection (simple {var} + advanced Jinja2) |
760
+ | Multi-Provider LLMs | ✅ | Factory pattern supporting Anthropic/Mistral/OpenAI |
761
+ | Dynamic Node Generation | ✅ | Nodes compiled from YAML at runtime |
762
+
763
+ ### Implemented Patterns
764
+
765
+ | Feature | Status | Notes |
766
+ |---------|--------|-------|
767
+ | Branching/Routing | ✅ | `type: router` for LLM-based conditional routing |
768
+ | Self-Correction Loops | ✅ | Reflexion pattern with critique → refine cycles |
769
+ | Tool/Agent Patterns | ✅ | Shell tools + agent nodes with LangChain tool binding |
770
+ | Per-Node Error Handling | ✅ | `on_error: skip/retry/fail/fallback` |
771
+ | Conversation Memory | ✅ | Message accumulation via `AgentState.messages` |
772
+ | Native Checkpointing | ✅ | `SqliteSaver` from `langgraph-checkpoint-sqlite` |
773
+ | State Export | ✅ | JSON/Markdown export with `export_result()` |
774
+ | LangSmith Share Links | ✅ | Auto-generate public trace URLs after runs |
775
+
776
+ ### Missing LangGraph Features
777
+
778
+ | Feature | Status | Notes |
779
+ |---------|--------|-------|
780
+ | Fan-out/Fan-in | ✅ | `type: map` with `Send()` for item-level parallelism |
781
+ | Human-in-the-Loop | ❌ | No `interrupt_before` / `interrupt_after` demonstration |
782
+ | Streaming | ❌ | No streaming output support |
783
+ | Sub-graphs | ❌ | No nested graph composition |
784
+
785
+ ### Potential Enhancements
786
+
787
+ #### Short-term (Quick Wins)
788
+ 1. **Add `in` operator to conditions** - Support `status in ["done", "complete"]` expressions
789
+ 2. **Document agent `max_iterations`** - Expose in YAML schema for agent nodes
790
+ 3. **Add `--dry-run` flag** - Validate graph without execution
791
+
792
+ #### Medium-term (Feature Improvements)
793
+ 4. **Async map node execution** - Use `asyncio.gather()` for parallel branches
794
+ 5. **State field collision warnings** - Log when YAML fields override base fields
795
+ 6. **Map node error aggregation** - Summary with success/failure counts per branch
796
+ 7. **Add streaming** - `--stream` CLI flag for real-time output
797
+
798
+ #### Long-term (Architecture)
799
+ 8. **Plugin system** - Custom node types via entry points
800
+ 9. **Hot-reload for development** - File watcher for prompt/graph YAML changes
801
+ 10. **OpenTelemetry integration** - Complement LangSmith with standard observability
802
+ 11. **Sub-graphs** - Nested graph composition for complex workflows
803
+ 12. **Human-in-the-loop** - `interrupt_before` / `interrupt_after` demonstration
804
+
805
+ ## Security
806
+
807
+ ### Shell Command Injection Protection
808
+
809
+ Shell tools (defined in `graphs/*.yaml` with `type: tool`) execute commands with variable substitution. All user-provided variable values are sanitized using `shlex.quote()` to prevent shell injection attacks.
810
+
811
+ ```yaml
812
+ # In graph YAML - command template is trusted
813
+ tools:
814
+ git_log:
815
+ type: shell
816
+ command: "git log --author={author} -n {count}"
817
+ ```
818
+
819
+ **Security model:**
820
+ - ✅ **Command templates** (from YAML) are trusted configuration
821
+ - ✅ **Variable values** (from user input/LLM) are escaped with `shlex.quote()`
822
+ - ✅ **Complex types** (lists, dicts) are JSON-serialized then quoted
823
+ - ✅ **No `eval()`** - condition expressions parsed with regex, not evaluated
824
+
825
+ **Example protection:**
826
+ ```python
827
+ # Malicious input is safely escaped
828
+ variables = {"author": "$(rm -rf /)"}
829
+ # Executed as: git log --author='$(rm -rf /)' (quoted, harmless)
830
+ ```
831
+
832
+ See [yamlgraph/tools/shell.py](yamlgraph/tools/shell.py) for implementation details.
833
+
834
+ ### ⚠️ Security Considerations
835
+
836
+ **Shell tools execute real commands** on your system. While variables are sanitized:
837
+
838
+ 1. **Command templates are trusted** - Only use shell tools from trusted YAML configs
839
+ 2. **No sandboxing** - Commands run with your user permissions
840
+ 3. **Agent autonomy** - Agent nodes may call tools unpredictably
841
+ 4. **Review tool definitions** - Audit `tools:` section in graph YAML before running
842
+
843
+ For production deployments, consider:
844
+ - Running in a container with limited permissions
845
+ - Restricting available tools to read-only operations
846
+ - Implementing approval workflows for sensitive operations
847
+
848
+ ## License
849
+
850
+ MIT
851
+
852
+ ## Remember
853
+
854
+ Prompts in yaml templates, graphs in yaml, shared executor, pydantic, data stored in sqlite, langgraph, langsmith, venv, tdd red-green-refactor, modules < 400 lines, kiss