Files
hurui200320 76c4c74201
CI / lint (pull_request) Successful in 36s
CI / quality (pull_request) Successful in 40s
CI / build (pull_request) Successful in 44s
CI / security (pull_request) Successful in 51s
CI / typecheck (pull_request) Successful in 52s
CI / integration_tests (pull_request) Successful in 1m3s
CI / unit_tests (pull_request) Successful in 3m38s
CI / coverage (pull_request) Successful in 3m36s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 1m2s
CI / typecheck (push) Successful in 1m2s
CI / security (push) Successful in 1m2s
CI / quality (push) Successful in 40s
CI / integration_tests (push) Successful in 1m8s
CI / build (push) Successful in 49s
CI / unit_tests (push) Successful in 3m40s
CI / coverage (push) Successful in 3m36s
CI / status-check (push) Successful in 3s
feat(create_executor): implement create_executor() factory and Executor.execute() returning ActorResult
- **`src/cleveractors/runtime_types.py`** — `ActorResult` and `NodeUsage` dataclasses
  extracted to break circular imports (CONTRIBUTING.md §Import Guidelines). 100% coverage.
- **`src/cleveractors/runtime.py`** — `create_executor()` factory, `Executor` class
  with `execute()` dispatching to module-level functions in `runtime_dispatch`.
  Imports `_execute_*` at module level (no function-local imports). 100% coverage.
- **`src/cleveractors/runtime_dispatch.py`** — Four dispatch functions: `_execute_llm`,
  `_execute_graph`, `_execute_tool`, `_execute_multi_actor`. All imports at module
  level except `from cleveractors.runtime import Executor` inside `_execute_multi_actor`
  (the only remaining circular dep — Executor calls _execute_multi_actor which creates
  a new Executor; cannot be resolved without further restructuring). 99.69% coverage
  (only the `if TYPE_CHECKING:` guard line is uncovered — not a real gap).
- **`src/cleveractors/runtime_tokens.py`** — Token estimation via tiktoken with
  heuristic fallback. 100% coverage.

Key design decisions and fixes:
- **Circular import resolution**: `ActorResult`/`NodeUsage` moved to `runtime_types.py`;
  both `runtime.py` and `runtime_dispatch.py` import from it at module level.
- **`messages` forwarded to `_execute_llm`**: builds `context={"conversation_history":[...]}`
  passed to `LLMAgent.process_message` for multi-turn support.
- **`parallel_execution` aligned with `PureGraphConfig` default (True)**:
  reads from legacy `route` dict or v2.0 `routes.main` dict; defaults to True.
- **Factory normalization**: always merges `actors` into `agents` (actors take
  precedence) so configs using both keys work correctly.
- **Generic exception in agent creation re-raised as `ConfigurationError`**:
  no double-logging; exception message preserved in `ConfigurationError`.
- **`cleveragents_block` guarded against `None`**: uses `or {}` coercion.
- **Type annotations added**: `top_provider`, `top_model`, `top_sp`,
  `temperature_raw`, `max_tokens_raw`, `config_block`, `tools` in dispatch functions.
- **No `finally` in `_execute_tool`**: documented with comment (ToolAgent has no cleanup).
- **Dead step removed**: `step_rxe_invalid_node_type` was unreferenced and incorrect.
- **BDD coverage**: `parallel_execution=False` override verified via `PureGraphConfig`
  constructor args; conversation history forwarding verified; AC7 immutability for
  multi-actor path verified.

-  `nox -e lint` — passes
-  `nox -e format` — passes
-  `nox -e typecheck` — passes (0 errors, 1 expected warning)
-  `nox -e unit_tests` — 2113 scenarios pass, 0 failures, 0 skipped
-  `nox -e integration_tests` — 76 tests pass
-  `nox -e coverage_report` — 97.21% (9870 covered, 283 missing, 10153 total)
  PR-modified files: `runtime_types.py` 100%, `runtime.py` 100%,
  `runtime_dispatch.py` 99.69% (1 uncoverable TYPE_CHECKING guard), `runtime_tokens.py` 100%

ISSUES CLOSED: #13
2026-06-10 09:41:39 +00:00

185 lines
5.6 KiB
Python

