yamlgraph 0.3.9__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.
Files changed (185) hide show
  1. examples/__init__.py +1 -0
  2. examples/codegen/__init__.py +5 -0
  3. examples/codegen/models/__init__.py +13 -0
  4. examples/codegen/models/schemas.py +76 -0
  5. examples/codegen/tests/__init__.py +1 -0
  6. examples/codegen/tests/test_ai_helpers.py +235 -0
  7. examples/codegen/tests/test_ast_analysis.py +174 -0
  8. examples/codegen/tests/test_code_analysis.py +134 -0
  9. examples/codegen/tests/test_code_context.py +301 -0
  10. examples/codegen/tests/test_code_nav.py +89 -0
  11. examples/codegen/tests/test_dependency_tools.py +119 -0
  12. examples/codegen/tests/test_example_tools.py +185 -0
  13. examples/codegen/tests/test_git_tools.py +112 -0
  14. examples/codegen/tests/test_impl_agent_schemas.py +193 -0
  15. examples/codegen/tests/test_impl_agent_v4_graph.py +94 -0
  16. examples/codegen/tests/test_jedi_analysis.py +226 -0
  17. examples/codegen/tests/test_meta_tools.py +250 -0
  18. examples/codegen/tests/test_plan_discovery_prompt.py +98 -0
  19. examples/codegen/tests/test_syntax_tools.py +85 -0
  20. examples/codegen/tests/test_synthesize_prompt.py +94 -0
  21. examples/codegen/tests/test_template_tools.py +244 -0
  22. examples/codegen/tools/__init__.py +80 -0
  23. examples/codegen/tools/ai_helpers.py +420 -0
  24. examples/codegen/tools/ast_analysis.py +92 -0
  25. examples/codegen/tools/code_context.py +180 -0
  26. examples/codegen/tools/code_nav.py +52 -0
  27. examples/codegen/tools/dependency_tools.py +120 -0
  28. examples/codegen/tools/example_tools.py +188 -0
  29. examples/codegen/tools/git_tools.py +151 -0
  30. examples/codegen/tools/impl_executor.py +614 -0
  31. examples/codegen/tools/jedi_analysis.py +311 -0
  32. examples/codegen/tools/meta_tools.py +202 -0
  33. examples/codegen/tools/syntax_tools.py +26 -0
  34. examples/codegen/tools/template_tools.py +356 -0
  35. examples/fastapi_interview.py +167 -0
  36. examples/npc/api/__init__.py +1 -0
  37. examples/npc/api/app.py +100 -0
  38. examples/npc/api/routes/__init__.py +5 -0
  39. examples/npc/api/routes/encounter.py +182 -0
  40. examples/npc/api/session.py +330 -0
  41. examples/npc/demo.py +387 -0
  42. examples/npc/nodes/__init__.py +5 -0
  43. examples/npc/nodes/image_node.py +92 -0
  44. examples/npc/run_encounter.py +230 -0
  45. examples/shared/__init__.py +0 -0
  46. examples/shared/replicate_tool.py +238 -0
  47. examples/storyboard/__init__.py +1 -0
  48. examples/storyboard/generate_videos.py +335 -0
  49. examples/storyboard/nodes/__init__.py +12 -0
  50. examples/storyboard/nodes/animated_character_node.py +248 -0
  51. examples/storyboard/nodes/animated_image_node.py +138 -0
  52. examples/storyboard/nodes/character_node.py +162 -0
  53. examples/storyboard/nodes/image_node.py +118 -0
  54. examples/storyboard/nodes/replicate_tool.py +49 -0
  55. examples/storyboard/retry_images.py +118 -0
  56. scripts/demo_async_executor.py +212 -0
  57. scripts/demo_interview_e2e.py +200 -0
  58. scripts/demo_streaming.py +140 -0
  59. scripts/run_interview_demo.py +94 -0
  60. scripts/test_interrupt_fix.py +26 -0
  61. tests/__init__.py +1 -0
  62. tests/conftest.py +178 -0
  63. tests/integration/__init__.py +1 -0
  64. tests/integration/test_animated_storyboard.py +63 -0
  65. tests/integration/test_cli_commands.py +242 -0
  66. tests/integration/test_colocated_prompts.py +139 -0
  67. tests/integration/test_map_demo.py +50 -0
  68. tests/integration/test_memory_demo.py +283 -0
  69. tests/integration/test_npc_api/__init__.py +1 -0
  70. tests/integration/test_npc_api/test_routes.py +357 -0
  71. tests/integration/test_npc_api/test_session.py +216 -0
  72. tests/integration/test_pipeline_flow.py +105 -0
  73. tests/integration/test_providers.py +163 -0
  74. tests/integration/test_resume.py +75 -0
  75. tests/integration/test_subgraph_integration.py +295 -0
  76. tests/integration/test_subgraph_interrupt.py +106 -0
  77. tests/unit/__init__.py +1 -0
  78. tests/unit/test_agent_nodes.py +355 -0
  79. tests/unit/test_async_executor.py +346 -0
  80. tests/unit/test_checkpointer.py +212 -0
  81. tests/unit/test_checkpointer_factory.py +212 -0
  82. tests/unit/test_cli.py +121 -0
  83. tests/unit/test_cli_package.py +81 -0
  84. tests/unit/test_compile_graph_map.py +132 -0
  85. tests/unit/test_conditions_routing.py +253 -0
  86. tests/unit/test_config.py +93 -0
  87. tests/unit/test_conversation_memory.py +276 -0
  88. tests/unit/test_database.py +145 -0
  89. tests/unit/test_deprecation.py +104 -0
  90. tests/unit/test_executor.py +172 -0
  91. tests/unit/test_executor_async.py +179 -0
  92. tests/unit/test_export.py +149 -0
  93. tests/unit/test_expressions.py +178 -0
  94. tests/unit/test_feature_brainstorm.py +194 -0
  95. tests/unit/test_format_prompt.py +145 -0
  96. tests/unit/test_generic_report.py +200 -0
  97. tests/unit/test_graph_commands.py +327 -0
  98. tests/unit/test_graph_linter.py +627 -0
  99. tests/unit/test_graph_loader.py +357 -0
  100. tests/unit/test_graph_schema.py +193 -0
  101. tests/unit/test_inline_schema.py +151 -0
  102. tests/unit/test_interrupt_node.py +182 -0
  103. tests/unit/test_issues.py +164 -0
  104. tests/unit/test_jinja2_prompts.py +85 -0
  105. tests/unit/test_json_extract.py +134 -0
  106. tests/unit/test_langsmith.py +600 -0
  107. tests/unit/test_langsmith_tools.py +204 -0
  108. tests/unit/test_llm_factory.py +109 -0
  109. tests/unit/test_llm_factory_async.py +118 -0
  110. tests/unit/test_loops.py +403 -0
  111. tests/unit/test_map_node.py +144 -0
  112. tests/unit/test_no_backward_compat.py +56 -0
  113. tests/unit/test_node_factory.py +348 -0
  114. tests/unit/test_passthrough_node.py +126 -0
  115. tests/unit/test_prompts.py +324 -0
  116. tests/unit/test_python_nodes.py +198 -0
  117. tests/unit/test_reliability.py +298 -0
  118. tests/unit/test_result_export.py +234 -0
  119. tests/unit/test_router.py +296 -0
  120. tests/unit/test_sanitize.py +99 -0
  121. tests/unit/test_schema_loader.py +295 -0
  122. tests/unit/test_shell_tools.py +229 -0
  123. tests/unit/test_state_builder.py +331 -0
  124. tests/unit/test_state_builder_map.py +104 -0
  125. tests/unit/test_state_config.py +197 -0
  126. tests/unit/test_streaming.py +307 -0
  127. tests/unit/test_subgraph.py +596 -0
  128. tests/unit/test_template.py +190 -0
  129. tests/unit/test_tool_call_integration.py +164 -0
  130. tests/unit/test_tool_call_node.py +178 -0
  131. tests/unit/test_tool_nodes.py +129 -0
  132. tests/unit/test_websearch.py +234 -0
  133. yamlgraph/__init__.py +35 -0
  134. yamlgraph/builder.py +110 -0
  135. yamlgraph/cli/__init__.py +159 -0
  136. yamlgraph/cli/__main__.py +6 -0
  137. yamlgraph/cli/commands.py +231 -0
  138. yamlgraph/cli/deprecation.py +92 -0
  139. yamlgraph/cli/graph_commands.py +541 -0
  140. yamlgraph/cli/validators.py +37 -0
  141. yamlgraph/config.py +67 -0
  142. yamlgraph/constants.py +70 -0
  143. yamlgraph/error_handlers.py +227 -0
  144. yamlgraph/executor.py +290 -0
  145. yamlgraph/executor_async.py +288 -0
  146. yamlgraph/graph_loader.py +451 -0
  147. yamlgraph/map_compiler.py +150 -0
  148. yamlgraph/models/__init__.py +36 -0
  149. yamlgraph/models/graph_schema.py +181 -0
  150. yamlgraph/models/schemas.py +124 -0
  151. yamlgraph/models/state_builder.py +236 -0
  152. yamlgraph/node_factory.py +768 -0
  153. yamlgraph/routing.py +87 -0
  154. yamlgraph/schema_loader.py +240 -0
  155. yamlgraph/storage/__init__.py +20 -0
  156. yamlgraph/storage/checkpointer.py +72 -0
  157. yamlgraph/storage/checkpointer_factory.py +123 -0
  158. yamlgraph/storage/database.py +320 -0
  159. yamlgraph/storage/export.py +269 -0
  160. yamlgraph/tools/__init__.py +1 -0
  161. yamlgraph/tools/agent.py +320 -0
  162. yamlgraph/tools/graph_linter.py +388 -0
  163. yamlgraph/tools/langsmith_tools.py +125 -0
  164. yamlgraph/tools/nodes.py +126 -0
  165. yamlgraph/tools/python_tool.py +179 -0
  166. yamlgraph/tools/shell.py +205 -0
  167. yamlgraph/tools/websearch.py +242 -0
  168. yamlgraph/utils/__init__.py +48 -0
  169. yamlgraph/utils/conditions.py +157 -0
  170. yamlgraph/utils/expressions.py +245 -0
  171. yamlgraph/utils/json_extract.py +104 -0
  172. yamlgraph/utils/langsmith.py +416 -0
  173. yamlgraph/utils/llm_factory.py +118 -0
  174. yamlgraph/utils/llm_factory_async.py +105 -0
  175. yamlgraph/utils/logging.py +104 -0
  176. yamlgraph/utils/prompts.py +171 -0
  177. yamlgraph/utils/sanitize.py +98 -0
  178. yamlgraph/utils/template.py +102 -0
  179. yamlgraph/utils/validators.py +181 -0
  180. yamlgraph-0.3.9.dist-info/METADATA +1105 -0
  181. yamlgraph-0.3.9.dist-info/RECORD +185 -0
  182. yamlgraph-0.3.9.dist-info/WHEEL +5 -0
  183. yamlgraph-0.3.9.dist-info/entry_points.txt +2 -0
  184. yamlgraph-0.3.9.dist-info/licenses/LICENSE +33 -0
  185. yamlgraph-0.3.9.dist-info/top_level.txt +4 -0
