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,234 @@
|
|
|
1
|
+
"""Tests for yamlgraph.tools.websearch module."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestWebSearchToolConfig:
|
|
7
|
+
"""Tests for WebSearchToolConfig dataclass."""
|
|
8
|
+
|
|
9
|
+
def test_config_defaults(self):
|
|
10
|
+
"""Config should have sensible defaults."""
|
|
11
|
+
from yamlgraph.tools.websearch import WebSearchToolConfig
|
|
12
|
+
|
|
13
|
+
config = WebSearchToolConfig()
|
|
14
|
+
assert config.provider == "duckduckgo"
|
|
15
|
+
assert config.max_results == 5
|
|
16
|
+
assert config.description == ""
|
|
17
|
+
assert config.timeout == 30
|
|
18
|
+
|
|
19
|
+
def test_config_custom_values(self):
|
|
20
|
+
"""Config should accept custom values."""
|
|
21
|
+
from yamlgraph.tools.websearch import WebSearchToolConfig
|
|
22
|
+
|
|
23
|
+
config = WebSearchToolConfig(
|
|
24
|
+
provider="duckduckgo",
|
|
25
|
+
max_results=10,
|
|
26
|
+
description="Search the web",
|
|
27
|
+
timeout=60,
|
|
28
|
+
)
|
|
29
|
+
assert config.max_results == 10
|
|
30
|
+
assert config.description == "Search the web"
|
|
31
|
+
assert config.timeout == 60
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestWebSearchResult:
|
|
35
|
+
"""Tests for WebSearchResult dataclass."""
|
|
36
|
+
|
|
37
|
+
def test_result_success(self):
|
|
38
|
+
"""Result should store successful search data."""
|
|
39
|
+
from yamlgraph.tools.websearch import WebSearchResult
|
|
40
|
+
|
|
41
|
+
result = WebSearchResult(
|
|
42
|
+
success=True,
|
|
43
|
+
results=[{"title": "Test", "url": "https://test.com", "body": "Content"}],
|
|
44
|
+
query="test query",
|
|
45
|
+
)
|
|
46
|
+
assert result.success is True
|
|
47
|
+
assert len(result.results) == 1
|
|
48
|
+
assert result.error is None
|
|
49
|
+
|
|
50
|
+
def test_result_failure(self):
|
|
51
|
+
"""Result should store error information."""
|
|
52
|
+
from yamlgraph.tools.websearch import WebSearchResult
|
|
53
|
+
|
|
54
|
+
result = WebSearchResult(
|
|
55
|
+
success=False,
|
|
56
|
+
results=[],
|
|
57
|
+
query="test query",
|
|
58
|
+
error="Search failed",
|
|
59
|
+
)
|
|
60
|
+
assert result.success is False
|
|
61
|
+
assert result.error == "Search failed"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestExecuteWebSearch:
|
|
65
|
+
"""Tests for execute_web_search function."""
|
|
66
|
+
|
|
67
|
+
@patch("yamlgraph.tools.websearch.DUCKDUCKGO_AVAILABLE", True)
|
|
68
|
+
@patch("yamlgraph.tools.websearch.DDGS")
|
|
69
|
+
def test_successful_search(self, mock_ddgs_class):
|
|
70
|
+
"""Should return results from DuckDuckGo."""
|
|
71
|
+
from yamlgraph.tools.websearch import WebSearchToolConfig, execute_web_search
|
|
72
|
+
|
|
73
|
+
# Mock the DDGS context manager
|
|
74
|
+
mock_ddgs = MagicMock()
|
|
75
|
+
mock_ddgs.__enter__ = MagicMock(return_value=mock_ddgs)
|
|
76
|
+
mock_ddgs.__exit__ = MagicMock(return_value=False)
|
|
77
|
+
mock_ddgs.text.return_value = [
|
|
78
|
+
{"title": "Result 1", "href": "https://example.com", "body": "Content 1"},
|
|
79
|
+
{"title": "Result 2", "href": "https://example2.com", "body": "Content 2"},
|
|
80
|
+
]
|
|
81
|
+
mock_ddgs_class.return_value = mock_ddgs
|
|
82
|
+
|
|
83
|
+
config = WebSearchToolConfig(max_results=5)
|
|
84
|
+
result = execute_web_search("python programming", config)
|
|
85
|
+
|
|
86
|
+
assert result.success is True
|
|
87
|
+
assert len(result.results) == 2
|
|
88
|
+
assert result.results[0]["title"] == "Result 1"
|
|
89
|
+
mock_ddgs.text.assert_called_once_with("python programming", max_results=5)
|
|
90
|
+
|
|
91
|
+
@patch("yamlgraph.tools.websearch.DUCKDUCKGO_AVAILABLE", True)
|
|
92
|
+
@patch("yamlgraph.tools.websearch.DDGS")
|
|
93
|
+
def test_empty_results(self, mock_ddgs_class):
|
|
94
|
+
"""Should handle empty search results."""
|
|
95
|
+
from yamlgraph.tools.websearch import WebSearchToolConfig, execute_web_search
|
|
96
|
+
|
|
97
|
+
mock_ddgs = MagicMock()
|
|
98
|
+
mock_ddgs.__enter__ = MagicMock(return_value=mock_ddgs)
|
|
99
|
+
mock_ddgs.__exit__ = MagicMock(return_value=False)
|
|
100
|
+
mock_ddgs.text.return_value = []
|
|
101
|
+
mock_ddgs_class.return_value = mock_ddgs
|
|
102
|
+
|
|
103
|
+
config = WebSearchToolConfig()
|
|
104
|
+
result = execute_web_search("obscure query xyz123", config)
|
|
105
|
+
|
|
106
|
+
assert result.success is True
|
|
107
|
+
assert result.results == []
|
|
108
|
+
|
|
109
|
+
@patch("yamlgraph.tools.websearch.DUCKDUCKGO_AVAILABLE", True)
|
|
110
|
+
@patch("yamlgraph.tools.websearch.DDGS")
|
|
111
|
+
def test_search_error(self, mock_ddgs_class):
|
|
112
|
+
"""Should handle search errors gracefully."""
|
|
113
|
+
from yamlgraph.tools.websearch import WebSearchToolConfig, execute_web_search
|
|
114
|
+
|
|
115
|
+
mock_ddgs = MagicMock()
|
|
116
|
+
mock_ddgs.__enter__ = MagicMock(return_value=mock_ddgs)
|
|
117
|
+
mock_ddgs.__exit__ = MagicMock(return_value=False)
|
|
118
|
+
mock_ddgs.text.side_effect = Exception("Network error")
|
|
119
|
+
mock_ddgs_class.return_value = mock_ddgs
|
|
120
|
+
|
|
121
|
+
config = WebSearchToolConfig()
|
|
122
|
+
result = execute_web_search("test query", config)
|
|
123
|
+
|
|
124
|
+
assert result.success is False
|
|
125
|
+
assert "Network error" in result.error
|
|
126
|
+
|
|
127
|
+
def test_empty_query(self):
|
|
128
|
+
"""Should handle empty query."""
|
|
129
|
+
from yamlgraph.tools.websearch import WebSearchToolConfig, execute_web_search
|
|
130
|
+
|
|
131
|
+
config = WebSearchToolConfig()
|
|
132
|
+
result = execute_web_search("", config)
|
|
133
|
+
|
|
134
|
+
assert result.success is False
|
|
135
|
+
assert "empty" in result.error.lower()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestFormatSearchResults:
|
|
139
|
+
"""Tests for format_search_results function."""
|
|
140
|
+
|
|
141
|
+
def test_format_results_as_text(self):
|
|
142
|
+
"""Should format results as readable text."""
|
|
143
|
+
from yamlgraph.tools.websearch import WebSearchResult, format_search_results
|
|
144
|
+
|
|
145
|
+
result = WebSearchResult(
|
|
146
|
+
success=True,
|
|
147
|
+
query="python",
|
|
148
|
+
results=[
|
|
149
|
+
{
|
|
150
|
+
"title": "Python.org",
|
|
151
|
+
"href": "https://python.org",
|
|
152
|
+
"body": "Official site",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"title": "Python Wiki",
|
|
156
|
+
"href": "https://wiki.python.org",
|
|
157
|
+
"body": "Wiki",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
formatted = format_search_results(result)
|
|
163
|
+
|
|
164
|
+
assert "Python.org" in formatted
|
|
165
|
+
assert "https://python.org" in formatted
|
|
166
|
+
assert "Official site" in formatted
|
|
167
|
+
|
|
168
|
+
def test_format_empty_results(self):
|
|
169
|
+
"""Should handle empty results."""
|
|
170
|
+
from yamlgraph.tools.websearch import WebSearchResult, format_search_results
|
|
171
|
+
|
|
172
|
+
result = WebSearchResult(success=True, query="xyz", results=[])
|
|
173
|
+
|
|
174
|
+
formatted = format_search_results(result)
|
|
175
|
+
|
|
176
|
+
assert "no results" in formatted.lower()
|
|
177
|
+
|
|
178
|
+
def test_format_error_result(self):
|
|
179
|
+
"""Should format error message."""
|
|
180
|
+
from yamlgraph.tools.websearch import WebSearchResult, format_search_results
|
|
181
|
+
|
|
182
|
+
result = WebSearchResult(
|
|
183
|
+
success=False,
|
|
184
|
+
query="test",
|
|
185
|
+
results=[],
|
|
186
|
+
error="Search failed",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
formatted = format_search_results(result)
|
|
190
|
+
|
|
191
|
+
assert "error" in formatted.lower()
|
|
192
|
+
assert "Search failed" in formatted
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class TestCreateWebSearchTool:
|
|
196
|
+
"""Tests for create_web_search_tool function."""
|
|
197
|
+
|
|
198
|
+
def test_create_langchain_tool(self):
|
|
199
|
+
"""Should create a LangChain-compatible tool."""
|
|
200
|
+
from yamlgraph.tools.websearch import (
|
|
201
|
+
WebSearchToolConfig,
|
|
202
|
+
create_web_search_tool,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
config = WebSearchToolConfig(description="Search the web for information")
|
|
206
|
+
tool = create_web_search_tool("search_web", config)
|
|
207
|
+
|
|
208
|
+
assert tool.name == "search_web"
|
|
209
|
+
assert tool.description == "Search the web for information"
|
|
210
|
+
|
|
211
|
+
@patch("yamlgraph.tools.websearch.DUCKDUCKGO_AVAILABLE", True)
|
|
212
|
+
@patch("yamlgraph.tools.websearch.DDGS")
|
|
213
|
+
def test_tool_invocation(self, mock_ddgs_class):
|
|
214
|
+
"""Created tool should be invocable."""
|
|
215
|
+
from yamlgraph.tools.websearch import (
|
|
216
|
+
WebSearchToolConfig,
|
|
217
|
+
create_web_search_tool,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
mock_ddgs = MagicMock()
|
|
221
|
+
mock_ddgs.__enter__ = MagicMock(return_value=mock_ddgs)
|
|
222
|
+
mock_ddgs.__exit__ = MagicMock(return_value=False)
|
|
223
|
+
mock_ddgs.text.return_value = [
|
|
224
|
+
{"title": "Test", "href": "https://test.com", "body": "Test content"},
|
|
225
|
+
]
|
|
226
|
+
mock_ddgs_class.return_value = mock_ddgs
|
|
227
|
+
|
|
228
|
+
config = WebSearchToolConfig()
|
|
229
|
+
tool = create_web_search_tool("search_web", config)
|
|
230
|
+
|
|
231
|
+
result = tool.invoke({"query": "test"})
|
|
232
|
+
|
|
233
|
+
assert "Test" in result
|
|
234
|
+
assert "https://test.com" in result
|
yamlgraph/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""YamlGraph - YAML-first LLM pipeline framework.
|
|
2
|
+
|
|
3
|
+
Framework for building LLM pipelines with YAML configuration.
|
|
4
|
+
State is generated dynamically from graph config.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from yamlgraph.builder import build_graph, build_resume_graph, run_pipeline
|
|
8
|
+
from yamlgraph.executor import execute_prompt, get_executor
|
|
9
|
+
from yamlgraph.models import (
|
|
10
|
+
ErrorType,
|
|
11
|
+
GenericReport,
|
|
12
|
+
PipelineError,
|
|
13
|
+
build_state_class,
|
|
14
|
+
create_initial_state,
|
|
15
|
+
)
|
|
16
|
+
from yamlgraph.storage import YamlGraphDB
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
# Builder
|
|
20
|
+
"build_graph",
|
|
21
|
+
"build_resume_graph",
|
|
22
|
+
"run_pipeline",
|
|
23
|
+
# Executor
|
|
24
|
+
"execute_prompt",
|
|
25
|
+
"get_executor",
|
|
26
|
+
# Framework models
|
|
27
|
+
"ErrorType",
|
|
28
|
+
"PipelineError",
|
|
29
|
+
"GenericReport",
|
|
30
|
+
# Dynamic state
|
|
31
|
+
"build_state_class",
|
|
32
|
+
"create_initial_state",
|
|
33
|
+
# Storage
|
|
34
|
+
"YamlGraphDB",
|
|
35
|
+
]
|
yamlgraph/builder.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Graph builders for yamlgraph pipelines.
|
|
2
|
+
|
|
3
|
+
Provides functions to build pipeline graphs from YAML configuration.
|
|
4
|
+
|
|
5
|
+
Pipeline Architecture
|
|
6
|
+
=====================
|
|
7
|
+
|
|
8
|
+
The main pipeline follows this flow:
|
|
9
|
+
|
|
10
|
+
```mermaid
|
|
11
|
+
graph LR
|
|
12
|
+
A[generate] -->|content| B{should_continue}
|
|
13
|
+
B -->|continue| C[analyze]
|
|
14
|
+
B -->|end| E[END]
|
|
15
|
+
C -->|analysis| D[summarize]
|
|
16
|
+
D --> E[END]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
State Flow:
|
|
20
|
+
- generate: Creates structured content from topic
|
|
21
|
+
- analyze: Produces analysis from generated content
|
|
22
|
+
- summarize: Combines all outputs into final_summary
|
|
23
|
+
|
|
24
|
+
Graph Definition:
|
|
25
|
+
- Pipelines are defined in graphs/*.yaml
|
|
26
|
+
- Loaded and compiled via graph_loader module
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from langgraph.graph import StateGraph
|
|
33
|
+
|
|
34
|
+
from yamlgraph.config import DEFAULT_GRAPH
|
|
35
|
+
from yamlgraph.graph_loader import load_and_compile
|
|
36
|
+
from yamlgraph.models import create_initial_state
|
|
37
|
+
|
|
38
|
+
# Type alias for dynamic state
|
|
39
|
+
GraphState = dict[str, Any]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_graph(
|
|
43
|
+
graph_path: Path | str | None = None,
|
|
44
|
+
checkpointer: Any | None = None,
|
|
45
|
+
) -> StateGraph:
|
|
46
|
+
"""Build a pipeline graph from YAML with optional checkpointer.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
graph_path: Path to YAML graph definition.
|
|
50
|
+
Defaults to graphs/yamlgraph.yaml
|
|
51
|
+
checkpointer: Optional LangGraph checkpointer for state persistence.
|
|
52
|
+
Use get_checkpointer() from storage.checkpointer.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
StateGraph ready for compilation
|
|
56
|
+
"""
|
|
57
|
+
path = Path(graph_path) if graph_path else DEFAULT_GRAPH
|
|
58
|
+
graph = load_and_compile(path)
|
|
59
|
+
|
|
60
|
+
# Checkpointer is applied at compile time
|
|
61
|
+
if checkpointer is not None:
|
|
62
|
+
# Store reference for compile() to use
|
|
63
|
+
graph._checkpointer = checkpointer
|
|
64
|
+
|
|
65
|
+
return graph
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def build_resume_graph() -> StateGraph:
|
|
69
|
+
"""Build a graph for resuming an interrupted pipeline.
|
|
70
|
+
|
|
71
|
+
This is an alias for build_graph(). Resume works automatically
|
|
72
|
+
because nodes skip execution if their output already exists in state
|
|
73
|
+
(skip_if_exists behavior).
|
|
74
|
+
|
|
75
|
+
To resume:
|
|
76
|
+
1. Load saved state from database
|
|
77
|
+
2. Invoke graph with that state
|
|
78
|
+
3. Nodes with existing outputs are skipped
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
StateGraph for resume (same as main pipeline)
|
|
82
|
+
"""
|
|
83
|
+
return build_graph()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def run_pipeline(
|
|
87
|
+
topic: str,
|
|
88
|
+
style: str = "informative",
|
|
89
|
+
word_count: int = 300,
|
|
90
|
+
graph_path: Path | str | None = None,
|
|
91
|
+
) -> GraphState:
|
|
92
|
+
"""Run the complete pipeline with given inputs.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
topic: Topic to generate content about
|
|
96
|
+
style: Writing style
|
|
97
|
+
word_count: Target word count
|
|
98
|
+
graph_path: Optional path to graph YAML
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Final state with all outputs
|
|
102
|
+
"""
|
|
103
|
+
graph = build_graph(graph_path).compile()
|
|
104
|
+
initial_state = create_initial_state(
|
|
105
|
+
topic=topic,
|
|
106
|
+
style=style,
|
|
107
|
+
word_count=word_count,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return graph.invoke(initial_state)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""YamlGraph CLI - Command-line interface for yamlgraph.
|
|
2
|
+
|
|
3
|
+
This package provides the CLI entry point and command implementations.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
yamlgraph graph run graphs/yamlgraph.yaml --var topic="AI" --var style=casual
|
|
7
|
+
yamlgraph graph run graphs/router-demo.yaml --var message="hello"
|
|
8
|
+
yamlgraph graph list
|
|
9
|
+
yamlgraph list-runs
|
|
10
|
+
yamlgraph resume --thread-id abc123
|
|
11
|
+
yamlgraph trace --run-id <run-id>
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
# Import submodules for package access
|
|
17
|
+
from yamlgraph.cli import commands, validators
|
|
18
|
+
from yamlgraph.cli.commands import (
|
|
19
|
+
cmd_export,
|
|
20
|
+
cmd_list_runs,
|
|
21
|
+
cmd_resume,
|
|
22
|
+
cmd_trace,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
# Submodules
|
|
27
|
+
"commands",
|
|
28
|
+
"validators",
|
|
29
|
+
# Entry points
|
|
30
|
+
"main",
|
|
31
|
+
"create_parser",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
36
|
+
"""Create and configure the CLI argument parser.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Configured ArgumentParser for testing and main().
|
|
40
|
+
"""
|
|
41
|
+
parser = argparse.ArgumentParser(
|
|
42
|
+
description="YAMLGraph - YAML-first LLM Pipeline Framework",
|
|
43
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
47
|
+
|
|
48
|
+
# List runs command
|
|
49
|
+
list_parser = subparsers.add_parser("list-runs", help="List recent runs")
|
|
50
|
+
list_parser.add_argument(
|
|
51
|
+
"--limit", "-l", type=int, default=10, help="Maximum runs to show"
|
|
52
|
+
)
|
|
53
|
+
list_parser.set_defaults(func=cmd_list_runs)
|
|
54
|
+
|
|
55
|
+
# Resume command
|
|
56
|
+
resume_parser = subparsers.add_parser("resume", help="Resume a pipeline")
|
|
57
|
+
resume_parser.add_argument(
|
|
58
|
+
"--thread-id", "-i", required=True, help="Thread ID to resume"
|
|
59
|
+
)
|
|
60
|
+
resume_parser.set_defaults(func=cmd_resume)
|
|
61
|
+
|
|
62
|
+
# Trace command
|
|
63
|
+
trace_parser = subparsers.add_parser("trace", help="Show execution trace")
|
|
64
|
+
trace_parser.add_argument(
|
|
65
|
+
"--run-id", "-r", help="Run ID (uses latest if not provided)"
|
|
66
|
+
)
|
|
67
|
+
trace_parser.add_argument(
|
|
68
|
+
"--verbose", "-v", action="store_true", help="Include timing details"
|
|
69
|
+
)
|
|
70
|
+
trace_parser.set_defaults(func=cmd_trace)
|
|
71
|
+
|
|
72
|
+
# Export command
|
|
73
|
+
export_parser = subparsers.add_parser("export", help="Export a run to JSON")
|
|
74
|
+
export_parser.add_argument(
|
|
75
|
+
"--thread-id", "-i", required=True, help="Thread ID to export"
|
|
76
|
+
)
|
|
77
|
+
export_parser.set_defaults(func=cmd_export)
|
|
78
|
+
|
|
79
|
+
# Graph command group (universal runner)
|
|
80
|
+
from yamlgraph.cli.graph_commands import cmd_graph_dispatch
|
|
81
|
+
|
|
82
|
+
graph_parser = subparsers.add_parser(
|
|
83
|
+
"graph", help="Universal graph runner and utilities"
|
|
84
|
+
)
|
|
85
|
+
graph_subparsers = graph_parser.add_subparsers(
|
|
86
|
+
dest="graph_command", help="Graph commands"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# graph run
|
|
90
|
+
graph_run_parser = graph_subparsers.add_parser("run", help="Run any graph")
|
|
91
|
+
graph_run_parser.add_argument("graph_path", help="Path to graph YAML file")
|
|
92
|
+
graph_run_parser.add_argument(
|
|
93
|
+
"--var",
|
|
94
|
+
"-v",
|
|
95
|
+
action="append",
|
|
96
|
+
default=[],
|
|
97
|
+
help="Set state variable (key=value), can repeat",
|
|
98
|
+
)
|
|
99
|
+
graph_run_parser.add_argument(
|
|
100
|
+
"--thread", "-t", type=str, default=None, help="Thread ID for persistence"
|
|
101
|
+
)
|
|
102
|
+
graph_run_parser.add_argument(
|
|
103
|
+
"--export", "-e", action="store_true", help="Export results to files"
|
|
104
|
+
)
|
|
105
|
+
graph_run_parser.add_argument(
|
|
106
|
+
"--full", "-f", action="store_true", help="Show full output without truncation"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# graph list
|
|
110
|
+
graph_subparsers.add_parser("list", help="List available graphs")
|
|
111
|
+
|
|
112
|
+
# graph info
|
|
113
|
+
graph_info_parser = graph_subparsers.add_parser(
|
|
114
|
+
"info", help="Show graph information"
|
|
115
|
+
)
|
|
116
|
+
graph_info_parser.add_argument("graph_path", help="Path to graph YAML file")
|
|
117
|
+
|
|
118
|
+
# graph validate
|
|
119
|
+
graph_validate_parser = graph_subparsers.add_parser(
|
|
120
|
+
"validate", help="Validate graph YAML schema"
|
|
121
|
+
)
|
|
122
|
+
graph_validate_parser.add_argument("graph_path", help="Path to graph YAML file")
|
|
123
|
+
|
|
124
|
+
# graph lint
|
|
125
|
+
graph_lint_parser = graph_subparsers.add_parser(
|
|
126
|
+
"lint", help="Lint graph for issues (missing state, unused tools, etc.)"
|
|
127
|
+
)
|
|
128
|
+
graph_lint_parser.add_argument(
|
|
129
|
+
"graph_path", nargs="+", help="Path(s) to graph YAML file(s)"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# graph mermaid
|
|
133
|
+
graph_mermaid_parser = graph_subparsers.add_parser(
|
|
134
|
+
"mermaid", help="Generate Mermaid diagram from graph"
|
|
135
|
+
)
|
|
136
|
+
graph_mermaid_parser.add_argument("graph_path", help="Path to graph YAML file")
|
|
137
|
+
graph_mermaid_parser.add_argument(
|
|
138
|
+
"--output", "-o", type=str, help="Output file (default: stdout)"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
graph_parser.set_defaults(func=cmd_graph_dispatch)
|
|
142
|
+
|
|
143
|
+
return parser
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def main():
|
|
147
|
+
"""Main CLI entry point."""
|
|
148
|
+
parser = create_parser()
|
|
149
|
+
args = parser.parse_args()
|
|
150
|
+
|
|
151
|
+
if not args.command:
|
|
152
|
+
parser.print_help()
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
args.func(args)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
main()
|