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.
- examples/__init__.py +1 -0
- examples/codegen/__init__.py +5 -0
- examples/codegen/models/__init__.py +13 -0
- examples/codegen/models/schemas.py +76 -0
- examples/codegen/tests/__init__.py +1 -0
- examples/codegen/tests/test_ai_helpers.py +235 -0
- examples/codegen/tests/test_ast_analysis.py +174 -0
- examples/codegen/tests/test_code_analysis.py +134 -0
- examples/codegen/tests/test_code_context.py +301 -0
- examples/codegen/tests/test_code_nav.py +89 -0
- examples/codegen/tests/test_dependency_tools.py +119 -0
- examples/codegen/tests/test_example_tools.py +185 -0
- examples/codegen/tests/test_git_tools.py +112 -0
- examples/codegen/tests/test_impl_agent_schemas.py +193 -0
- examples/codegen/tests/test_impl_agent_v4_graph.py +94 -0
- examples/codegen/tests/test_jedi_analysis.py +226 -0
- examples/codegen/tests/test_meta_tools.py +250 -0
- examples/codegen/tests/test_plan_discovery_prompt.py +98 -0
- examples/codegen/tests/test_syntax_tools.py +85 -0
- examples/codegen/tests/test_synthesize_prompt.py +94 -0
- examples/codegen/tests/test_template_tools.py +244 -0
- examples/codegen/tools/__init__.py +80 -0
- examples/codegen/tools/ai_helpers.py +420 -0
- examples/codegen/tools/ast_analysis.py +92 -0
- examples/codegen/tools/code_context.py +180 -0
- examples/codegen/tools/code_nav.py +52 -0
- examples/codegen/tools/dependency_tools.py +120 -0
- examples/codegen/tools/example_tools.py +188 -0
- examples/codegen/tools/git_tools.py +151 -0
- examples/codegen/tools/impl_executor.py +614 -0
- examples/codegen/tools/jedi_analysis.py +311 -0
- examples/codegen/tools/meta_tools.py +202 -0
- examples/codegen/tools/syntax_tools.py +26 -0
- examples/codegen/tools/template_tools.py +356 -0
- examples/fastapi_interview.py +167 -0
- examples/npc/api/__init__.py +1 -0
- examples/npc/api/app.py +100 -0
- examples/npc/api/routes/__init__.py +5 -0
- examples/npc/api/routes/encounter.py +182 -0
- examples/npc/api/session.py +330 -0
- examples/npc/demo.py +387 -0
- examples/npc/nodes/__init__.py +5 -0
- examples/npc/nodes/image_node.py +92 -0
- examples/npc/run_encounter.py +230 -0
- examples/shared/__init__.py +0 -0
- examples/shared/replicate_tool.py +238 -0
- examples/storyboard/__init__.py +1 -0
- examples/storyboard/generate_videos.py +335 -0
- examples/storyboard/nodes/__init__.py +12 -0
- examples/storyboard/nodes/animated_character_node.py +248 -0
- examples/storyboard/nodes/animated_image_node.py +138 -0
- examples/storyboard/nodes/character_node.py +162 -0
- examples/storyboard/nodes/image_node.py +118 -0
- examples/storyboard/nodes/replicate_tool.py +49 -0
- examples/storyboard/retry_images.py +118 -0
- scripts/demo_async_executor.py +212 -0
- scripts/demo_interview_e2e.py +200 -0
- scripts/demo_streaming.py +140 -0
- scripts/run_interview_demo.py +94 -0
- scripts/test_interrupt_fix.py +26 -0
- tests/__init__.py +1 -0
- tests/conftest.py +178 -0
- tests/integration/__init__.py +1 -0
- tests/integration/test_animated_storyboard.py +63 -0
- tests/integration/test_cli_commands.py +242 -0
- tests/integration/test_colocated_prompts.py +139 -0
- tests/integration/test_map_demo.py +50 -0
- tests/integration/test_memory_demo.py +283 -0
- tests/integration/test_npc_api/__init__.py +1 -0
- tests/integration/test_npc_api/test_routes.py +357 -0
- tests/integration/test_npc_api/test_session.py +216 -0
- tests/integration/test_pipeline_flow.py +105 -0
- tests/integration/test_providers.py +163 -0
- tests/integration/test_resume.py +75 -0
- tests/integration/test_subgraph_integration.py +295 -0
- tests/integration/test_subgraph_interrupt.py +106 -0
- tests/unit/__init__.py +1 -0
- tests/unit/test_agent_nodes.py +355 -0
- tests/unit/test_async_executor.py +346 -0
- tests/unit/test_checkpointer.py +212 -0
- tests/unit/test_checkpointer_factory.py +212 -0
- tests/unit/test_cli.py +121 -0
- tests/unit/test_cli_package.py +81 -0
- tests/unit/test_compile_graph_map.py +132 -0
- tests/unit/test_conditions_routing.py +253 -0
- tests/unit/test_config.py +93 -0
- tests/unit/test_conversation_memory.py +276 -0
- tests/unit/test_database.py +145 -0
- tests/unit/test_deprecation.py +104 -0
- tests/unit/test_executor.py +172 -0
- tests/unit/test_executor_async.py +179 -0
- tests/unit/test_export.py +149 -0
- tests/unit/test_expressions.py +178 -0
- tests/unit/test_feature_brainstorm.py +194 -0
- tests/unit/test_format_prompt.py +145 -0
- tests/unit/test_generic_report.py +200 -0
- tests/unit/test_graph_commands.py +327 -0
- tests/unit/test_graph_linter.py +627 -0
- tests/unit/test_graph_loader.py +357 -0
- tests/unit/test_graph_schema.py +193 -0
- tests/unit/test_inline_schema.py +151 -0
- tests/unit/test_interrupt_node.py +182 -0
- tests/unit/test_issues.py +164 -0
- tests/unit/test_jinja2_prompts.py +85 -0
- tests/unit/test_json_extract.py +134 -0
- tests/unit/test_langsmith.py +600 -0
- tests/unit/test_langsmith_tools.py +204 -0
- tests/unit/test_llm_factory.py +109 -0
- tests/unit/test_llm_factory_async.py +118 -0
- tests/unit/test_loops.py +403 -0
- tests/unit/test_map_node.py +144 -0
- tests/unit/test_no_backward_compat.py +56 -0
- tests/unit/test_node_factory.py +348 -0
- tests/unit/test_passthrough_node.py +126 -0
- tests/unit/test_prompts.py +324 -0
- tests/unit/test_python_nodes.py +198 -0
- tests/unit/test_reliability.py +298 -0
- tests/unit/test_result_export.py +234 -0
- tests/unit/test_router.py +296 -0
- tests/unit/test_sanitize.py +99 -0
- tests/unit/test_schema_loader.py +295 -0
- tests/unit/test_shell_tools.py +229 -0
- tests/unit/test_state_builder.py +331 -0
- tests/unit/test_state_builder_map.py +104 -0
- tests/unit/test_state_config.py +197 -0
- tests/unit/test_streaming.py +307 -0
- tests/unit/test_subgraph.py +596 -0
- tests/unit/test_template.py +190 -0
- tests/unit/test_tool_call_integration.py +164 -0
- tests/unit/test_tool_call_node.py +178 -0
- tests/unit/test_tool_nodes.py +129 -0
- tests/unit/test_websearch.py +234 -0
- yamlgraph/__init__.py +35 -0
- yamlgraph/builder.py +110 -0
- yamlgraph/cli/__init__.py +159 -0
- yamlgraph/cli/__main__.py +6 -0
- yamlgraph/cli/commands.py +231 -0
- yamlgraph/cli/deprecation.py +92 -0
- yamlgraph/cli/graph_commands.py +541 -0
- yamlgraph/cli/validators.py +37 -0
- yamlgraph/config.py +67 -0
- yamlgraph/constants.py +70 -0
- yamlgraph/error_handlers.py +227 -0
- yamlgraph/executor.py +290 -0
- yamlgraph/executor_async.py +288 -0
- yamlgraph/graph_loader.py +451 -0
- yamlgraph/map_compiler.py +150 -0
- yamlgraph/models/__init__.py +36 -0
- yamlgraph/models/graph_schema.py +181 -0
- yamlgraph/models/schemas.py +124 -0
- yamlgraph/models/state_builder.py +236 -0
- yamlgraph/node_factory.py +768 -0
- yamlgraph/routing.py +87 -0
- yamlgraph/schema_loader.py +240 -0
- yamlgraph/storage/__init__.py +20 -0
- yamlgraph/storage/checkpointer.py +72 -0
- yamlgraph/storage/checkpointer_factory.py +123 -0
- yamlgraph/storage/database.py +320 -0
- yamlgraph/storage/export.py +269 -0
- yamlgraph/tools/__init__.py +1 -0
- yamlgraph/tools/agent.py +320 -0
- yamlgraph/tools/graph_linter.py +388 -0
- yamlgraph/tools/langsmith_tools.py +125 -0
- yamlgraph/tools/nodes.py +126 -0
- yamlgraph/tools/python_tool.py +179 -0
- yamlgraph/tools/shell.py +205 -0
- yamlgraph/tools/websearch.py +242 -0
- yamlgraph/utils/__init__.py +48 -0
- yamlgraph/utils/conditions.py +157 -0
- yamlgraph/utils/expressions.py +245 -0
- yamlgraph/utils/json_extract.py +104 -0
- yamlgraph/utils/langsmith.py +416 -0
- yamlgraph/utils/llm_factory.py +118 -0
- yamlgraph/utils/llm_factory_async.py +105 -0
- yamlgraph/utils/logging.py +104 -0
- yamlgraph/utils/prompts.py +171 -0
- yamlgraph/utils/sanitize.py +98 -0
- yamlgraph/utils/template.py +102 -0
- yamlgraph/utils/validators.py +181 -0
- yamlgraph-0.3.9.dist-info/METADATA +1105 -0
- yamlgraph-0.3.9.dist-info/RECORD +185 -0
- yamlgraph-0.3.9.dist-info/WHEEL +5 -0
- yamlgraph-0.3.9.dist-info/entry_points.txt +2 -0
- yamlgraph-0.3.9.dist-info/licenses/LICENSE +33 -0
- 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
|
+
[](https://pypi.org/project/yamlgraph/)
|
|
37
|
+
[](https://www.python.org/downloads/)
|
|
38
|
+
[](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
|