fix(cli): derive actor name from config file instead of positional argument #1189
@@ -34,6 +34,7 @@ from cleveragents.cli.commands.actor import app as actor_app # noqa: E402
|
||||
from cleveragents.domain.models.core.actor import Actor # noqa: E402
|
||||
|
||||
_VALID_CONFIG = {
|
||||
"name": "local/bench",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4",
|
||||
"temperature": 0.5,
|
||||
@@ -84,7 +85,7 @@ class ActorCLIAddSuite:
|
||||
|
||||
def time_add_from_config(self) -> None:
|
||||
"""Benchmark add --config end-to-end."""
|
||||
_runner.invoke(actor_app, ["add", "local/bench", "--config", self._path])
|
||||
_runner.invoke(actor_app, ["add", "--config", self._path])
|
||||
|
||||
|
||||
class ActorCLIListSuite:
|
||||
|
||||
@@ -59,6 +59,7 @@ def step_impl(context):
|
||||
@given("I have an actor JSON config file")
|
||||
def step_impl(context):
|
||||
context.actor_config_data = {
|
||||
"name": "local/test-actor",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"temperature": 0.5,
|
||||
@@ -76,6 +77,7 @@ def step_impl(context):
|
||||
@given("I have an actor JSON config file with options")
|
||||
def step_impl(context):
|
||||
context.actor_config_data = {
|
||||
"name": "local/test-actor",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o",
|
||||
"options": {"temperature": 0.4, "enabled": True},
|
||||
@@ -104,11 +106,15 @@ def step_impl(context):
|
||||
@given("I have an actor YAML config file returning empty data")
|
||||
def step_impl(context):
|
||||
context.actor_config_data = {
|
||||
"name": "local/existing-actor",
|
||||
"provider": "existing-provider",
|
||||
"model": "existing-model",
|
||||
}
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".yaml") as handle:
|
||||
handle.write(b"provider: existing-provider\nmodel: existing-model\n")
|
||||
handle.write(
|
||||
b"name: local/existing-actor\n"
|
||||
b"provider: existing-provider\nmodel: existing-model\n"
|
||||
)
|
||||
handle.flush()
|
||||
context.actor_config_path = Path(handle.name)
|
||||
_register_cleanup(context, context.actor_config_path)
|
||||
@@ -117,6 +123,7 @@ def step_impl(context):
|
||||
@given("I have an actor YAML-only config file")
|
||||
def step_impl(context):
|
||||
context.actor_config_data = {
|
||||
"name": "local/test-actor",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"yaml_only": True,
|
||||
@@ -124,7 +131,10 @@ def step_impl(context):
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, suffix=".yaml", mode="w", encoding="utf-8"
|
||||
) as handle:
|
||||
handle.write("provider: openai\nmodel: gpt-4o-mini\nyaml_only: true\n")
|
||||
handle.write(
|
||||
"name: local/test-actor\nprovider: openai\n"
|
||||
"model: gpt-4o-mini\nyaml_only: true\n"
|
||||
)
|
||||
handle.flush()
|
||||
context.actor_config_path = Path(handle.name)
|
||||
_register_cleanup(context, context.actor_config_path)
|
||||
@@ -133,6 +143,7 @@ def step_impl(context):
|
||||
@given("I have an actor graph config file")
|
||||
def step_impl(context):
|
||||
context.actor_config_data = {
|
||||
"name": "service/graph-actor",
|
||||
"provider": "graph-provider",
|
||||
"model": "graph-model",
|
||||
"graph": {"nodes": ["start"], "edges": []},
|
||||
@@ -149,7 +160,7 @@ def step_impl(context):
|
||||
@given("I have an empty actor YAML config file")
|
||||
def step_impl(context):
|
||||
context.actor_config_data = {}
|
||||
context.expected_error = "provider is required"
|
||||
context.expected_error = "Config file must contain a 'name' field"
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, suffix=".yaml", mode="w", encoding="utf-8"
|
||||
) as handle:
|
||||
@@ -162,6 +173,7 @@ def step_impl(context):
|
||||
@given("I have an unsafe actor JSON config file")
|
||||
def step_impl(context):
|
||||
context.actor_config_data = {
|
||||
"name": "local/test-actor",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"unsafe": True,
|
||||
@@ -181,7 +193,7 @@ def step_impl(context):
|
||||
mock_actor_service = MagicMock()
|
||||
mock_actor_registry = MagicMock()
|
||||
mock_get_services.return_value = (mock_actor_service, mock_actor_registry)
|
||||
context.result = context.runner.invoke(actor_app, ["add", "test-actor"])
|
||||
context.result = context.runner.invoke(actor_app, ["add"])
|
||||
context.mock_actor_registry = mock_actor_registry
|
||||
context.expected_error = "Config file is required"
|
||||
|
||||
@@ -216,7 +228,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
],
|
||||
@@ -232,8 +243,8 @@ def step_impl(context):
|
||||
context.expected_allow_unsafe = False
|
||||
if not isinstance(context.actor_config_data, dict):
|
||||
context.expected_error = "Config must be a JSON/YAML object"
|
||||
elif context.actor_config_data == {}:
|
||||
context.expected_error = "provider is required"
|
||||
elif not context.actor_config_data.get("name"):
|
||||
context.expected_error = "Config file must contain a 'name' field"
|
||||
elif isinstance(
|
||||
context.actor_config_data, dict
|
||||
) and context.actor_config_data.get("unsafe"):
|
||||
@@ -273,7 +284,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
*option_args,
|
||||
@@ -315,7 +325,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
*option_args,
|
||||
@@ -357,7 +366,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
*option_args,
|
||||
@@ -381,7 +389,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
"--option",
|
||||
@@ -399,7 +406,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
"--option",
|
||||
@@ -439,7 +445,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
"--unsafe",
|
||||
@@ -480,7 +485,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"service/graph-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
],
|
||||
@@ -499,7 +503,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(missing_path),
|
||||
],
|
||||
@@ -509,7 +512,11 @@ def step_impl(context):
|
||||
|
||||
@when("I run actor add with business rule violation")
|
||||
def step_impl(context):
|
||||
config_data = {"provider": "openai", "model": "gpt-4o"}
|
||||
config_data = {
|
||||
"name": "local/test-actor",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o",
|
||||
}
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, suffix=".json", mode="w", encoding="utf-8"
|
||||
) as handle:
|
||||
@@ -528,7 +535,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(config_path),
|
||||
],
|
||||
@@ -546,7 +552,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"service/unsafe",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
],
|
||||
@@ -585,7 +590,6 @@ def step_impl(context):
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"service/unsafe",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
],
|
||||
|
||||
@@ -209,7 +209,6 @@ def step_add_update(context: Any) -> None:
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
"--update",
|
||||
@@ -246,7 +245,6 @@ def step_add_format_json(context: Any) -> None:
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
"--format",
|
||||
@@ -268,7 +266,6 @@ def step_add_format_yaml(context: Any) -> None:
|
||||
actor_app,
|
||||
[
|
||||
"add",
|
||||
"test-actor",
|
||||
"--config",
|
||||
str(context.actor_config_path),
|
||||
"--format",
|
||||
|
||||
@@ -48,6 +48,7 @@ M2 Full Actor Compiler And LLM Integration
|
||||
Create File ${actor_yaml_path} ${actor_yaml}
|
||||
${actor_config}= Catenate SEPARATOR=\n
|
||||
... {
|
||||
... "name": "${ACTOR_NAME}",
|
||||
... "provider": "openai",
|
||||
... "model": "gpt-4",
|
||||
... "options": {"temperature": 0.2}
|
||||
@@ -55,7 +56,7 @@ M2 Full Actor Compiler And LLM Integration
|
||||
${config_path}= Set Variable ${SUITE_HOME}${/}actor_config.json
|
||||
Create File ${config_path} ${actor_config}
|
||||
${r_actor}= Run CleverAgents Command
|
||||
... actor add ${ACTOR_NAME} --config ${config_path} --format plain
|
||||
... actor add --config ${config_path} --format plain
|
||||
Should Not Contain ${r_actor.stdout}${r_actor.stderr} Traceback
|
||||
Log Actor registration: ${r_actor.stdout}
|
||||
|
||||
|
||||
@@ -104,12 +104,13 @@ WF14 E2E Actor Add For Namespace
|
||||
... the current ActorService validation.
|
||||
${actor_name}= Set Variable local/team-rev-${RUN_SUFFIX}
|
||||
${yaml_content}= Catenate SEPARATOR=\n
|
||||
... name: ${actor_name}
|
||||
... provider: openai
|
||||
... model: gpt-4
|
||||
... description: Team code reviewer actor for collaborative reviews
|
||||
${yaml_file}= Set Variable ${SUITE_HOME}${/}team-reviewer-actor.yaml
|
||||
Create File ${yaml_file} ${yaml_content}
|
||||
${add_result}= Run CleverAgents Command actor add ${actor_name} --config ${yaml_file}
|
||||
${add_result}= Run CleverAgents Command actor add --config ${yaml_file}
|
||||
Output Should Contain ${add_result} team-rev
|
||||
# Use --format plain to avoid table truncation
|
||||
${list_result}= Run CleverAgents Command actor list --format plain
|
||||
|
||||
@@ -167,6 +167,7 @@ def actor_add_config_cli() -> None:
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(
|
||||
{
|
||||
"name": "local/m2-cli-actor",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4",
|
||||
"options": {"temperature": 0.5},
|
||||
@@ -177,7 +178,6 @@ def actor_add_config_cli() -> None:
|
||||
result = run_cli(
|
||||
"actor",
|
||||
"add",
|
||||
"local/m2-cli-actor",
|
||||
"--config",
|
||||
config_path,
|
||||
"--format",
|
||||
|
||||
@@ -367,7 +367,6 @@ def _print_role_warnings(config_blob: dict[str, Any]) -> None:
|
||||
|
||||
@app.command()
|
||||
def add(
|
||||
name: Annotated[str, typer.Argument(help="Name for the actor")],
|
||||
config: Annotated[
|
||||
Path | None,
|
||||
typer.Option("--config", "-c", help="Path to JSON/YAML actor config"),
|
||||
@@ -397,14 +396,16 @@ def add(
|
||||
) -> None:
|
||||
"""Add a new actor configuration.
|
||||
|
||||
Actors are defined via YAML configuration files (``--config``).
|
||||
The actor name follows the ``[[server:]namespace/]name`` format;
|
||||
names without a namespace default to ``local/``.
|
||||
The actor is fully defined by the YAML configuration file specified
|
||||
with ``--config``, including the ``name`` field which determines the
|
||||
actor's registered name.
|
||||
|
||||
Signature: ``agents actor add (--config|-c) <FILE> [--update]``
|
||||
|
||||
Examples:
|
||||
agents actor add local/my-actor --config ./actors/my-actor.yaml
|
||||
agents actor add local/my-actor --config ./actors/my-actor.yaml --update
|
||||
agents actor add local/my-actor --config actor.yaml --format json
|
||||
agents actor add --config ./actors/my-actor.yaml
|
||||
agents actor add --config ./actors/my-actor.yaml --update
|
||||
agents actor add --config actor.yaml --format json
|
||||
"""
|
||||
service, registry = _get_services()
|
||||
config_blob = _load_config(config)
|
||||
@@ -415,6 +416,12 @@ def add(
|
||||
if config_blob is None:
|
||||
raise typer.BadParameter("Config file is required for actor add")
|
||||
|
||||
name = config_blob.get("name")
|
||||
if not name or not isinstance(name, str):
|
||||
raise typer.BadParameter(
|
||||
"Config file must contain a 'name' field for the actor name"
|
||||
)
|
||||
|
||||
resolved, canonical_blob, requires_confirmation = _canonicalize_actor_config(
|
||||
name=name,
|
||||
config_blob=config_blob,
|
||||
|
||||
Reference in New Issue
Block a user