fix: Edge format normalization and nullable workflow_id
All checks were successful
Deploy Development / deploy (push) Successful in 55s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s

Fixes:
1. Edge Format Mismatch:
   - graph_data uses React Flow format (source/target)
   - WorkflowEdge expects backend format (from/to)
   - Added normalization in parse_workflow_graph()

2. UUID Validation Error:
   - workflow_id can be None when using graph_data (Phase 5)
   - save_execution_state now accepts Optional[str]
   - ExecutionResult uses "N/A" placeholder when None

Changes:
- workflow_engine.py: normalize edges before Pydantic validation
- workflow_executor.py: Optional[str] for workflow_id parameter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-04-05 07:22:32 +02:00
parent fe28cce921
commit c95b4e185d
2 changed files with 20 additions and 4 deletions

View File

@ -354,6 +354,9 @@ def parse_workflow_graph(graph_jsonb: Dict) -> WorkflowGraph:
Args:
graph_jsonb: JSONB dict aus workflow_definitions.graph
Unterstützt beide Edge-Formate:
- Backend: {"from": "...", "to": "..."}
- Frontend: {"source": "...", "target": "..."}
Returns:
Validiertes WorkflowGraph-Objekt
@ -361,6 +364,19 @@ def parse_workflow_graph(graph_jsonb: Dict) -> WorkflowGraph:
Raises:
ValidationError: Bei ungültigem Graph-Format
"""
# Normalize edges: convert React Flow format (source/target) to backend format (from/to)
if "edges" in graph_jsonb:
normalized_edges = []
for edge in graph_jsonb["edges"]:
normalized_edge = edge.copy()
# Convert source → from, target → to (if present)
if "source" in normalized_edge and "from" not in normalized_edge:
normalized_edge["from"] = normalized_edge.pop("source")
if "target" in normalized_edge and "to" not in normalized_edge:
normalized_edge["to"] = normalized_edge.pop("target")
normalized_edges.append(normalized_edge)
graph_jsonb = {**graph_jsonb, "edges": normalized_edges}
return WorkflowGraph(**graph_jsonb)

View File

@ -201,7 +201,7 @@ async def execute_workflow(
completed_at = datetime.utcnow().isoformat()
save_execution_state(
execution_id=execution_id,
workflow_id=workflow_id or "", # Empty string if None (when graph_data is used)
workflow_id=workflow_id, # Can be None when graph_data is used
profile_id=profile_id,
node_states=node_states if 'node_states' in locals() else [],
status="failed",
@ -212,7 +212,7 @@ async def execute_workflow(
return ExecutionResult(
execution_id=execution_id,
workflow_id=workflow_id or "", # Empty string if None (when graph_data is used)
workflow_id=workflow_id or "N/A", # ExecutionResult requires string, use placeholder
status="failed",
node_states=node_states if 'node_states' in locals() else [],
aggregated_result={},
@ -840,7 +840,7 @@ def aggregate_results(node_states: List[NodeExecutionState]) -> Dict[str, Any]:
def save_execution_state(
execution_id: str,
workflow_id: str,
workflow_id: Optional[str], # None when using graph_data directly (Phase 5)
profile_id: str,
node_states: List[NodeExecutionState],
status: str,
@ -853,7 +853,7 @@ def save_execution_state(
Args:
execution_id: UUID der Execution
workflow_id: UUID des Workflows
workflow_id: UUID des Workflows (None wenn graph_data direkt verwendet wird)
profile_id: UUID des Profils
node_states: Liste aller NodeExecutionState
status: 'completed' | 'failed' | 'partial'