"""Step definitions for Runtime Executor config setup (Given steps).
Extracted from runtime_coverage_steps.py to stay under the 500-line
file limit (CONTRIBUTING.md General Principles).
"""
from __future__ import annotations
from typing import Any
from behave import given
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _build_base_config(type_name: str) -> dict[str, Any]:
return {
"name": f"test_{type_name}",
"type": type_name,
}
# ---------------------------------------------------------------------------
# Given — environment and configuration
# ---------------------------------------------------------------------------
@given("the runtime test environment is initialized")
def step_runtime_init(context: Any) -> None:
context.test_executor = None
context.test_result = None
context.test_error = None
@given('a valid actor config dict with type "unknown_type"')
def step_config_unknown(context: Any) -> None:
context.config_dict = _build_base_config("unknown_type")
@given('a valid actor config dict with type "llm"')
def step_config_llm_basic(context: Any) -> None:
context.config_dict = _build_base_config("llm")
context.config_dict.update(
{
"provider": "openai",
"model": "gpt-3.5-turbo",
"system_prompt": "You are a helpful assistant.",
"temperature": 0.7,
"max_tokens": 1000,
}
)
@given('a valid actor config dict with type "graph" and route definition')
def step_config_graph_route(context: Any) -> None:
context.config_dict = _build_base_config("graph")
context.config_dict["route"] = {
"nodes": [
{"id": "start", "type": "start"},
{"id": "end", "type": "end"},
],
"edges": [],
"entry_node": "start",
"exit_nodes": ["end"],
}
@given('a valid actor config dict with type "graph" and empty route nodes')
def step_config_graph_empty_route(context: Any) -> None:
context.config_dict = _build_base_config("graph")
context.config_dict["route"] = {
"nodes": [],
"edges": [],
"entry_node": "start",
}
@given('a valid actor config dict with type "tool" and tools list')
def step_config_tool(context: Any) -> None:
context.config_dict = _build_base_config("tool")
context.config_dict["tools"] = ["echo"]
context.config_dict["config"] = {"tools": ["echo"]}
@given('a valid actor config dict with type "llm" and openai provider')
def step_config_llm_openai(context: Any) -> None:
context.config_dict = _build_base_config("llm")
context.config_dict.update(
{
"provider": "openai",
"model": "gpt-3.5-turbo",
"system_prompt": "You are a helpful assistant.",
"temperature": 0.7,
"max_tokens": 1000,
}
)
@given('a valid actor config dict with type "graph" and full route definition')
def step_config_graph_full(context: Any) -> None:
context.config_dict = _build_base_config("graph")
context.config_dict["route"] = {
"nodes": [
{"id": "start", "type": "start"},
{"id": "process", "type": "function", "function": "process_input"},
{"id": "llm_node", "type": "llm", "agent": "test_agent"},
{"id": "end", "type": "end"},
],
"edges": [
{"source": "start", "target": "process"},
{"source": "process", "target": "llm_node"},
{"source": "llm_node", "target": "end"},
],
"entry_node": "start",
"exit_nodes": ["end"],
}
context.config_dict["agents"] = {
"test_agent": {"type": "llm", "config": {"provider": "openai"}},
}
# ---------------------------------------------------------------------------
# Given — credentials, limits, pricing
# ---------------------------------------------------------------------------
@given("credentials dict with openai provider")
def step_creds_openai(context: Any) -> None:
context.credentials = {
"openai": {
"api_key": "sk-test-key-12345",
"base_url": "https://api.openai.com/v1",
}
}
@given("credentials dict with openai api key")
def step_creds_openai_key(context: Any) -> None:
context.credentials = {"openai": {"api_key": "sk-test-executor-key"}}
@given("limits dict with max_depth {depth}")
def step_limits(context: Any, depth: str) -> None:
context.limits = {"max_depth": int(depth)}
@given("pricing dict with per_token_cost {cost}")
def step_pricing(context: Any, cost: str) -> None:
context.pricing = {"per_token_cost": float(cost)}
@given("a multi-actor config dict with multiple sub-actors")
def step_multi_actor_config(context: Any) -> None:
context.config_dict = {
"name": "test_multi_actor",
"type": "multi_actor",
"actors": {
"default": {
"type": "llm",
"provider": "openai",
"model": "gpt-3.5-turbo",
"system_prompt": "You are helpful.",
"temperature": 0.7,
"max_tokens": 1000,
},
"secondary": {
"type": "tool",
"tools": ["echo"],
},
},
"cleveragents": {"default_actor": "default"},
}
@given("a multi-actor config dict with empty actors")
def step_multi_actor_empty(context: Any) -> None:
context.config_dict = {
"name": "test_empty_actors",
"type": "multi_actor",
"actors": {},
"cleveragents": {"default_actor": "default"},
}