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,231 @@
1
+ """CLI command implementations.
2
+
3
+ Contains all cmd_* functions for CLI subcommands.
4
+ """
5
+
6
+ import sys
7
+ from argparse import Namespace
8
+ from typing import Any
9
+
10
+ from pydantic import BaseModel
11
+
12
+ from yamlgraph.cli.validators import validate_run_args
13
+
14
+ # Internal keys to skip when formatting results
15
+ _INTERNAL_KEYS = frozenset(
16
+ {
17
+ "_route",
18
+ "_loop_counts",
19
+ "thread_id",
20
+ "current_step",
21
+ "errors",
22
+ "topic",
23
+ "style",
24
+ "word_count",
25
+ }
26
+ )
27
+
28
+
29
+ def _format_value(value: Any, max_length: int = 200) -> str:
30
+ """Format a single value for display.
31
+
32
+ Args:
33
+ value: The value to format (str, list, Pydantic model, etc.)
34
+ max_length: Maximum length before truncation
35
+
36
+ Returns:
37
+ Formatted string representation
38
+ """
39
+ if isinstance(value, BaseModel):
40
+ # Format Pydantic model as key: value pairs
41
+ lines = []
42
+ for field_name, field_value in value.model_dump().items():
43
+ formatted = _format_value(field_value, max_length)
44
+ lines.append(f" {field_name}: {formatted}")
45
+ return "\n" + "\n".join(lines)
46
+
47
+ if isinstance(value, list):
48
+ # Format list items
49
+ if not value:
50
+ return "[]"
51
+ if len(value) <= 3:
52
+ return str(value)
53
+ return f"[{len(value)} items]"
54
+
55
+ if isinstance(value, str):
56
+ if len(value) > max_length:
57
+ return value[:max_length] + "..."
58
+ return value
59
+
60
+ return str(value)
61
+
62
+
63
+ def _format_result(result: dict[str, Any]) -> None:
64
+ """Format and print pipeline result generically.
65
+
66
+ Iterates over all non-internal keys in the result dict
67
+ and prints their values. Works with any Pydantic model.
68
+
69
+ Args:
70
+ result: Pipeline result dict with arbitrary Pydantic models
71
+ """
72
+ for key, value in result.items():
73
+ if key in _INTERNAL_KEYS or value is None:
74
+ continue
75
+
76
+ print(f"\nšŸ“ {key}:")
77
+ formatted = _format_value(value)
78
+ if formatted.startswith("\n"):
79
+ print(formatted)
80
+ else:
81
+ print(f" {formatted}")
82
+
83
+
84
+ def cmd_run(args: Namespace) -> None:
85
+ """Run the yamlgraph pipeline."""
86
+ if not validate_run_args(args):
87
+ sys.exit(1)
88
+
89
+ from yamlgraph.builder import run_pipeline
90
+ from yamlgraph.storage import YamlGraphDB, export_state
91
+ from yamlgraph.utils import get_run_url, is_tracing_enabled
92
+
93
+ print("\nšŸš€ Running yamlgraph pipeline")
94
+ print(f" Topic: {args.topic}")
95
+ print(f" Style: {args.style}")
96
+ print(f" Words: {args.word_count}")
97
+ print()
98
+
99
+ # Run pipeline
100
+ result = run_pipeline(
101
+ topic=args.topic,
102
+ style=args.style,
103
+ word_count=args.word_count,
104
+ )
105
+
106
+ # Save to database
107
+ db = YamlGraphDB()
108
+ db.save_state(result["thread_id"], result, status="completed")
109
+ print(f"\nšŸ’¾ Saved to database: thread_id={result['thread_id']}")
110
+
111
+ # Export to JSON
112
+ if args.export:
113
+ filepath = export_state(result)
114
+ print(f"šŸ“„ Exported to: {filepath}")
115
+
116
+ # Show results
117
+ print("\n" + "=" * 60)
118
+ print("RESULTS")
119
+ print("=" * 60)
120
+
121
+ _format_result(result)
122
+
123
+ # Show LangSmith link
124
+ if is_tracing_enabled() and (url := get_run_url()):
125
+ print(f"\nšŸ”— LangSmith: {url}")
126
+
127
+ print()
128
+
129
+
130
+ def cmd_list_runs(args: Namespace) -> None:
131
+ """List recent pipeline runs."""
132
+ from yamlgraph.storage import YamlGraphDB
133
+
134
+ db = YamlGraphDB()
135
+ runs = db.list_runs(limit=args.limit)
136
+
137
+ if not runs:
138
+ print("No runs found.")
139
+ return
140
+
141
+ print(f"\nšŸ“‹ Recent runs ({len(runs)}):\n")
142
+ print(f"{'Thread ID':<12} {'Status':<12} {'Updated':<20}")
143
+ print("-" * 50)
144
+
145
+ for run in runs:
146
+ print(f"{run['thread_id']:<12} {run['status']:<12} {run['updated_at'][:19]}")
147
+
148
+ print()
149
+
150
+
151
+ def cmd_resume(args: Namespace) -> None:
152
+ """Resume a pipeline from saved state."""
153
+ from yamlgraph.builder import build_resume_graph
154
+ from yamlgraph.storage import YamlGraphDB
155
+
156
+ db = YamlGraphDB()
157
+ state = db.load_state(args.thread_id)
158
+
159
+ if not state:
160
+ print(f"āŒ No run found with thread_id: {args.thread_id}")
161
+ return
162
+
163
+ print(f"\nšŸ”„ Resuming from: {state.get('current_step', 'unknown')}")
164
+
165
+ # Check what's already completed
166
+ if state.get("final_summary"):
167
+ print("āœ… Pipeline already complete!")
168
+ return
169
+
170
+ # Show what will be skipped vs run
171
+ skipping = []
172
+ running = []
173
+ if state.get("generated"):
174
+ skipping.append("generate")
175
+ else:
176
+ running.append("generate")
177
+ if state.get("analysis"):
178
+ skipping.append("analyze")
179
+ else:
180
+ running.append("analyze")
181
+ running.append("summarize") # Always runs if we get here
182
+
183
+ if skipping:
184
+ print(f" Skipping: {', '.join(skipping)} (already in state)")
185
+ print(f" Running: {', '.join(running)}")
186
+
187
+ graph = build_resume_graph().compile()
188
+ result = graph.invoke(state)
189
+
190
+ # Save updated state
191
+ db.save_state(args.thread_id, result, status="completed")
192
+ print("\nāœ… Pipeline completed!")
193
+
194
+ if summary := result.get("final_summary"):
195
+ print(f"\nšŸ“Š Summary: {summary[:200]}...")
196
+
197
+
198
+ def cmd_trace(args: Namespace) -> None:
199
+ """Show execution trace for a run."""
200
+ from yamlgraph.utils import get_latest_run_id, get_run_url, print_run_tree
201
+
202
+ run_id = args.run_id or get_latest_run_id()
203
+
204
+ if not run_id:
205
+ print("āŒ No run ID provided and could not find latest run.")
206
+ return
207
+
208
+ print(f"\nšŸ“Š Execution trace for: {run_id}")
209
+ print("─" * 50)
210
+ print()
211
+ print_run_tree(run_id, verbose=args.verbose)
212
+
213
+ if url := get_run_url(run_id):
214
+ print(f"\nšŸ”— View in LangSmith: {url}")
215
+
216
+ print()
217
+
218
+
219
+ def cmd_export(args: Namespace) -> None:
220
+ """Export a run to JSON."""
221
+ from yamlgraph.storage import YamlGraphDB, export_state
222
+
223
+ db = YamlGraphDB()
224
+ state = db.load_state(args.thread_id)
225
+
226
+ if not state:
227
+ print(f"āŒ No run found with thread_id: {args.thread_id}")
228
+ return
229
+
230
+ filepath = export_state(state)
231
+ print(f"āœ… Exported to: {filepath}")
@@ -0,0 +1,92 @@
1
+ """Deprecation utilities for CLI commands.
2
+
3
+ Provides DeprecationError exception and helpers for migrating
4
+ old commands to the universal graph runner.
5
+
6
+ Term 'backward compatibility' signals refactoring need per project guidelines.
7
+
8
+ Example:
9
+ >>> from yamlgraph.cli.deprecation import DeprecationError, deprecated_command
10
+ >>>
11
+ >>> def cmd_old_command(args):
12
+ ... # Signal this command needs refactoring
13
+ ... deprecated_command("old", "graph run graphs/new.yaml --var x=y")
14
+ ...
15
+ >>> # Running raises:
16
+ >>> # DeprecationError: DEPRECATED: 'old' is deprecated.
17
+ >>> # Use 'graph run graphs/new.yaml --var x=y' instead.
18
+ """
19
+
20
+
21
+ class DeprecationError(Exception):
22
+ """Raised when a deprecated command is used.
23
+
24
+ Signals "Refactor me" - deprecated commands should be removed.
25
+
26
+ Attributes:
27
+ old_command: The deprecated command being used
28
+ new_command: The replacement command to use instead
29
+ """
30
+
31
+ def __init__(self, old_command: str, new_command: str):
32
+ self.old_command = old_command
33
+ self.new_command = new_command
34
+ message = (
35
+ f"DEPRECATED: '{old_command}' is deprecated. Use '{new_command}' instead."
36
+ )
37
+ super().__init__(message)
38
+
39
+
40
+ # Command mappings: old command -> graph path
41
+ COMMAND_MAPPINGS = {
42
+ "route": "graphs/router-demo.yaml",
43
+ "refine": "graphs/reflexion-demo.yaml",
44
+ "git-report": "graphs/git-report.yaml",
45
+ "memory-demo": "graphs/memory-demo.yaml",
46
+ }
47
+
48
+ # Variable mappings: old command -> arg name -> var name
49
+ ARG_MAPPINGS = {
50
+ "route": {"message": "message"},
51
+ "refine": {"topic": "topic"},
52
+ "git-report": {"query": "input"},
53
+ "memory-demo": {"input": "input"},
54
+ }
55
+
56
+
57
+ def get_replacement_command(old_command: str, args: dict[str, str]) -> str | None:
58
+ """Get the replacement graph run command.
59
+
60
+ Args:
61
+ old_command: The deprecated command name
62
+ args: Dict of argument values
63
+
64
+ Returns:
65
+ Replacement command string, or None if unknown command
66
+ """
67
+ graph_path = COMMAND_MAPPINGS.get(old_command)
68
+ if not graph_path:
69
+ return None
70
+
71
+ # Build --var arguments
72
+ var_args = []
73
+ arg_mapping = ARG_MAPPINGS.get(old_command, {})
74
+ for arg_name, var_name in arg_mapping.items():
75
+ if arg_name in args:
76
+ var_args.append(f"--var {var_name}={args[arg_name]}")
77
+
78
+ var_str = " ".join(var_args)
79
+ return f"graph run {graph_path} {var_str}".strip()
80
+
81
+
82
+ def deprecated_command(old_command: str, new_command: str) -> None:
83
+ """Raise DeprecationError for a deprecated command.
84
+
85
+ Args:
86
+ old_command: The deprecated command being used
87
+ new_command: The replacement command suggestion
88
+
89
+ Raises:
90
+ DeprecationError: Always raised to signal deprecation
91
+ """
92
+ raise DeprecationError(old_command, new_command)