Files
cleveragents-core/robot/helper_actor_compiler.py
HAL9000 02e51b9298
CI / build (pull_request) Successful in 34s
CI / helm (pull_request) Successful in 38s
CI / lint (pull_request) Successful in 45s
CI / typecheck (pull_request) Successful in 1m4s
CI / security (pull_request) Successful in 1m10s
CI / quality (pull_request) Successful in 1m16s
CI / push-validation (pull_request) Successful in 18s
CI / unit_tests (pull_request) Successful in 6m32s
CI / docker (pull_request) Successful in 1m37s
CI / coverage (pull_request) Successful in 13m26s
CI / integration_tests (pull_request) Successful in 23m55s
CI / status-check (pull_request) Successful in 4s
fix(actor): fix lint errors and add regression tests for lsp_binding field (#1432)
- Remove trailing whitespace from blank lines in _extract_lsp_bindings()
  (W293 at lines 160, 164, 167, 172, 183, 188, 203)
- Simplify over-long line 180 (E501): auto_detect=node.lsp_binding.auto
- Add Behave scenario verifying NodeLspBinding typed field populates
  CompilationMetadata.lsp_bindings (features/actor_lsp_binding_field.feature)
- Add corresponding @given step using NodeLspBinding in actor_compiler_steps.py
- Add Robot Framework regression test case and lsp-binding-field helper command
  confirming node.lsp_binding is read by compile_actor()
- Update CHANGELOG.md and CONTRIBUTORS.md

ISSUES CLOSED: #1432
2026-05-30 00:36:20 -04:00

200 lines
6.7 KiB
Python

"""Robot Framework helper for actor compiler smoke tests.
Provides a CLI interface for Robot to invoke the actor compiler on
YAML-defined GRAPH actors and inspect compilation metadata.
Usage:
python robot/helper_actor_compiler.py compile <yaml_file>
python robot/helper_actor_compiler.py compile-fail <yaml_file>
python robot/helper_actor_compiler.py metadata <yaml_file>
"""
from __future__ import annotations
import json
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)
from cleveragents.actor.compiler import compile_actor # noqa: E402
from cleveragents.actor.schema import ActorConfigSchema # noqa: E402
def main() -> int:
"""Entry point called by Robot Framework ``Run Process``."""
if len(sys.argv) < 3:
print("Usage: helper_actor_compiler.py <compile|compile-fail|metadata> <file>")
return 1
command = sys.argv[1]
yaml_path = sys.argv[2]
if command == "compile":
try:
config = ActorConfigSchema.from_yaml_file(yaml_path)
compiled = compile_actor(config)
print(f"actor-compiler-ok: {compiled.name}")
print(f"nodes: {len(compiled.nodes)}")
print(f"edges: {len(compiled.edges)}")
print(f"entry: {compiled.entry_point}")
return 0
except Exception as exc:
print(f"actor-compiler-fail: {exc}")
return 1
if command == "compile-fail":
try:
config = ActorConfigSchema.from_yaml_file(yaml_path)
compile_actor(config)
print("actor-compiler-unexpected-success")
return 1
except Exception as exc:
print(f"actor-compiler-expected-fail: {exc}")
return 0
if command == "metadata":
try:
config = ActorConfigSchema.from_yaml_file(yaml_path)
compiled = compile_actor(config)
meta = compiled.metadata.model_dump(mode="json")
print(f"actor-compiler-metadata: {json.dumps(meta)}")
return 0
except Exception as exc:
print(f"actor-compiler-fail: {exc}")
return 1
if command == "cycle-detect":
# Test cross-actor cycle detection using actor_ref top-level field.
# Creates two mutually-referencing actors and verifies SubgraphCycleError
# is raised, confirming the fix for issue #1431.
from cleveragents.actor.compiler import SubgraphCycleError
from cleveragents.actor.schema import (
ActorType,
EdgeDefinition,
NodeDefinition,
NodeType,
RouteDefinition,
)
def _make_actor(
name: str, subgraph_ref: str | None = None
) -> ActorConfigSchema:
if subgraph_ref is not None:
nodes = [
NodeDefinition(
id="start",
type=NodeType.AGENT,
name="Start",
description="Start",
config={"agent": "default"},
),
NodeDefinition(
id="sub",
type=NodeType.SUBGRAPH,
name="Sub",
description="Subgraph",
config={},
actor_ref=subgraph_ref,
),
]
edges = [EdgeDefinition(from_node="start", to_node="sub")]
entry, exits = "start", ["sub"]
else:
nodes = [
NodeDefinition(
id="leaf",
type=NodeType.AGENT,
name="Leaf",
description="Leaf",
config={"agent": "default"},
)
]
edges = []
entry, exits = "leaf", ["leaf"]
route = RouteDefinition(
nodes=nodes, edges=edges, entry_node=entry, exit_nodes=exits
)
return ActorConfigSchema(
name=name,
type=ActorType.GRAPH,
description=f"Test actor {name}",
model="gpt-4",
route=route,
)
actor_a = _make_actor("test/actor-a", subgraph_ref="test/actor-b")
actor_b = _make_actor("test/actor-b", subgraph_ref="test/actor-a")
registry = {"test/actor-a": actor_a, "test/actor-b": actor_b}
try:
compile_actor(actor_a, actor_resolver=registry.get)
print(
"actor-compiler-cycle-not-detected: FAIL — expected SubgraphCycleError"
)
return 1
except SubgraphCycleError as exc:
print(f"actor-compiler-cycle-detected: {exc}")
return 0
except Exception as exc:
print(f"actor-compiler-unexpected-error: {exc}")
return 1
if command == "lsp-binding-field":
# Regression test for issue #1432: _extract_lsp_bindings() must read
# from node.lsp_binding (NodeLspBinding typed field), not only config dict.
from cleveragents.actor.schema import (
ActorType,
NodeDefinition,
NodeLspBinding,
NodeType,
RouteDefinition,
)
node = NodeDefinition(
id="coder",
type=NodeType.AGENT,
name="Coder",
description="Code writing node with typed LSP binding",
config={"agent": "coder_agent"},
lsp_binding=NodeLspBinding(
server="local/pyright",
languages=["python"],
auto=False,
),
)
route = RouteDefinition(
nodes=[node],
edges=[],
entry_node="coder",
exit_nodes=["coder"],
)
config = ActorConfigSchema(
name="workflows/lsp_typed",
type=ActorType.GRAPH,
description="Test actor with typed lsp_binding field",
model="gpt-4",
route=route,
)
try:
compiled = compile_actor(config)
bindings = compiled.metadata.lsp_bindings
if len(bindings) == 1 and bindings[0].lsp_server_name == "local/pyright":
print("actor-compiler-lsp-binding-ok")
print(f"lsp-server: {bindings[0].lsp_server_name}")
return 0
print(f"actor-compiler-lsp-binding-fail: got {bindings}")
return 1
except Exception as exc:
print(f"actor-compiler-fail: {exc}")
return 1
print(f"Unknown command: {command}")
return 1
if __name__ == "__main__":
sys.exit(main())