e476d2de0e
Extend actor YAML schema to support hierarchical graphs with explicit node types (agent, tool, conditional, subgraph), per-node LSP bindings (lsp_binding with server, languages, auto, capabilities), and tool-source references (skills, mcp_servers, agent_skills). Add schema validation for namespaced actor references, duplicate node IDs, edge target existence, and graph reachability — all nodes must be reachable from entry_node via explicit edges or conditional node routing targets. Update loader to report YAML parse errors with precise line/column positions and schema validation errors with dotted field paths and remediation hints pointing to docs/reference/actor_config.md. Add docs/reference/actor_config.md as the practical configuration reference covering hierarchical graph examples, node type table, topology rules, and common error cases with fix guidance. Refresh examples/actors/graph_workflow.yaml to replace deprecated actor_path with actor_ref. Add benchmarks/actor_yaml_bench.py for schema load overhead. Tests: 95 Behave scenarios, 10 Robot smoke tests (including hierarchical loader smoke test), security scan clean, coverage 99% (threshold 97%). ISSUES CLOSED: #157
143 lines
3.6 KiB
Python
143 lines
3.6 KiB
Python
"""ASV benchmarks for hierarchical actor YAML parsing performance.
|
|
|
|
Measures the performance of:
|
|
- Parsing actors with per-node LSP bindings
|
|
- Parsing actors with tool-source references
|
|
- Parsing actors with subgraph actor_ref
|
|
- Parsing actors with full hierarchical features combined
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
import cleveragents # noqa: E402
|
|
|
|
importlib.reload(cleveragents)
|
|
|
|
from cleveragents.actor.schema import ActorConfigSchema # noqa: E402
|
|
|
|
|
|
def _graph_data(nodes: list[dict], *, skills: list[str] | None = None) -> dict:
|
|
node_ids = [n["id"] for n in nodes]
|
|
edges = []
|
|
for i in range(len(node_ids) - 1):
|
|
edges.append({"from_node": node_ids[i], "to_node": node_ids[i + 1]})
|
|
data: dict = {
|
|
"name": "bench/hierarchy",
|
|
"type": "graph",
|
|
"description": "Benchmark hierarchical actor",
|
|
"model": "gpt-4",
|
|
"route": {
|
|
"nodes": nodes,
|
|
"edges": edges,
|
|
"entry_node": node_ids[0],
|
|
"exit_nodes": [node_ids[-1]],
|
|
},
|
|
}
|
|
if skills:
|
|
data["skills"] = skills
|
|
return data
|
|
|
|
|
|
_AGENT_NODE = {
|
|
"id": "agent",
|
|
"type": "agent",
|
|
"name": "Agent",
|
|
"description": "Agent node",
|
|
"config": {"model": "gpt-4"},
|
|
}
|
|
|
|
_LSP_NODE = {
|
|
"id": "lsp_agent",
|
|
"type": "agent",
|
|
"name": "LSP Agent",
|
|
"description": "Agent with LSP binding",
|
|
"config": {"model": "gpt-4"},
|
|
"lsp_binding": {
|
|
"server": "local/pyright",
|
|
"languages": ["python"],
|
|
"capabilities": ["diagnostics", "hover"],
|
|
},
|
|
}
|
|
|
|
_TS_NODE = {
|
|
"id": "ts_agent",
|
|
"type": "agent",
|
|
"name": "Tool Source Agent",
|
|
"description": "Agent with tool sources",
|
|
"config": {},
|
|
"tool_sources": [
|
|
{"type": "skill", "name": "local/file-ops"},
|
|
{"type": "mcp", "name": "local/filesystem"},
|
|
{"type": "builtin", "group": "git_operations"},
|
|
],
|
|
}
|
|
|
|
_SUBGRAPH_NODE = {
|
|
"id": "sub",
|
|
"type": "subgraph",
|
|
"name": "Sub",
|
|
"description": "Subgraph node",
|
|
"actor_ref": "local/code-reviewer",
|
|
}
|
|
|
|
|
|
class TimeLspBindingParse:
|
|
"""Benchmark parsing actors with LSP bindings."""
|
|
|
|
def setup(self) -> None:
|
|
self.data = _graph_data([_LSP_NODE])
|
|
|
|
def time_parse_lsp_binding(self) -> None:
|
|
ActorConfigSchema.model_validate(self.data)
|
|
|
|
|
|
class TimeToolSourcesParse:
|
|
"""Benchmark parsing actors with tool-source references."""
|
|
|
|
def setup(self) -> None:
|
|
self.data = _graph_data([_TS_NODE])
|
|
|
|
def time_parse_tool_sources(self) -> None:
|
|
ActorConfigSchema.model_validate(self.data)
|
|
|
|
|
|
class TimeSubgraphRefParse:
|
|
"""Benchmark parsing actors with subgraph actor_ref."""
|
|
|
|
def setup(self) -> None:
|
|
self.data = _graph_data(
|
|
[_AGENT_NODE, _SUBGRAPH_NODE],
|
|
)
|
|
|
|
def time_parse_subgraph_ref(self) -> None:
|
|
ActorConfigSchema.model_validate(self.data)
|
|
|
|
|
|
class TimeFullHierarchyParse:
|
|
"""Benchmark parsing a full hierarchical actor with all features."""
|
|
|
|
def setup(self) -> None:
|
|
self.data = _graph_data(
|
|
[_LSP_NODE, _TS_NODE, _SUBGRAPH_NODE],
|
|
skills=["local/file-ops", "local/git-ops"],
|
|
)
|
|
self.data["lsp"] = ["local/pyright"]
|
|
self.data["lsp_capabilities"] = ["diagnostics"]
|
|
|
|
def time_parse_full_hierarchy(self) -> None:
|
|
ActorConfigSchema.model_validate(self.data)
|
|
|
|
|
|
time_lsp = TimeLspBindingParse()
|
|
time_ts = TimeToolSourcesParse()
|
|
time_sg = TimeSubgraphRefParse()
|
|
time_full = TimeFullHierarchyParse()
|