@@ -0,0 +1,1105 @@
1
+ Metadata-Version: 2.4
2
+ Name: yamlgraph
3
+ Version: 0.3.9
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: analysis
25
+ Requires-Dist: jedi>=0.19.0; extra == "analysis"
26
+ Provides-Extra: websearch
27
+ Requires-Dist: ddgs>=6.0.0; extra == "websearch"
28
+ Provides-Extra: storyboard
29
+ Requires-Dist: replicate>=0.25.0; extra == "storyboard"
30
+ Provides-Extra: redis
31
+ Requires-Dist: langgraph-checkpoint-redis>=0.3.0; extra == "redis"
32
+ Dynamic: license-file
33
+
34
+ # YamlGraph
35
+
36
+ [![PyPI version](https://badge.fury.io/py/yamlgraph.svg)](https://pypi.org/project/yamlgraph/)
37
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
+
40
+ A YAML-first framework for building LLM pipelines using:
41
+
42
+ - **YAML Graph Configuration** - Declarative pipeline definition with schema validation
43
+ - **YAML Prompts** - Declarative prompt templates with Jinja2 support
44
+ - **Pydantic Models** - Structured LLM outputs
45
+ - **Multi-Provider LLMs** - Support for Anthropic, Mistral, and OpenAI
46
+ - **LangGraph** - Pipeline orchestration with resume support
47
+ - **Human-in-the-Loop** - Interrupt nodes for user input
48
+ - **Streaming** - Token-by-token LLM output
49
+ - **Async Support** - FastAPI-ready async execution
50
+ - **Checkpointers** - Memory, SQLite, and Redis state persistence
51
+ - **Graph-Relative Prompts** - Colocate prompts with graphs
52
+ - **JSON Extraction** - Auto-extract JSON from LLM responses
53
+ - **LangSmith** - Observability and tracing
54
+ - **JSON Export** - Result serialization
55
+
56
+ ## Installation
57
+
58
+ ### From PyPI
59
+
60
+ ```bash
61
+ pip install yamlgraph
62
+
63
+ # With Redis support for distributed checkpointing
64
+ pip install yamlgraph[redis]
65
+ ```
66
+
67
+ ### From Source
68
+
69
+ ```bash
70
+ git clone https://github.com/sheikkinen/yamlgraph.git
71
+ cd yamlgraph
72
+ pip install -e ".[dev]"
73
+ ```
74
+
75
+ ## Quick Start
76
+
77
+ ### 1. Create a Prompt
78
+
79
+ Create `prompts/greet.yaml`:
80
+
81
+ ```yaml
82
+ system: |
83
+ You are a friendly assistant.
84
+
85
+ user: |
86
+ Say hello to {name} in a {style} way.
87
+ ```
88
+
89
+ ### 2. Create a Graph
90
+
91
+ Create `graphs/hello.yaml`:
92
+
93
+ ```yaml
94
+ version: "1.0"
95
+ name: hello-world
96
+
97
+ nodes:
98
+ greet:
99
+ type: llm
100
+ prompt: greet
101
+ variables:
102
+ name: "{state.name}"
103
+ style: "{state.style}"
104
+ state_key: greeting
105
+
106
+ edges:
107
+ - from: START
108
+ to: greet
109
+ - from: greet
110
+ to: END
111
+ ```
112
+
113
+ ### 3. Set API Key
114
+
115
+ ```bash
116
+ export ANTHROPIC_API_KEY=your-key-here
117
+ # Or: export MISTRAL_API_KEY=... or OPENAI_API_KEY=...
118
+ ```
119
+
120
+ ### 4. Run It
121
+
122
+ ```bash
123
+ yamlgraph graph run graphs/hello.yaml --var name="World" --var style="enthusiastic"
124
+ ```
125
+
126
+ Or use the Python API:
127
+
128
+ ```python
129
+ from yamlgraph.graph_loader import load_and_compile
130
+
131
+ graph = load_and_compile("graphs/hello.yaml")
132
+ app = graph.compile()
133
+ result = app.invoke({"name": "World", "style": "enthusiastic"})
134
+ print(result["greeting"])
135
+ ```
136
+
137
+ ---
138
+
139
+ ## More Examples
140
+
141
+ ```bash
142
+ # Content generation pipeline
143
+ yamlgraph graph run graphs/yamlgraph.yaml --var topic="AI" --var style=casual
144
+
145
+ # Sentiment-based routing
146
+ yamlgraph graph run graphs/router-demo.yaml --var message="I love this!"
147
+
148
+ # Self-correction loop (Reflexion pattern)
149
+ yamlgraph graph run graphs/reflexion-demo.yaml --var topic="climate change"
150
+
151
+ # AI agent with shell tools
152
+ yamlgraph graph run graphs/git-report.yaml --var input="What changed recently?"
153
+
154
+ # Web research agent (requires: pip install yamlgraph[websearch])
155
+ yamlgraph graph run graphs/web-research.yaml --var topic="LangGraph tutorials"
156
+
157
+ # Code quality analysis with shell tools
158
+ yamlgraph graph run graphs/code-analysis.yaml --var path="yamlgraph" --var package="yamlgraph"
159
+
160
+ # Implementation agent - analyze code and generate plans
161
+ yamlgraph graph run examples/codegen/impl-agent.yaml \
162
+ --var 'story=Add timeout to websearch' --var scope=yamlgraph/tools
163
+
164
+ # Meta: YAMLGraph brainstorms its own features
165
+ yamlgraph graph run graphs/feature-brainstorm.yaml --var focus="tools"
166
+
167
+ # Parallel fan-out with map nodes
168
+ yamlgraph graph run examples/storyboard/animated-character-graph.yaml \
169
+ --var concept="A brave mouse knight" --var model=hidream
170
+ ```
171
+
172
+ ### Human-in-the-Loop (Interrupt Nodes)
173
+
174
+ Create interactive workflows that pause for user input:
175
+
176
+ ```yaml
177
+ # graphs/interview.yaml
178
+ checkpointer:
179
+ type: memory
180
+
181
+ nodes:
182
+ ask_name:
183
+ type: interrupt
184
+ message: "What is your name?"
185
+ resume_key: user_name
186
+
187
+ greet:
188
+ type: llm
189
+ prompt: greet
190
+ variables:
191
+ name: "{state.user_name}"
192
+ ```
193
+
194
+ ```python
195
+ from langgraph.types import Command
196
+ from yamlgraph.executor_async import load_and_compile_async, run_graph_async
197
+
198
+ app = await load_and_compile_async("graphs/interview.yaml")
199
+ config = {"configurable": {"thread_id": "session-1"}}
200
+
201
+ result = await run_graph_async(app, {}, config)
202
+ # result["__interrupt__"] contains the question
203
+
204
+ result = await run_graph_async(app, Command(resume="Alice"), config)
205
+ # result["greeting"] contains personalized response
206
+ ```
207
+
208
+ ### Streaming
209
+
210
+ Token-by-token LLM output for real-time UX:
211
+
212
+ ```python
213
+ from yamlgraph.executor_async import execute_prompt_streaming
214
+
215
+ async for token in execute_prompt_streaming("greet", {"name": "World"}):
216
+ print(token, end="", flush=True)
217
+ ```
218
+
219
+ Or in YAML nodes:
220
+
221
+ ```yaml
222
+ nodes:
223
+ generate:
224
+ type: llm
225
+ prompt: story
226
+ stream: true
227
+ ```
228
+
229
+ ### Graph-Relative Prompts
230
+
231
+ Colocate prompts with graphs for cleaner project structures:
232
+
233
+ ```yaml
234
+ # questionnaires/audit/graph.yaml
235
+ defaults:
236
+ prompts_relative: true # Resolve prompts from graph directory
237
+
238
+ nodes:
239
+ opening:
240
+ type: llm
241
+ prompt: prompts/opening # → questionnaires/audit/prompts/opening.yaml
242
+ ```
243
+
244
+ ### JSON Extraction
245
+
246
+ Auto-extract JSON from LLM responses wrapped in markdown:
247
+
248
+ ```yaml
249
+ nodes:
250
+ extract:
251
+ type: llm
252
+ prompt: extract_fields
253
+ state_key: data
254
+ parse_json: true # {"key": "value"} instead of "```json..."
255
+ ```
256
+
257
+ ### CLI Utilities
258
+
259
+ ```bash
260
+ yamlgraph graph list # List available graphs
261
+ yamlgraph graph info graphs/router-demo.yaml # Show graph structure
262
+ yamlgraph graph validate graphs/*.yaml # Validate graph schemas
263
+ yamlgraph graph lint graphs/*.yaml # Lint graphs for common issues
264
+ yamlgraph list-runs # View recent runs
265
+ yamlgraph resume --thread-id abc123 # Resume a run
266
+ yamlgraph export --thread-id abc123 # Export run to JSON
267
+
268
+ # Observability (requires LangSmith)
269
+ yamlgraph trace --verbose # View execution trace
270
+ yamlgraph mermaid # Show pipeline as Mermaid diagram
271
+ ```
272
+
273
+ ## Documentation
274
+
275
+ 📚 **Start here:** [reference/README.md](reference/README.md) - Complete index of all 18 reference docs
276
+
277
+ ### Reading Order
278
+
279
+ | Level | Document | Description |
280
+ |-------|----------|-------------|
281
+ | 🟢 Beginner | [Quick Start](reference/quickstart.md) | Create your first pipeline in 5 minutes |
282
+ | 🟢 Beginner | [Graph YAML](reference/graph-yaml.md) | Node types, edges, tools, state |
283
+ | 🟢 Beginner | [Prompt YAML](reference/prompt-yaml.md) | Schema and template syntax |
284
+ | 🟡 Intermediate | [Common Patterns](reference/patterns.md) | Router, loops, agents |
285
+ | 🟡 Intermediate | [Map Nodes](reference/map-nodes.md) | Parallel fan-out processing |
286
+ | 🟡 Intermediate | [Interrupt Nodes](reference/interrupt-nodes.md) | Human-in-the-loop |
287
+ | 🔴 Advanced | [Subgraph Nodes](reference/subgraph-nodes.md) | Modular graph composition |
288
+ | 🔴 Advanced | [Async Usage](reference/async-usage.md) | FastAPI integration |
289
+ | 🔴 Advanced | [Checkpointers](reference/checkpointers.md) | State persistence |
290
+
291
+ ## Architecture
292
+
293
+ 🏗️ **For core developers:** See [ARCHITECTURE.md](ARCHITECTURE.md) for internal design, extension points, and contribution guidelines.
294
+
295
+ ### Data Flow
296
+
297
+ ```mermaid
298
+ flowchart TB
299
+ subgraph Input["📥 Input Layer"]
300
+ CLI["CLI Command"]
301
+ YAML_G["graphs/*.yaml"]
302
+ YAML_P["prompts/*.yaml"]
303
+ end
304
+
305
+ subgraph Core["⚙️ Core Processing"]
306
+ GL["graph_loader.py<br/>YAML → StateGraph"]
307
+ NF["node_factory.py<br/>Create Node Functions"]
308
+ EH["error_handlers.py<br/>Skip/Retry/Fail/Fallback"]
309
+ EX["executor.py<br/>Prompt Execution"]
310
+ end
311
+
312
+ subgraph LLM["🤖 LLM Layer"]
313
+ LF["llm_factory.py"]
314
+ ANT["Anthropic"]
315
+ MIS["Mistral"]
316
+ OAI["OpenAI"]
317
+ end
318
+
319
+ subgraph State["💾 State Layer"]
320
+ SB["state_builder.py<br/>Dynamic TypedDict"]
321
+ CP["checkpointer.py<br/>SQLite Persistence"]
322
+ DB[(SQLite DB)]
323
+ end
324
+
325
+ subgraph Output["📤 Output Layer"]
326
+ EXP["export.py"]
327
+ JSON["JSON Export"]
328
+ LS["LangSmith Traces"]
329
+ end
330
+
331
+ CLI --> GL
332
+ YAML_G --> GL
333
+ YAML_P --> EX
334
+ GL --> NF
335
+ NF --> EH
336
+ EH --> EX
337
+ EX --> LF
338
+ LF --> ANT & MIS & OAI
339
+ GL --> SB
340
+ SB --> CP
341
+ CP --> DB
342
+ EX --> EXP
343
+ EXP --> JSON
344
+ EX --> LS
345
+ ```
346
+
347
+ ### Directory Structure
348
+
349
+ ```
350
+ yamlgraph/
351
+ ├── README.md
352
+ ├── pyproject.toml # Package definition with CLI entry point and dependencies
353
+ ├── .env.sample # Environment template
354
+
355
+ ├── graphs/ # YAML graph definitions
356
+ │ ├── yamlgraph.yaml # Main pipeline definition
357
+ │ ├── router-demo.yaml # Tone-based routing demo
358
+ │ ├── reflexion-demo.yaml # Self-refinement loop demo
359
+ │ └── git-report.yaml # AI agent demo with shell tools
360
+
361
+ ├── yamlgraph/ # Main package
362
+ │ ├── __init__.py # Package exports
363
+ │ ├── builder.py # Graph builders (loads from YAML)
364
+ │ ├── graph_loader.py # YAML → LangGraph compiler
365
+ │ ├── config.py # Centralized configuration
366
+ │ ├── executor.py # YAML prompt executor
367
+ │ ├── cli.py # CLI commands
368
+ │ │
369
+ │ ├── models/ # Pydantic models
370
+ │ │ ├── __init__.py
371
+ │ │ ├── schemas.py # Framework schemas (ErrorType, PipelineError, GenericReport)
372
+ │ │ ├── state_builder.py # Dynamic state generation from YAML
373
+ │ │ └── graph_schema.py # Pydantic schema validation
374
+ │ │
375
+ │ ├── tools/ # Tool execution
376
+ │ │ ├── __init__.py
377
+ │ │ ├── shell.py # Shell command executor
378
+ │ │ ├── nodes.py # Tool node factory
379
+ │ │ └── agent.py # Agent node factory
380
+ │ │
381
+ │ ├── storage/ # Persistence layer
382
+ │ │ ├── __init__.py
383
+ │ │ ├── database.py # SQLite wrapper
384
+ │ │ └── export.py # JSON export
385
+ │ │
386
+ │ └── utils/ # Utilities
387
+ │ ├── __init__.py
388
+ │ ├── llm_factory.py # Multi-provider LLM creation
389
+ │ └── langsmith.py # Tracing helpers
390
+
391
+ ├── prompts/ # YAML prompt templates
392
+ │ ├── greet.yaml
393
+ │ ├── analyze.yaml
394
+ │ ├── analyze_list.yaml # Jinja2 example with loops/filters
395
+ │ ├── generate.yaml
396
+ │ ├── summarize.yaml
397
+ │ └── router-demo/ # Tone routing prompts
398
+ │ ├── classify_tone.yaml
399
+ │ ├── respond_positive.yaml
400
+ │ ├── respond_negative.yaml
401
+ │ └── respond_neutral.yaml
402
+
403
+ ├── reference/ # YAML configuration reference docs
404
+ │ ├── README.md # Overview and key concepts
405
+ │ ├── quickstart.md # 5-minute getting started guide
406
+ │ ├── graph-yaml.md # Graph YAML reference
407
+ │ ├── prompt-yaml.md # Prompt YAML reference
408
+ │ └── patterns.md # Common patterns and examples
409
+
410
+ ├── tests/ # Test suite
411
+ │ ├── conftest.py # Shared fixtures
412
+ │ ├── unit/ # Unit tests
413
+ │ └── integration/ # Integration tests
414
+
415
+ └── outputs/ # Generated files (gitignored)
416
+ ```
417
+ ```
418
+
419
+ ## Pipeline Flow
420
+
421
+ ```mermaid
422
+ graph TD
423
+ A["📝 generate"] -->|content| B{should_continue}
424
+ B -->|"✓ content exists"| C["🔍 analyze"]
425
+ B -->|"✗ error/empty"| F["🛑 END"]
426
+ C -->|analysis| D["📊 summarize"]
427
+ D -->|final_summary| F
428
+
429
+ style A fill:#e1f5fe
430
+ style C fill:#fff3e0
431
+ style D fill:#e8f5e9
432
+ style F fill:#fce4ec
433
+ ```
434
+
435
+ ### Node Outputs
436
+
437
+ | Node | Output Type | Description |
438
+ |------|-------------|-------------|
439
+ | `generate` | Inline schema | Title, content, word_count, tags |
440
+ | `analyze` | Inline schema | Summary, key_points, sentiment, confidence |
441
+ | `summarize` | `str` | Final combined summary |
442
+
443
+ Output schemas are defined inline in YAML prompt files using the `schema:` block.
444
+
445
+ ### Resume Flow
446
+
447
+ Pipelines can be resumed from any checkpoint. The resume behavior uses `skip_if_exists`:
448
+ nodes check if their output already exists in state and skip LLM calls if so.
449
+
450
+ ```mermaid
451
+ graph LR
452
+ subgraph "Resume after 'analyze' completed"
453
+ A1["Load State"] --> B1["analyze (skipped)"] --> C1["summarize"] --> D1["END"]
454
+ end
455
+ ```
456
+
457
+ ```bash
458
+ # Resume an interrupted run
459
+ yamlgraph resume --thread-id abc123
460
+ ```
461
+
462
+ When resumed:
463
+ - Nodes with existing outputs are **skipped** (no duplicate LLM calls)
464
+ - Only nodes without outputs in state actually run
465
+ - State is preserved via SQLite checkpointing
466
+
467
+ ## Key Patterns
468
+
469
+ ### 1. YAML Prompt Templates
470
+
471
+ **Simple Templating (Basic Substitution)**:
472
+ ```yaml
473
+ # prompts/generate.yaml
474
+ system: |
475
+ You are a creative content writer...
476
+
477
+ user: |
478
+ Write about: {topic}
479
+ Target length: approximately {word_count} words
480
+ ```
481
+
482
+ **Advanced Templating (Jinja2)**:
483
+ ```yaml
484
+ # prompts/analyze_list.yaml
485
+ template: |
486
+ Analyze the following {{ items|length }} items:
487
+
488
+ {% for item in items %}
489
+ ### {{ loop.index }}. {{ item.title }}
490
+ Topic: {{ item.topic }}
491
+ {% if item.tags %}
492
+ Tags: {{ item.tags | join(", ") }}
493
+ {% endif %}
494
+ {% endfor %}
495
+ ```
496
+
497
+ **Template Features**:
498
+ - **Auto-detection**: Uses Jinja2 if `{{` or `{%` present, otherwise simple formatting
499
+ - **Loops**: `{% for item in items %}...{% endfor %}`
500
+ - **Conditionals**: `{% if condition %}...{% endif %}`
501
+ - **Filters**: `{{ text[:50] }}`, `{{ items | join(", ") }}`, `{{ name | upper }}`
502
+ - **Backward compatible**: Existing `{variable}` prompts work unchanged
503
+
504
+ ### 2. Structured Executor
505
+
506
+ ```python
507
+ from yamlgraph.executor import execute_prompt
508
+ from yamlgraph.models import GenericReport
509
+
510
+ result = execute_prompt(
511
+ "generate",
512
+ variables={"topic": "AI", "word_count": 300},
513
+ output_model=GenericReport,
514
+ )
515
+ print(result.title) # Typed access!
516
+ ```
517
+
518
+ ### 3. Multi-Provider LLM Support
519
+
520
+ ```python
521
+ from yamlgraph.executor import execute_prompt
522
+
523
+ # Use default provider (Anthropic)
524
+ result = execute_prompt(
525
+ "greet",
526
+ variables={"name": "Alice", "style": "formal"},
527
+ )
528
+
529
+ # Switch to Mistral
530
+ result = execute_prompt(
531
+ "greet",
532
+ variables={"name": "Bob", "style": "casual"},
533
+ provider="mistral",
534
+ )
535
+
536
+ # Or set via environment variable
537
+ # PROVIDER=openai yamlgraph graph run ...
538
+ ```
539
+
540
+ Supported providers:
541
+ - **Anthropic** (default): Claude models
542
+ - **Mistral**: Mistral Large and other models
543
+ - **OpenAI**: GPT-4 and other models
544
+
545
+ Provider selection priority:
546
+ 1. Function parameter: `execute_prompt(..., provider="mistral")`
547
+ 2. YAML metadata: `provider: mistral` in prompt file
548
+ 3. Environment variable: `PROVIDER=mistral`
549
+ 4. Default: `anthropic`
550
+
551
+ ### 4. YAML Graph Configuration
552
+
553
+ Pipelines are defined declaratively in YAML and compiled to LangGraph:
554
+
555
+ ```yaml
556
+ # graphs/yamlgraph.yaml
557
+ version: "1.0"
558
+ name: yamlgraph-demo
559
+ description: Content generation pipeline
560
+
561
+ defaults:
562
+ provider: mistral
563
+ temperature: 0.7
564
+
565
+ nodes:
566
+ generate:
567
+ type: llm
568
+ prompt: generate
569
+ output_schema: # Inline schema - no Python model needed!
570
+ title: str
571
+ content: str
572
+ word_count: int
573
+ tags: list[str]
574
+ temperature: 0.8
575
+ variables:
576
+ topic: "{state.topic}"
577
+ word_count: "{state.word_count}"
578
+ style: "{state.style}"
579
+ state_key: generated
580
+
581
+ analyze:
582
+ type: llm
583
+ prompt: analyze
584
+ output_schema: # Inline schema
585
+ summary: str
586
+ key_points: list[str]
587
+ sentiment: str
588
+ confidence: float
589
+ temperature: 0.3
590
+ variables:
591
+ content: "{state.generated.content}"
592
+ state_key: analysis
593
+ requires: [generated]
594
+
595
+ summarize:
596
+ type: llm
597
+ prompt: summarize
598
+ temperature: 0.5
599
+ state_key: final_summary
600
+ requires: [generated, analysis]
601
+
602
+ edges:
603
+ - from: START
604
+ to: generate
605
+ - from: generate
606
+ to: analyze
607
+ condition: continue
608
+ - from: generate
609
+ to: END
610
+ condition: end
611
+ - from: analyze
612
+ to: summarize
613
+ - from: summarize
614
+ to: END
615
+ ```
616
+
617
+ **Load and run**:
618
+ ```python
619
+ from yamlgraph.builder import build_graph
620
+
621
+ graph = build_graph().compile() # Loads from graphs/yamlgraph.yaml
622
+ result = graph.invoke(initial_state)
623
+ ```
624
+
625
+ ### 5. State Persistence
626
+
627
+ ```python
628
+ from yamlgraph.storage import YamlGraphDB
629
+
630
+ db = YamlGraphDB()
631
+ db.save_state("thread-123", state)
632
+ state = db.load_state("thread-123")
633
+ ```
634
+
635
+ ### 6. LangSmith Tracing
636
+
637
+ ```python
638
+ from yamlgraph.utils.langsmith import print_run_tree
639
+
640
+ print_run_tree(verbose=True)
641
+ # 📊 Execution Tree:
642
+ # └─ yamlgraph_pipeline (12.3s) ✅
643
+ # ├─ generate (5.2s) ✅
644
+ # ├─ analyze (3.1s) ✅
645
+ # └─ summarize (4.0s) ✅
646
+ ```
647
+
648
+ #### Self-Correcting Pipelines
649
+
650
+ Use LangSmith tools to let agents inspect previous runs and fix errors:
651
+
652
+ ```python
653
+ from yamlgraph.utils.langsmith import get_run_details, get_run_errors, get_failed_runs
654
+
655
+ # Get details of the last run
656
+ details = get_run_details() # or get_run_details("specific-run-id")
657
+ print(details["status"]) # "success" or "error"
658
+
659
+ # Get all errors from a run and its child nodes
660
+ errors = get_run_errors()
661
+ for e in errors:
662
+ print(f"{e['node']}: {e['error']}")
663
+
664
+ # List recent failed runs
665
+ failures = get_failed_runs(limit=5)
666
+ ```
667
+
668
+ As agent tools (see [reference/langsmith-tools.md](reference/langsmith-tools.md)):
669
+
670
+ ```yaml
671
+ tools:
672
+ check_last_run:
673
+ type: python
674
+ module: yamlgraph.tools.langsmith_tools
675
+ function: get_run_details_tool
676
+ description: "Get status and errors from the last pipeline run"
677
+
678
+ get_errors:
679
+ type: python
680
+ module: yamlgraph.tools.langsmith_tools
681
+ function: get_run_errors_tool
682
+ description: "Get detailed error info from a run"
683
+
684
+ nodes:
685
+ self_correct:
686
+ type: agent
687
+ prompt: error_analyzer
688
+ tools: [check_last_run, get_errors]
689
+ max_iterations: 3
690
+ ```
691
+
692
+ ### 7. Shell Tools & Agent Nodes
693
+
694
+ Define shell tools and let the LLM decide when to use them:
695
+
696
+ ```yaml
697
+ # graphs/git-report.yaml
698
+ tools:
699
+ recent_commits:
700
+ type: shell
701
+ command: git log --oneline -n {count}
702
+ description: "List recent commits"
703
+
704
+ changed_files:
705
+ type: shell
706
+ command: git diff --name-only HEAD~{n}
707
+ description: "List files changed in last n commits"
708
+
709
+ nodes:
710
+ analyze:
711
+ type: agent # LLM decides which tools to call
712
+ prompt: git_analyst
713
+ tools: [recent_commits, changed_files]
714
+ max_iterations: 8
715
+ state_key: analysis
716
+ ```
717
+
718
+ Run the git analysis agent:
719
+
720
+ ```bash
721
+ yamlgraph git-report -q "What changed recently?"
722
+ yamlgraph git-report -q "Summarize the test directory"
723
+ ```
724
+
725
+ ### 8. Web Search Tools
726
+
727
+ Enable agents to search the web using DuckDuckGo (no API key required):
728
+
729
+ ```bash
730
+ pip install yamlgraph[websearch]
731
+ ```
732
+
733
+ ```yaml
734
+ # graphs/web-research.yaml
735
+ tools:
736
+ search_web:
737
+ type: websearch
738
+ provider: duckduckgo
739
+ max_results: 5
740
+ description: "Search the web for current information"
741
+
742
+ nodes:
743
+ research:
744
+ type: agent
745
+ prompt: web-research/researcher
746
+ tools: [search_web]
747
+ max_iterations: 5
748
+ state_key: research
749
+ ```
750
+
751
+ Run web research:
752
+
753
+ ```bash
754
+ yamlgraph graph run graphs/web-research.yaml --var topic="LangGraph tutorials"
755
+ ```
756
+
757
+ ### 9. Code Quality Analysis
758
+
759
+ Run automated code analysis with shell-based quality tools:
760
+
761
+ ```yaml
762
+ # graphs/code-analysis.yaml
763
+ state:
764
+ path: str # Directory to analyze
765
+ package: str # Package name for coverage
766
+
767
+ tools:
768
+ run_ruff:
769
+ type: shell
770
+ command: ruff check {path} --output-format=text 2>&1
771
+ description: "Run ruff linter for code style issues"
772
+
773
+ run_tests:
774
+ type: shell
775
+ command: python -m pytest {path} -q --tb=no 2>&1 | tail -10
776
+ description: "Run pytest"
777
+
778
+ run_bandit:
779
+ type: shell
780
+ command: bandit -r {path} -ll -q 2>&1
781
+ description: "Security vulnerability scanner"
782
+
783
+ nodes:
784
+ run_analysis:
785
+ type: agent
786
+ prompt: code-analysis/analyzer
787
+ tools: [run_ruff, run_tests, run_coverage, run_bandit, run_radon, run_vulture]
788
+ max_iterations: 12
789
+ state_key: analysis_results
790
+
791
+ generate_recommendations:
792
+ type: llm
793
+ prompt: code-analysis/recommend
794
+ requires: [analysis_results]
795
+ state_key: recommendations
796
+ ```
797
+
798
+ Run code analysis (yamlgraph analyzes itself!):
799
+
800
+ ```bash
801
+ yamlgraph graph run graphs/code-analysis.yaml --var path="yamlgraph" --var package="yamlgraph"
802
+ ```
803
+
804
+ **Tool types:**
805
+ - `type: shell` - Execute shell commands with variable substitution
806
+ - `type: websearch` - Web search via DuckDuckGo (provider: duckduckgo)
807
+ - `type: python` - Execute custom Python functions
808
+
809
+ **Node types:**
810
+ - `type: llm` - Standard LLM call with structured output
811
+ - `type: router` - Classify and route to different paths
812
+ - `type: map` - Parallel fan-out over lists with `Send()`
813
+ - `type: python` - Execute custom Python functions
814
+ - `type: agent` - LLM loop that autonomously calls tools
815
+
816
+ ## Environment Variables
817
+
818
+ | Variable | Required | Description |
819
+ |----------|----------|-------------|
820
+ | `ANTHROPIC_API_KEY` | Yes* | Anthropic API key (* if using Anthropic) |
821
+ | `MISTRAL_API_KEY` | No | Mistral API key (required if using Mistral) |
822
+ | `OPENAI_API_KEY` | No | OpenAI API key (required if using OpenAI) |
823
+ | `PROVIDER` | No | Default LLM provider (anthropic/mistral/openai) |
824
+ | `ANTHROPIC_MODEL` | No | Anthropic model (default: claude-sonnet-4-20250514) |
825
+ | `MISTRAL_MODEL` | No | Mistral model (default: mistral-large-latest) |
826
+ | `OPENAI_MODEL` | No | OpenAI model (default: gpt-4o) |
827
+ | `LANGCHAIN_TRACING` | No | Enable LangSmith tracing |
828
+ | `LANGCHAIN_API_KEY` | No | LangSmith API key |
829
+ | `LANGCHAIN_ENDPOINT` | No | LangSmith endpoint URL |
830
+ | `LANGCHAIN_PROJECT` | No | LangSmith project name |
831
+
832
+ ## Testing
833
+
834
+ Run the test suite:
835
+
836
+ ```bash
837
+ # Run all tests
838
+ pytest tests/ -v
839
+
840
+ # Run only unit tests
841
+ pytest tests/unit/ -v
842
+
843
+ # Run only integration tests
844
+ pytest tests/integration/ -v
845
+
846
+ # Run with coverage report
847
+ pytest tests/ --cov=yamlgraph --cov-report=term-missing
848
+
849
+ # Run with HTML coverage report
850
+ pytest tests/ --cov=yamlgraph --cov-report=html
851
+ # Then open htmlcov/index.html
852
+ ```
853
+
854
+ **Current coverage**: 60% overall, 98% on graph_loader, 100% on builder/llm_factory.
855
+
856
+ ## Extending the Pipeline
857
+
858
+ ### Adding a New Node (YAML-First Approach)
859
+
860
+ Let's add a "fact_check" node that verifies generated content:
861
+
862
+ **Step 1: Define the output schema** (`yamlgraph/models/schemas.py`):
863
+ ```python
864
+ class FactCheck(BaseModel):
865
+ """Structured fact-checking output."""
866
+
867
+ claims: list[str] = Field(description="Claims identified in content")
868
+ verified: bool = Field(description="Whether claims are verifiable")
869
+ confidence: float = Field(ge=0.0, le=1.0, description="Verification confidence")
870
+ notes: str = Field(description="Additional context")
871
+ ```
872
+
873
+ **Step 2: Create the prompt** (`prompts/fact_check.yaml`):
874
+ ```yaml
875
+ system: |
876
+ You are a fact-checker. Analyze the given content and identify
877
+ claims that can be verified. Assess the overall verifiability.
878
+
879
+ user: |
880
+ Content to fact-check:
881
+ {content}
882
+
883
+ Identify key claims and assess their verifiability.
884
+ ```
885
+
886
+ **Step 3: State is auto-generated**
887
+
888
+ State fields are now generated automatically from your YAML graph config.
889
+ The `state_key` in your node config determines where output is stored:
890
+ ```yaml
891
+ # Node output stored in state.fact_check automatically
892
+ fact_check:
893
+ type: llm
894
+ prompt: fact_check
895
+ state_key: fact_check # This creates the state field
896
+ ```
897
+
898
+ **Step 4: Add the node to your graph** (`graphs/yamlgraph.yaml`):
899
+ ```yaml
900
+ nodes:
901
+ generate:
902
+ type: prompt
903
+ prompt: generate
904
+ output_schema: # Inline schema - no Python model needed!
905
+ title: str
906
+ content: str
907
+ variables:
908
+ topic: topic
909
+ state_key: generated
910
+
911
+ fact_check: # ✨ New node - just YAML!
912
+ type: prompt
913
+ prompt: fact_check
914
+ output_schema: # Define schema inline
915
+ is_accurate: bool
916
+ issues: list[str]
917
+ requires: [generated]
918
+ variables:
919
+ content: generated.content
920
+ state_key: fact_check
921
+
922
+ analyze:
923
+ # ... existing config ...
924
+
925
+ edges:
926
+ - from: START
927
+ to: generate
928
+ - from: generate
929
+ to: fact_check
930
+ condition:
931
+ type: has_value
932
+ field: generated
933
+ - from: fact_check
934
+ to: analyze
935
+ # ... rest of edges ...
936
+ ```
937
+
938
+ That's it! No Python node code needed. The graph loader dynamically generates the node function.
939
+
940
+ Resulting pipeline:
941
+ ```mermaid
942
+ graph TD
943
+ A[generate] --> B{has generated?}
944
+ B -->|yes| C[fact_check]
945
+ C --> D[analyze]
946
+ D --> E[summarize]
947
+ E --> F[END]
948
+ B -->|no| F
949
+ ```
950
+
951
+ ### Adding Conditional Branching
952
+
953
+ Route to different nodes based on analysis results (all in YAML):
954
+
955
+ ```yaml
956
+ edges:
957
+ - from: analyze
958
+ to: rewrite_node
959
+ condition:
960
+ type: field_equals
961
+ field: analysis.sentiment
962
+ value: negative
963
+
964
+ - from: analyze
965
+ to: enhance_node
966
+ condition:
967
+ type: field_equals
968
+ field: analysis.sentiment
969
+ value: positive
970
+
971
+ - from: analyze
972
+ to: summarize # Default fallback
973
+ ```
974
+
975
+ ### Add a New Prompt
976
+
977
+ 1. Create `prompts/new_prompt.yaml`:
978
+ ```yaml
979
+ system: Your system prompt...
980
+ user: Your user prompt with {variables}...
981
+ ```
982
+
983
+ 2. Call it:
984
+ ```python
985
+ result = execute_prompt("new_prompt", variables={"var": "value"})
986
+ ```
987
+
988
+ ### Add Structured Output
989
+
990
+ 1. Define model in `yamlgraph/models/schemas.py`:
991
+ ```python
992
+ class MyOutput(BaseModel):
993
+ field: str = Field(description="...")
994
+ ```
995
+
996
+ 2. Use with executor:
997
+ ```python
998
+ result = execute_prompt("prompt", output_model=MyOutput)
999
+ ```
1000
+
1001
+ ## Known Issues & Future Improvements
1002
+
1003
+ This project demonstrates solid production patterns with declarative YAML-based configuration.
1004
+
1005
+ ### Completed Features
1006
+
1007
+ | Feature | Status | Notes |
1008
+ |---------|--------|-------|
1009
+ | YAML Graph Configuration | ✅ | Declarative pipeline definition in `graphs/yamlgraph.yaml` |
1010
+ | Jinja2 Templating | ✅ | Hybrid auto-detection (simple {var} + advanced Jinja2) |
1011
+ | Multi-Provider LLMs | ✅ | Factory pattern supporting Anthropic/Mistral/OpenAI |
1012
+ | Dynamic Node Generation | ✅ | Nodes compiled from YAML at runtime |
1013
+
1014
+ ### Implemented Patterns
1015
+
1016
+ | Feature | Status | Notes |
1017
+ |---------|--------|-------|
1018
+ | Branching/Routing | ✅ | `type: router` for LLM-based conditional routing |
1019
+ | Self-Correction Loops | ✅ | Reflexion pattern with critique → refine cycles |
1020
+ | Tool/Agent Patterns | ✅ | Shell tools + agent nodes with LangChain tool binding |
1021
+ | Per-Node Error Handling | ✅ | `on_error: skip/retry/fail/fallback` |
1022
+ | Conversation Memory | ✅ | Message accumulation via `AgentState.messages` |
1023
+ | Native Checkpointing | ✅ | `SqliteSaver` from `langgraph-checkpoint-sqlite` |
1024
+ | State Export | ✅ | JSON/Markdown export with `export_result()` |
1025
+ | LangSmith Share Links | ✅ | Auto-generate public trace URLs after runs |
1026
+
1027
+ ### Missing LangGraph Features
1028
+
1029
+ | Feature | Status | Notes |
1030
+ |---------|--------|-------|
1031
+ | Fan-out/Fan-in | ✅ | `type: map` with `Send()` for item-level parallelism |
1032
+ | Human-in-the-Loop | ❌ | No `interrupt_before` / `interrupt_after` demonstration |
1033
+ | Streaming | ❌ | No streaming output support |
1034
+ | Sub-graphs | ❌ | No nested graph composition |
1035
+
1036
+ ### Potential Enhancements
1037
+
1038
+ #### Short-term (Quick Wins)
1039
+ 1. **Add `in` operator to conditions** - Support `status in ["done", "complete"]` expressions
1040
+ 2. **Document agent `max_iterations`** - Expose in YAML schema for agent nodes
1041
+ 3. **Add `--dry-run` flag** - Validate graph without execution
1042
+
1043
+ #### Medium-term (Feature Improvements)
1044
+ 4. **Async map node execution** - Use `asyncio.gather()` for parallel branches
1045
+ 5. **State field collision warnings** - Log when YAML fields override base fields
1046
+ 6. **Map node error aggregation** - Summary with success/failure counts per branch
1047
+ 7. **Add streaming** - `--stream` CLI flag for real-time output
1048
+
1049
+ #### Long-term (Architecture)
1050
+ 8. **Plugin system** - Custom node types via entry points
1051
+ 9. **Hot-reload for development** - File watcher for prompt/graph YAML changes
1052
+ 10. **OpenTelemetry integration** - Complement LangSmith with standard observability
1053
+ 11. **Sub-graphs** - Nested graph composition for complex workflows
1054
+ 12. **Human-in-the-loop** - `interrupt_before` / `interrupt_after` demonstration
1055
+
1056
+ ## Security
1057
+
1058
+ ### Shell Command Injection Protection
1059
+
1060
+ 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.
1061
+
1062
+ ```yaml
1063
+ # In graph YAML - command template is trusted
1064
+ tools:
1065
+ git_log:
1066
+ type: shell
1067
+ command: "git log --author={author} -n {count}"
1068
+ ```
1069
+
1070
+ **Security model:**
1071
+ - ✅ **Command templates** (from YAML) are trusted configuration
1072
+ - ✅ **Variable values** (from user input/LLM) are escaped with `shlex.quote()`
1073
+ - ✅ **Complex types** (lists, dicts) are JSON-serialized then quoted
1074
+ - ✅ **No `eval()`** - condition expressions parsed with regex, not evaluated
1075
+
1076
+ **Example protection:**
1077
+ ```python
1078
+ # Malicious input is safely escaped
1079
+ variables = {"author": "$(rm -rf /)"}
1080
+ # Executed as: git log --author='$(rm -rf /)' (quoted, harmless)
1081
+ ```
1082
+
1083
+ See [yamlgraph/tools/shell.py](yamlgraph/tools/shell.py) for implementation details.
1084
+
1085
+ ### ⚠️ Security Considerations
1086
+
1087
+ **Shell tools execute real commands** on your system. While variables are sanitized:
1088
+
1089
+ 1. **Command templates are trusted** - Only use shell tools from trusted YAML configs
1090
+ 2. **No sandboxing** - Commands run with your user permissions
1091
+ 3. **Agent autonomy** - Agent nodes may call tools unpredictably
1092
+ 4. **Review tool definitions** - Audit `tools:` section in graph YAML before running
1093
+
1094
+ For production deployments, consider:
1095
+ - Running in a container with limited permissions
1096
+ - Restricting available tools to read-only operations
1097
+ - Implementing approval workflows for sensitive operations
1098
+
1099
+ ## License
1100
+
1101
+ MIT
1102
+
1103
+ ## Remember
1104
+
1105
+ 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