Spaces:
Runtime error
Runtime error
Download dev-agent/app/validators/workflow_validator.py from BeastGokul/MVP: direct link, hf CLI and curl.
- Browser
- Download file 5 kB
-
https://huggingface.co/spaces/BeastGokul/MVP/resolve/main/dev-agent/app/validators/workflow_validator.py
- Command line
-
hf download hf://spaces/BeastGokul/MVP/dev-agent/app/validators/workflow_validator.py
-
curl -L -o workflow_validator.py https://huggingface.co/spaces/BeastGokul/MVP/resolve/main/dev-agent/app/validators/workflow_validator.py
5 kB
| import re | |
| from typing import List, Set, Dict, Any | |
| from ..schemas.agent_spec import AgentSpec | |
| from ..schemas.conversation import ValidationError, ValidationResult | |
| class WorkflowValidator: | |
| def validate(self, spec: AgentSpec) -> ValidationResult: | |
| errors = [] | |
| steps_dict = {s.id: s for s in spec.steps} | |
| step_ids = list(steps_dict.keys()) | |
| # 1. Duplicate step IDs | |
| all_ids = [s.id for s in spec.steps] | |
| if len(all_ids) != len(set(all_ids)): | |
| errors.append(ValidationError( | |
| code="DUPLICATE_STEP_ID", | |
| message="Duplicate step IDs found in the workflow", | |
| path="steps" | |
| )) | |
| # 2. Depends_on exists | |
| for step in spec.steps: | |
| for dep in step.depends_on: | |
| if dep not in steps_dict: | |
| errors.append(ValidationError( | |
| code="MISSING_DEPENDENCY", | |
| message=f"Step '{step.id}' depends on missing step '{dep}'", | |
| path=f"steps.{step.id}.depends_on" | |
| )) | |
| # 3. Circular dependency detection (Cycle Detection using DFS) | |
| visited = {} # state: 0=unvisited, 1=visiting, 2=visited | |
| for s_id in step_ids: | |
| visited[s_id] = 0 | |
| def dfs(s_id: str, path: List[str]) -> bool: | |
| visited[s_id] = 1 # visiting | |
| step = steps_dict[s_id] | |
| for dep in step.depends_on: | |
| if dep not in steps_dict: | |
| continue | |
| if visited[dep] == 1: | |
| cycle_path = " -> ".join(path + [dep]) | |
| errors.append(ValidationError( | |
| code="CIRCULAR_DEPENDENCY", | |
| message=f"Circular dependency detected: {cycle_path}", | |
| path=f"steps.{s_id}.depends_on" | |
| )) | |
| return True | |
| elif visited[dep] == 0: | |
| if dfs(dep, path + [dep]): | |
| return True | |
| visited[s_id] = 2 # visited | |
| return False | |
| for s_id in step_ids: | |
| if visited[s_id] == 0: | |
| dfs(s_id, [s_id]) | |
| # 4. Helper to find all ancestors of a step | |
| def get_all_ancestors(s_id: str, memo: Dict[str, Set[str]]) -> Set[str]: | |
| if s_id in memo: | |
| return memo[s_id] | |
| ancestors = set() | |
| step = steps_dict.get(s_id) | |
| if step: | |
| for dep in step.depends_on: | |
| ancestors.add(dep) | |
| ancestors.update(get_all_ancestors(dep, memo)) | |
| memo[s_id] = ancestors | |
| return ancestors | |
| ancestors_memo = {} | |
| # 5. Data flow validation (regex check for $step_id.field references) | |
| ref_pattern = re.compile(r"^\$(\w+)(?:\.(\w+))?$") | |
| def scan_for_refs(val: Any) -> List[tuple]: | |
| refs = [] | |
| if isinstance(val, str): | |
| match = ref_pattern.match(val) | |
| if match: | |
| refs.append((match.group(1), match.group(2))) | |
| elif isinstance(val, dict): | |
| for k, v in val.items(): | |
| refs.extend(scan_for_refs(v)) | |
| elif isinstance(val, list): | |
| for item in val: | |
| refs.extend(scan_for_refs(item)) | |
| return refs | |
| for step in spec.steps: | |
| refs = scan_for_refs(step.input) | |
| for ref_step_id, ref_field in refs: | |
| if ref_step_id not in steps_dict: | |
| errors.append(ValidationError( | |
| code="INVALID_DATA_FLOW", | |
| message=f"Step '{step.id}' references input from non-existent step '{ref_step_id}'", | |
| path=f"steps.{step.id}.input" | |
| )) | |
| continue | |
| ancestors = get_all_ancestors(step.id, ancestors_memo) | |
| if ref_step_id != step.id and ref_step_id not in ancestors: | |
| errors.append(ValidationError( | |
| code="INVALID_DATA_FLOW", | |
| message=f"Step '{step.id}' references output of step '{ref_step_id}' but does not depend on it", | |
| path=f"steps.{step.id}.depends_on" | |
| )) | |
| ref_step = steps_dict[ref_step_id] | |
| if ref_field and ref_step.output: | |
| if ref_field not in ref_step.output: | |
| errors.append(ValidationError( | |
| code="INVALID_DATA_FLOW", | |
| message=f"Step '{step.id}' references field '{ref_field}' which is not defined in step '{ref_step_id}' outputs", | |
| path=f"steps.{step.id}.input" | |
| )) | |
| return ValidationResult(valid=len(errors) == 0, errors=errors) | |