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
- **`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
185 lines
5.6 KiB
Python
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"},
|
|
}
|