yamlgraph 0.1.1__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.

Potentially problematic release.


This version of yamlgraph might be problematic. Click here for more details.

Files changed (111) hide show
  1. examples/__init__.py +1 -0
  2. examples/storyboard/__init__.py +1 -0
  3. examples/storyboard/generate_videos.py +335 -0
  4. examples/storyboard/nodes/__init__.py +10 -0
  5. examples/storyboard/nodes/animated_character_node.py +248 -0
  6. examples/storyboard/nodes/animated_image_node.py +138 -0
  7. examples/storyboard/nodes/character_node.py +162 -0
  8. examples/storyboard/nodes/image_node.py +118 -0
  9. examples/storyboard/nodes/replicate_tool.py +238 -0
  10. examples/storyboard/retry_images.py +118 -0
  11. tests/__init__.py +1 -0
  12. tests/conftest.py +178 -0
  13. tests/integration/__init__.py +1 -0
  14. tests/integration/test_animated_storyboard.py +63 -0
  15. tests/integration/test_cli_commands.py +242 -0
  16. tests/integration/test_map_demo.py +50 -0
  17. tests/integration/test_memory_demo.py +281 -0
  18. tests/integration/test_pipeline_flow.py +105 -0
  19. tests/integration/test_providers.py +163 -0
  20. tests/integration/test_resume.py +75 -0
  21. tests/unit/__init__.py +1 -0
  22. tests/unit/test_agent_nodes.py +200 -0
  23. tests/unit/test_checkpointer.py +212 -0
  24. tests/unit/test_cli.py +121 -0
  25. tests/unit/test_cli_package.py +81 -0
  26. tests/unit/test_compile_graph_map.py +132 -0
  27. tests/unit/test_conditions_routing.py +253 -0
  28. tests/unit/test_config.py +93 -0
  29. tests/unit/test_conversation_memory.py +270 -0
  30. tests/unit/test_database.py +145 -0
  31. tests/unit/test_deprecation.py +104 -0
  32. tests/unit/test_executor.py +60 -0
  33. tests/unit/test_executor_async.py +179 -0
  34. tests/unit/test_export.py +150 -0
  35. tests/unit/test_expressions.py +178 -0
  36. tests/unit/test_format_prompt.py +145 -0
  37. tests/unit/test_generic_report.py +200 -0
  38. tests/unit/test_graph_commands.py +327 -0
  39. tests/unit/test_graph_loader.py +299 -0
  40. tests/unit/test_graph_schema.py +193 -0
  41. tests/unit/test_inline_schema.py +151 -0
  42. tests/unit/test_issues.py +164 -0
  43. tests/unit/test_jinja2_prompts.py +85 -0
  44. tests/unit/test_langsmith.py +319 -0
  45. tests/unit/test_llm_factory.py +109 -0
  46. tests/unit/test_llm_factory_async.py +118 -0
  47. tests/unit/test_loops.py +403 -0
  48. tests/unit/test_map_node.py +144 -0
  49. tests/unit/test_no_backward_compat.py +56 -0
  50. tests/unit/test_node_factory.py +225 -0
  51. tests/unit/test_prompts.py +166 -0
  52. tests/unit/test_python_nodes.py +198 -0
  53. tests/unit/test_reliability.py +298 -0
  54. tests/unit/test_result_export.py +234 -0
  55. tests/unit/test_router.py +296 -0
  56. tests/unit/test_sanitize.py +99 -0
  57. tests/unit/test_schema_loader.py +295 -0
  58. tests/unit/test_shell_tools.py +229 -0
  59. tests/unit/test_state_builder.py +331 -0
  60. tests/unit/test_state_builder_map.py +104 -0
  61. tests/unit/test_state_config.py +197 -0
  62. tests/unit/test_template.py +190 -0
  63. tests/unit/test_tool_nodes.py +129 -0
  64. yamlgraph/__init__.py +35 -0
  65. yamlgraph/builder.py +110 -0
  66. yamlgraph/cli/__init__.py +139 -0
  67. yamlgraph/cli/__main__.py +6 -0
  68. yamlgraph/cli/commands.py +232 -0
  69. yamlgraph/cli/deprecation.py +92 -0
  70. yamlgraph/cli/graph_commands.py +382 -0
  71. yamlgraph/cli/validators.py +37 -0
  72. yamlgraph/config.py +67 -0
  73. yamlgraph/constants.py +66 -0
  74. yamlgraph/error_handlers.py +226 -0
  75. yamlgraph/executor.py +275 -0
  76. yamlgraph/executor_async.py +122 -0
  77. yamlgraph/graph_loader.py +337 -0
  78. yamlgraph/map_compiler.py +138 -0
  79. yamlgraph/models/__init__.py +36 -0
  80. yamlgraph/models/graph_schema.py +141 -0
  81. yamlgraph/models/schemas.py +124 -0
  82. yamlgraph/models/state_builder.py +236 -0
  83. yamlgraph/node_factory.py +240 -0
  84. yamlgraph/routing.py +87 -0
  85. yamlgraph/schema_loader.py +160 -0
  86. yamlgraph/storage/__init__.py +17 -0
  87. yamlgraph/storage/checkpointer.py +72 -0
  88. yamlgraph/storage/database.py +320 -0
  89. yamlgraph/storage/export.py +269 -0
  90. yamlgraph/tools/__init__.py +1 -0
  91. yamlgraph/tools/agent.py +235 -0
  92. yamlgraph/tools/nodes.py +124 -0
  93. yamlgraph/tools/python_tool.py +178 -0
  94. yamlgraph/tools/shell.py +205 -0
  95. yamlgraph/utils/__init__.py +47 -0
  96. yamlgraph/utils/conditions.py +157 -0
  97. yamlgraph/utils/expressions.py +111 -0
  98. yamlgraph/utils/langsmith.py +308 -0
  99. yamlgraph/utils/llm_factory.py +118 -0
  100. yamlgraph/utils/llm_factory_async.py +105 -0
  101. yamlgraph/utils/logging.py +127 -0
  102. yamlgraph/utils/prompts.py +116 -0
  103. yamlgraph/utils/sanitize.py +98 -0
  104. yamlgraph/utils/template.py +102 -0
  105. yamlgraph/utils/validators.py +181 -0
  106. yamlgraph-0.1.1.dist-info/METADATA +854 -0
  107. yamlgraph-0.1.1.dist-info/RECORD +111 -0
  108. yamlgraph-0.1.1.dist-info/WHEEL +5 -0
  109. yamlgraph-0.1.1.dist-info/entry_points.txt +2 -0
  110. yamlgraph-0.1.1.dist-info/licenses/LICENSE +21 -0
  111. yamlgraph-0.1.1.dist-info/top_level.txt +3 -0
