mirror of
https://github.com/OpenBMB/ChatDev.git
synced 2026-04-25 11:18:06 +00:00
121 lines
4.0 KiB
Python
Executable File
121 lines
4.0 KiB
Python
Executable File
"""Utilities for loading, validating design_0.4.0 workflows."""
|
|
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
|
|
from runtime.bootstrap.schema import ensure_schema_registry_populated
|
|
from check.check_yaml import validate_design
|
|
from check.check_workflow import check_workflow_structure
|
|
from entity.config_loader import prepare_design_mapping
|
|
from entity.configs import DesignConfig, ConfigError
|
|
from schema_registry import iter_node_schemas
|
|
from utils.io_utils import read_yaml
|
|
|
|
|
|
ensure_schema_registry_populated()
|
|
|
|
|
|
class DesignError(RuntimeError):
|
|
"""Raised when a workflow design cannot be loaded or validated."""
|
|
|
|
|
|
|
|
def _allowed_node_types() -> set[str]:
|
|
names = set(iter_node_schemas().keys())
|
|
if not names:
|
|
raise DesignError("No node types registered; cannot validate workflow")
|
|
return names
|
|
|
|
|
|
def _ensure_supported(graph: Dict[str, Any]) -> None:
|
|
"""Ensure the MVP constraints are satisfied for the provided graph."""
|
|
for node in graph.get("nodes", []) or []:
|
|
nid = node.get("id")
|
|
ntype = node.get("type")
|
|
allowed = _allowed_node_types()
|
|
if ntype not in allowed:
|
|
raise DesignError(
|
|
f"Unsupported node type '{ntype}' for node '{nid}'. Only {allowed} nodes are supported."
|
|
)
|
|
if ntype == "agent":
|
|
agent_cfg = node.get("config") or {}
|
|
if not isinstance(agent_cfg, dict):
|
|
raise DesignError(f"Agent node '{nid}' config must be an object")
|
|
for legacy_key in ["memory"]:
|
|
if legacy_key in agent_cfg:
|
|
raise DesignError(
|
|
f"'{legacy_key}' is deprecated. Use the new graph-level memory stores for node '{nid}'."
|
|
)
|
|
|
|
|
|
def load_config(
|
|
config_path: Path,
|
|
*,
|
|
fn_module: Optional[str] = None,
|
|
set_defaults: bool = True,
|
|
vars_override: Optional[Dict[str, Any]] = None,
|
|
) -> DesignConfig:
|
|
"""Load, validate, and sanity-check a workflow file."""
|
|
|
|
try:
|
|
raw_data = read_yaml(config_path)
|
|
except FileNotFoundError as exc:
|
|
raise DesignError(f"Design file not found: {config_path}") from exc
|
|
|
|
if not isinstance(raw_data, dict):
|
|
raise DesignError("YAML root must be a mapping")
|
|
|
|
if vars_override:
|
|
merged_vars = dict(raw_data.get("vars") or {})
|
|
merged_vars.update(vars_override)
|
|
raw_data = dict(raw_data)
|
|
raw_data["vars"] = merged_vars
|
|
|
|
data = prepare_design_mapping(raw_data, source=str(config_path))
|
|
|
|
schema_errors = validate_design(data, set_defaults=set_defaults, fn_module_ref=fn_module)
|
|
if schema_errors:
|
|
formatted = "\n".join(f"- {err}" for err in schema_errors)
|
|
raise DesignError(f"Design validation failed for '{config_path}':\n{formatted}")
|
|
|
|
try:
|
|
design = DesignConfig.from_dict(data, path="root")
|
|
except ConfigError as exc:
|
|
raise DesignError(f"Design parsing failed for '{config_path}': {exc}") from exc
|
|
|
|
logic_errors = check_workflow_structure(data)
|
|
if logic_errors:
|
|
formatted = "\n".join(f"- {err}" for err in logic_errors)
|
|
raise DesignError(f"Workflow logical issues detected for '{config_path}':\n{formatted}")
|
|
else:
|
|
print("Workflow OK.")
|
|
|
|
graph = data.get("graph") or {}
|
|
_ensure_supported(graph)
|
|
|
|
return design
|
|
|
|
|
|
def check_config(yaml_content: Any) -> str:
|
|
if not isinstance(yaml_content, dict):
|
|
return "YAML root must be a mapping"
|
|
|
|
# Skip placeholder resolution during save - users may configure env vars at runtime
|
|
# Use yaml_content directly instead of prepare_design_mapping()
|
|
schema_errors = validate_design(yaml_content)
|
|
if schema_errors:
|
|
formatted = "\n".join(f"- {err}" for err in schema_errors)
|
|
return formatted
|
|
|
|
logic_errors = check_workflow_structure(yaml_content)
|
|
if logic_errors:
|
|
formatted = "\n".join(f"- {err}" for err in logic_errors)
|
|
return formatted
|
|
|
|
graph = yaml_content.get("graph") or {}
|
|
try:
|
|
_ensure_supported(graph)
|
|
except Exception as e:
|
|
return str(e)
|
|
|
|
return "" |