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,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()
@@ -0,0 +1,6 @@
1
+ """Allow running yamlgraph.cli as a module: python -m yamlgraph.cli"""
2
+
3
+ from yamlgraph.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()