@@ -0,0 +1,382 @@
1
+ """Graph commands for universal graph runner.
2
+
3
+ Implements:
4
+ - graph run <path> --var key=value
5
+ - graph list
6
+ - graph info <path>
7
+ """
8
+
9
+ import sys
10
+ from argparse import Namespace
11
+ from pathlib import Path
12
+
13
+ import yaml
14
+
15
+
16
+ def parse_vars(var_list: list[str] | None) -> dict[str, str]:
17
+ """Parse --var key=value arguments into a dict.
18
+
19
+ Args:
20
+ var_list: List of "key=value" strings
21
+
22
+ Returns:
23
+ Dict mapping keys to values
24
+
25
+ Raises:
26
+ ValueError: If a var doesn't contain '='
27
+ """
28
+ if not var_list:
29
+ return {}
30
+
31
+ result = {}
32
+ for item in var_list:
33
+ if "=" not in item:
34
+ raise ValueError(f"Invalid var format: '{item}' (expected key=value)")
35
+ key, value = item.split("=", 1)
36
+ result[key] = value
37
+
38
+ return result
39
+
40
+
41
+ def _display_result(result: dict) -> None:
42
+ """Display result summary to console.
43
+
44
+ Args:
45
+ result: Graph execution result dict
46
+ """
47
+ print("=" * 60)
48
+ print("RESULT")
49
+ print("=" * 60)
50
+
51
+ skip_keys = {"messages", "errors", "_loop_counts"}
52
+ for key, value in result.items():
53
+ if key.startswith("_") or key in skip_keys:
54
+ continue
55
+ if value is not None:
56
+ value_str = str(value)[:200]
57
+ if len(str(value)) > 200:
58
+ value_str += "..."
59
+ print(f" {key}: {value_str}")
60
+
61
+
62
+ def _handle_export(graph_path: Path, result: dict) -> None:
63
+ """Handle optional result export.
64
+
65
+ Args:
66
+ graph_path: Path to the graph YAML file
67
+ result: Graph execution result dict
68
+ """
69
+ from yamlgraph.storage.export import export_result
70
+
71
+ with open(graph_path) as f:
72
+ graph_config = yaml.safe_load(f)
73
+
74
+ export_config = graph_config.get("exports", {})
75
+ if export_config:
76
+ paths = export_result(result, export_config)
77
+ if paths:
78
+ print("\n📁 Exported:")
79
+ for p in paths:
80
+ print(f" {p}")
81
+
82
+
83
+ def cmd_graph_run(args: Namespace) -> None:
84
+ """Run any graph with provided variables.
85
+
86
+ Usage:
87
+ yamlgraph graph run graphs/yamlgraph.yaml --var topic=AI --var style=casual
88
+ """
89
+ from yamlgraph.graph_loader import load_and_compile
90
+
91
+ graph_path = Path(args.graph_path)
92
+
93
+ if not graph_path.exists():
94
+ print(f"❌ Graph file not found: {graph_path}")
95
+ sys.exit(1)
96
+
97
+ # Parse variables
98
+ try:
99
+ initial_state = parse_vars(args.var)
100
+ except ValueError as e:
101
+ print(f"❌ {e}")
102
+ sys.exit(1)
103
+
104
+ print(f"\n🚀 Running graph: {graph_path.name}")
105
+ if initial_state:
106
+ print(f" Variables: {initial_state}")
107
+ print()
108
+
109
+ try:
110
+ graph = load_and_compile(str(graph_path))
111
+ app = graph.compile()
112
+
113
+ # Add thread_id if provided
114
+ config = {}
115
+ if args.thread:
116
+ config["configurable"] = {"thread_id": args.thread}
117
+ initial_state["thread_id"] = args.thread
118
+
119
+ result = app.invoke(initial_state, config=config if config else None)
120
+
121
+ _display_result(result)
122
+
123
+ if args.export:
124
+ _handle_export(graph_path, result)
125
+
126
+ print()
127
+
128
+ except Exception as e:
129
+ print(f"❌ Error: {e}")
130
+ sys.exit(1)
131
+
132
+
133
+ def cmd_graph_list(args: Namespace) -> None:
134
+ """List available graphs in graphs/ directory."""
135
+ graphs_dir = Path("graphs")
136
+
137
+ if not graphs_dir.exists():
138
+ print("❌ graphs/ directory not found")
139
+ return
140
+
141
+ yaml_files = sorted(graphs_dir.glob("*.yaml"))
142
+
143
+ if not yaml_files:
144
+ print("No graphs found in graphs/")
145
+ return
146
+
147
+ print(f"\n📋 Available graphs ({len(yaml_files)}):\n")
148
+
149
+ for path in yaml_files:
150
+ try:
151
+ with open(path) as f:
152
+ config = yaml.safe_load(f)
153
+ description = config.get("description", "")
154
+ print(f" {path.name}")
155
+ if description:
156
+ print(f" {description[:60]}")
157
+ except Exception:
158
+ print(f" {path.name} (invalid)")
159
+
160
+ print()
161
+
162
+
163
+ def cmd_graph_info(args: Namespace) -> None:
164
+ """Show information about a graph."""
165
+ graph_path = Path(args.graph_path)
166
+
167
+ if not graph_path.exists():
168
+ print(f"❌ Graph file not found: {graph_path}")
169
+ sys.exit(1)
170
+
171
+ try:
172
+ with open(graph_path) as f:
173
+ config = yaml.safe_load(f)
174
+
175
+ name = config.get("name", graph_path.stem)
176
+ description = config.get("description", "No description")
177
+ state_class = config.get("state_class", "default")
178
+ nodes = config.get("nodes", {})
179
+ edges = config.get("edges", [])
180
+
181
+ print(f"\n📊 Graph: {name}")
182
+ print(f" {description}")
183
+ print(f"\n State: {state_class}")
184
+
185
+ # Show nodes
186
+ print(f"\n Nodes ({len(nodes)}):")
187
+ for node_name, node_config in nodes.items():
188
+ node_type = node_config.get("type", "prompt")
189
+ print(f" - {node_name} ({node_type})")
190
+
191
+ # Show edges
192
+ print(f"\n Edges ({len(edges)}):")
193
+ for edge in edges:
194
+ from_node = edge.get("from", "?")
195
+ to_node = edge.get("to", "?")
196
+ condition = edge.get("condition", "")
197
+ if condition:
198
+ print(f" {from_node} → {to_node} (conditional)")
199
+ else:
200
+ print(f" {from_node} → {to_node}")
201
+
202
+ # Show required inputs if defined
203
+ inputs = config.get("inputs", {})
204
+ if inputs:
205
+ print(f"\n Inputs ({len(inputs)}):")
206
+ for input_name, input_config in inputs.items():
207
+ required = input_config.get("required", False)
208
+ default = input_config.get("default", None)
209
+ req_str = " (required)" if required else f" (default: {default})"
210
+ print(f" --var {input_name}=<value>{req_str}")
211
+
212
+ print()
213
+
214
+ except Exception as e:
215
+ print(f"❌ Error reading graph: {e}")
216
+ sys.exit(1)
217
+
218
+
219
+ def _validate_required_fields(config: dict) -> tuple[list[str], list[str]]:
220
+ """Validate required fields in graph config.
221
+
222
+ Args:
223
+ config: Parsed YAML configuration
224
+
225
+ Returns:
226
+ Tuple of (errors, warnings) lists
227
+ """
228
+ errors = []
229
+ warnings = []
230
+
231
+ if not config.get("name"):
232
+ errors.append("Missing required field: name")
233
+
234
+ if not config.get("nodes"):
235
+ errors.append("Missing required field: nodes")
236
+
237
+ if not config.get("edges"):
238
+ warnings.append("No edges defined")
239
+
240
+ return errors, warnings
241
+
242
+
243
+ def _validate_edges(edges: list[dict], node_names: set[str]) -> list[str]:
244
+ """Validate edge references in graph config.
245
+
246
+ Args:
247
+ edges: List of edge configurations
248
+ node_names: Set of valid node names (including START/END)
249
+
250
+ Returns:
251
+ List of error messages
252
+ """
253
+ errors = []
254
+
255
+ for i, edge in enumerate(edges):
256
+ from_node = edge.get("from", "")
257
+ to_node = edge.get("to", "")
258
+
259
+ if from_node not in node_names:
260
+ errors.append(f"Edge {i + 1}: unknown 'from' node '{from_node}'")
261
+
262
+ # Handle conditional edges where 'to' is a list
263
+ if isinstance(to_node, list):
264
+ for t in to_node:
265
+ if t not in node_names:
266
+ errors.append(f"Edge {i + 1}: unknown 'to' node '{t}'")
267
+ elif to_node not in node_names:
268
+ errors.append(f"Edge {i + 1}: unknown 'to' node '{to_node}'")
269
+
270
+ return errors
271
+
272
+
273
+ def _validate_nodes(nodes: dict) -> list[str]:
274
+ """Validate node configurations.
275
+
276
+ Args:
277
+ nodes: Dict of node_name -> node_config
278
+
279
+ Returns:
280
+ List of warning messages
281
+ """
282
+ warnings = []
283
+
284
+ for node_name, node_config in nodes.items():
285
+ node_type = node_config.get("type", "llm")
286
+ if node_type == "agent" and not node_config.get("tools"):
287
+ warnings.append(f"Node '{node_name}': agent has no tools")
288
+
289
+ return warnings
290
+
291
+
292
+ def _report_validation_result(
293
+ graph_path: Path,
294
+ config: dict,
295
+ errors: list[str],
296
+ warnings: list[str],
297
+ ) -> None:
298
+ """Report validation results and exit appropriately.
299
+
300
+ Args:
301
+ graph_path: Path to the graph file
302
+ config: Parsed graph configuration
303
+ errors: List of error messages
304
+ warnings: List of warning messages
305
+ """
306
+ name = config.get("name", graph_path.stem)
307
+ nodes = config.get("nodes", {})
308
+ edges = config.get("edges", [])
309
+
310
+ if errors:
311
+ print(f"\n❌ {graph_path.name} ({name}) - INVALID\n")
312
+ for err in errors:
313
+ print(f" ✗ {err}")
314
+ for warn in warnings:
315
+ print(f" ⚠ {warn}")
316
+ print()
317
+ sys.exit(1)
318
+ elif warnings:
319
+ print(f"\n⚠️ {graph_path.name} ({name}) - VALID with warnings\n")
320
+ for warn in warnings:
321
+ print(f" ⚠ {warn}")
322
+ print()
323
+ else:
324
+ print(f"\n✅ {graph_path.name} ({name}) - VALID\n")
325
+ print(f" Nodes: {len(nodes)}")
326
+ print(f" Edges: {len(edges)}")
327
+ print()
328
+
329
+
330
+ def cmd_graph_validate(args: Namespace) -> None:
331
+ """Validate a graph YAML file.
332
+
333
+ Checks:
334
+ - File exists and is valid YAML
335
+ - Required fields present (name, nodes, edges)
336
+ - Node references are valid
337
+ - Edge references match existing nodes
338
+ """
339
+ graph_path = Path(args.graph_path)
340
+
341
+ if not graph_path.exists():
342
+ print(f"❌ Graph file not found: {graph_path}")
343
+ sys.exit(1)
344
+
345
+ try:
346
+ with open(graph_path) as f:
347
+ config = yaml.safe_load(f)
348
+
349
+ # Run validations
350
+ errors, warnings = _validate_required_fields(config)
351
+
352
+ nodes = config.get("nodes", {})
353
+ edges = config.get("edges", [])
354
+ node_names = set(nodes.keys()) | {"START", "END"}
355
+
356
+ errors.extend(_validate_edges(edges, node_names))
357
+ warnings.extend(_validate_nodes(nodes))
358
+
359
+ # Report results
360
+ _report_validation_result(graph_path, config, errors, warnings)
361
+
362
+ except yaml.YAMLError as e:
363
+ print(f"❌ Invalid YAML: {e}")
364
+ sys.exit(1)
365
+ except Exception as e:
366
+ print(f"❌ Error validating graph: {e}")
367
+ sys.exit(1)
368
+
369
+
370
+ def cmd_graph_dispatch(args: Namespace) -> None:
371
+ """Dispatch to graph subcommands."""
372
+ if args.graph_command == "run":
373
+ cmd_graph_run(args)
374
+ elif args.graph_command == "list":
375
+ cmd_graph_list(args)
376
+ elif args.graph_command == "info":
377
+ cmd_graph_info(args)
378
+ elif args.graph_command == "validate":
379
+ cmd_graph_validate(args)
380
+ else:
381
+ print(f"Unknown graph command: {args.graph_command}")
382
+ sys.exit(1)
@@ -0,0 +1,37 @@
1
+ """CLI validation functions.
2
+
3
+ Provides argument validation for CLI commands.
4
+ """
5
+
6
+ from yamlgraph.config import MAX_WORD_COUNT, MIN_WORD_COUNT
7
+ from yamlgraph.utils.sanitize import sanitize_topic
8
+
9
+
10
+ def validate_run_args(args) -> bool:
11
+ """Validate and sanitize run command arguments.
12
+
13
+ Args:
14
+ args: Parsed arguments namespace
15
+
16
+ Returns:
17
+ True if valid, False otherwise (prints error message)
18
+ """
19
+ # Sanitize topic
20
+ result = sanitize_topic(args.topic)
21
+ if not result.is_safe:
22
+ for warning in result.warnings:
23
+ print(f"❌ {warning}")
24
+ return False
25
+
26
+ # Update args with sanitized value
27
+ args.topic = result.value
28
+
29
+ # Print any warnings (e.g., truncation)
30
+ for warning in result.warnings:
31
+ print(f"⚠️ {warning}")
32
+
33
+ if args.word_count < MIN_WORD_COUNT or args.word_count > MAX_WORD_COUNT:
34
+ print(f"❌ Word count must be between {MIN_WORD_COUNT} and {MAX_WORD_COUNT}")
35
+ return False
36
+
37
+ return True
yamlgraph/config.py ADDED
@@ -0,0 +1,67 @@
1
+ """Centralized configuration for the yamlgraph package.
2
+
3
+ Provides paths, settings, and environment configuration
4
+ used across all modules.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+
10
+ from dotenv import load_dotenv
11
+
12
+ # Package root (yamlgraph/ directory)
13
+ PACKAGE_ROOT = Path(__file__).parent
14
+
15
+ # Working directory (where the user runs the CLI from)
16
+ WORKING_DIR = Path.cwd()
17
+
18
+ # Load environment variables from current working directory
19
+ # This ensures .env is found where the user runs yamlgraph, not in site-packages
20
+ load_dotenv(WORKING_DIR / ".env")
21
+
22
+ # Directory paths (relative to working directory)
23
+ PROMPTS_DIR = WORKING_DIR / "prompts"
24
+ GRAPHS_DIR = WORKING_DIR / "graphs"
25
+ OUTPUTS_DIR = WORKING_DIR / "outputs"
26
+ DATABASE_PATH = OUTPUTS_DIR / "yamlgraph.db"
27
+
28
+ # Default graph configuration
29
+ DEFAULT_GRAPH = GRAPHS_DIR / "yamlgraph.yaml"
30
+
31
+ # LLM Configuration
32
+ DEFAULT_TEMPERATURE = 0.7
33
+ DEFAULT_MAX_TOKENS = 4096
34
+
35
+ # Default models per provider (override with {PROVIDER}_MODEL env var)
36
+ DEFAULT_MODELS = {
37
+ "anthropic": os.getenv("ANTHROPIC_MODEL", "claude-haiku-4-5"),
38
+ "mistral": os.getenv("MISTRAL_MODEL", "mistral-large-latest"),
39
+ "openai": os.getenv("OPENAI_MODEL", "gpt-4o"),
40
+ }
41
+
42
+ # Retry Configuration
43
+ MAX_RETRIES = int(os.getenv("LLM_MAX_RETRIES", "3"))
44
+ RETRY_BASE_DELAY = float(os.getenv("LLM_RETRY_DELAY", "1.0")) # seconds
45
+ RETRY_MAX_DELAY = float(os.getenv("LLM_RETRY_MAX_DELAY", "30.0")) # seconds
46
+
47
+ # CLI Constraints - configurable via environment
48
+ MAX_TOPIC_LENGTH = int(os.getenv("YAMLGRAPH_MAX_TOPIC_LENGTH", "500"))
49
+ MAX_WORD_COUNT = int(os.getenv("YAMLGRAPH_MAX_WORD_COUNT", "5000"))
50
+ MIN_WORD_COUNT = int(os.getenv("YAMLGRAPH_MIN_WORD_COUNT", "50"))
51
+
52
+ # Valid styles - can be extended via environment (comma-separated)
53
+ _default_styles = "informative,casual,technical"
54
+ VALID_STYLES = tuple(os.getenv("YAMLGRAPH_VALID_STYLES", _default_styles).split(","))
55
+
56
+ # Input Sanitization Patterns
57
+ # Characters that could be used for prompt injection
58
+ DANGEROUS_PATTERNS = [
59
+ "ignore previous",
60
+ "ignore above",
61
+ "disregard",
62
+ "forget everything",
63
+ "new instructions",
64
+ "system:",
65
+ "<|", # Token delimiters
66
+ "|>",
67
+ ]
yamlgraph/constants.py ADDED
@@ -0,0 +1,66 @@
1
+ """Type-safe constants for YAML graph configuration.
2
+
3
+ Provides enums for node types, error handlers, and other magic strings
4
+ used throughout the codebase to enable static type checking and IDE support.
5
+ """
6
+
7
+ from enum import StrEnum
8
+
9
+
10
+ class NodeType(StrEnum):
11
+ """Valid node types in YAML graph configuration."""
12
+
13
+ LLM = "llm"
14
+ ROUTER = "router"
15
+ TOOL = "tool"
16
+ AGENT = "agent"
17
+ PYTHON = "python"
18
+ MAP = "map"
19
+
20
+ @classmethod
21
+ def requires_prompt(cls, node_type: str) -> bool:
22
+ """Check if node type requires a prompt field.
23
+
24
+ Args:
25
+ node_type: The node type string
26
+
27
+ Returns:
28
+ True if the node type requires a prompt
29
+ """
30
+ return node_type in (cls.LLM, cls.ROUTER)
31
+
32
+
33
+ class ErrorHandler(StrEnum):
34
+ """Valid on_error handling strategies."""
35
+
36
+ SKIP = "skip" # Skip node and continue pipeline
37
+ RETRY = "retry" # Retry with max_retries attempts
38
+ FAIL = "fail" # Raise exception immediately
39
+ FALLBACK = "fallback" # Try fallback provider
40
+
41
+ @classmethod
42
+ def all_values(cls) -> set[str]:
43
+ """Return all valid error handler values.
44
+
45
+ Returns:
46
+ Set of valid error handler strings
47
+ """
48
+ return {handler.value for handler in cls}
49
+
50
+
51
+ class EdgeType(StrEnum):
52
+ """Valid edge types in graph configuration."""
53
+
54
+ SIMPLE = "simple" # Direct edge from -> to
55
+ CONDITIONAL = "conditional" # Edge with conditions
56
+
57
+
58
+ class SpecialNodes(StrEnum):
59
+ """Special node names with semantic meaning."""
60
+
61
+ START = "__start__"
62
+ END = "__end__"
63
+
64
+
65
+ # Re-export for convenience
66
+ __all__ = ["NodeType", "ErrorHandler", "EdgeType", "SpecialNodes"]