5 Commits

Author SHA1 Message Date
CoreRasurae 8f986c1e31 test(coverage): add coverage scenarios for pre-existing code paths
Additional BDD scenarios covering registry resolver errors, cache
TTL expiry, runtime dispatch normalization, template base edge cases,
validation actor coverage gaps, and YAML Jinja loader deferred rendering.
2026-06-23 10:49:16 +01:00
hurui200320 2664ebfd75 feat(ActorResult): implement ActorResult and NodeUsage types; capture per-node token counts from LangChain responses
CI / lint (pull_request) Successful in 1m9s
CI / typecheck (pull_request) Successful in 1m10s
CI / security (pull_request) Successful in 1m8s
CI / quality (pull_request) Successful in 44s
CI / build (pull_request) Successful in 45s
CI / integration_tests (pull_request) Successful in 1m15s
CI / unit_tests (pull_request) Successful in 3m48s
CI / coverage (pull_request) Successful in 3m43s
CI / status-check (pull_request) Successful in 3s
CI / quality (push) Successful in 44s
CI / lint (push) Successful in 49s
CI / security (push) Successful in 47s
CI / typecheck (push) Successful in 48s
CI / build (push) Successful in 50s
CI / integration_tests (push) Successful in 1m16s
CI / unit_tests (push) Successful in 3m50s
CI / coverage (push) Successful in 3m35s
CI / status-check (push) Successful in 3s
AC1: Move NodeUsage and ActorResult from runtime.py into the spec-mandated
cleveractors/result.py module. runtime.py now imports and re-exports both
types for backward compatibility. result.py defines __all__, uses
dict[str, Any] | None (PEP 585) for the state field, and makes nodes a
required positional field (no default) so the contract that nodes is always
non-empty is statically enforced.

AC2: LLMAgent.process_message() extracts real token usage from
response.usage_metadata (primary) with isinstance(dict) guard, falling back
to response.response_metadata['token_usage'] with its own isinstance(dict)
guard. If neither is available, logs a WARNING with a cause parameter that
distinguishes the three failure conditions (empty usage_metadata, missing
response_metadata, or missing token_usage key) so operators can triage billing
discrepancies. Non-numeric token values are coerced via _safe_int() with
fallback to 0 and a warning log.

AC3: Token counts are available alongside the response string via the
side-channel attribute LLMAgent._last_token_usage, keeping process_message()
return type as str (preserving the Agent base-class contract unchanged).

AC4: Node._execute_agent() reads _last_token_usage, provider, and model from
any LLMAgent after execution and includes a _node_token_usage dict in the
state-updates return. PureLangGraph._node_usages accumulates one
(node_id, provider, model, prompt_tokens, completion_tokens) 5-tuple per LLM
node invocation during _execute_from_node(). PureLangGraph.execute() now
returns a 3-tuple: (response, final_state, node_usages). process_message()
unpacks and discards the extra elements.

AC5: Executor._execute_graph() uses the node_usages list from execute() to
build NodeUsage objects. Executor._execute_llm() reads _last_token_usage
directly from the LLMAgent instance instead of calling _estimate_tokens().
State isolation verified by a new test that calls execute() twice on the same
Executor with different mocked token counts and asserts independent results.

AC6: Aggregation invariant enforced in both _execute_llm() and _execute_graph()
via sum() over nodes list.

AC7: ActorResult and NodeUsage re-exported from cleveractors/__init__.py via
cleveractors.result (path updated from cleveractors.runtime).

Cleanup: Delete runtime_tokens.py (estimate_tokens, estimate_graph_tokens) and
the private _estimate_tokens() function in runtime.py — both are superseded by
real LangChain usage_metadata extraction. Remove unused import logging and
logger = logging.getLogger(__name__) from runtime.py (dead code after dispatch
logic was moved to runtime_dispatch.py). Move NodeUsageTuple type alias in
pure_graph.py to after the import blocks. Use tuple unpacking for last_usage
in runtime_dispatch._execute_llm().

New/updated BDD tests (actor_result_token_counting.feature, 25 scenarios):
- AC2: usage_metadata primary path (input_tokens/output_tokens)
- AC2: truthy non-dict usage_metadata treated as absent (isinstance guard)
- AC2: truthy non-dict response_metadata treated as absent (isinstance guard)
- AC2: response_metadata={} (empty dict) logs warning, counts 0
- AC2: response_metadata fallback path (prompt_tokens/completion_tokens)
- AC2: no usage data -> warning logged, counts 0
- AC2: response_metadata attribute present but None
- AC2: stale token reset before each call
- AC5: state isolation — execute() twice produces independent results
- AC6: aggregation invariant for single-node and multi-node actors
- AC6: placeholder node_id exact format assertion for _execute_graph
- AC6: placeholder node_id exact format assertion for _execute_multi_actor
- AC7: import correctness from cleveractors and cleveractors.result

CHANGELOG.md updated with Changed (real token extraction, result module move)
and Removed (runtime_tokens.py) entries under [Unreleased].

Quality gates: lint pass, typecheck 0 errors, unit_tests 2157/2157,
integration_tests 86/86, coverage 97.2% (threshold 97%).

ISSUES CLOSED: #14
2026-06-11 03:05:20 +00:00
hurui200320 76c4c74201 feat(create_executor): implement create_executor() factory and Executor.execute() returning ActorResult
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
2026-06-10 09:41:39 +00:00
hurui200320 f281fa3b24 feat(credentials): refactor LLMAgent/AgentFactory for per-request credential injection and extended provider routing
CI / lint (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 50s
CI / security (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 32s
CI / unit_tests (pull_request) Successful in 3m32s
CI / integration_tests (pull_request) Successful in 59s
CI / build (pull_request) Successful in 34s
CI / lint (push) Successful in 33s
CI / build (push) Successful in 37s
CI / quality (push) Successful in 48s
CI / typecheck (push) Successful in 48s
CI / security (push) Successful in 1m4s
CI / integration_tests (push) Successful in 1m18s
CI / coverage (pull_request) Successful in 3m34s
CI / status-check (pull_request) Successful in 3s
CI / unit_tests (push) Successful in 3m45s
CI / coverage (push) Successful in 3m34s
CI / status-check (push) Successful in 3s
Implements Wave 3 of the cleveractors-core integration (ADR-2026, ADR-2028).

## AgentFactory
- Added optional credentials: dict[str, dict[str, str]] | None parameter to
  __init__. Stored as self.credentials.
- When creating LLMAgent instances, the full credentials dict is forwarded via
  a new credentials constructor kwarg, threading per-request API keys without
  touching the stored actor config_dict.
- Added explicit type annotation ReactiveAgentFactory: type[AgentFactory].
- _create_agent_instance raises ConfigurationError when credentials is not None
  but does not contain an entry for the requested provider (AC4 compliance).

## LLMAgent
- Added optional credentials: dict[str, str] | None parameter to __init__.
  Stored as self._credentials; config_dict is never modified.
- LangChain client construction deferred to first access (lazy init) via a
  chat_model property backed by _ensure_chat_model(). The property setter
  allows test-injection of mock models without going through the lazy-init path.
- _create_chat_model() dispatches to credential-injection / standalone paths.
- Extracted to llm_client.py and llm_providers.py for modularity.
- Defined shared module-level constants DEFAULT_MODEL, DEFAULT_TEMPERATURE,
  DEFAULT_MAX_TOKENS to eliminate duplicated magic numbers across llm.py,
  runtime.py, and factory.py.
- Added public credentials property exposing self._credentials.
- Added type narrowing guard in chat_model property to satisfy static analysis.
- Added temperature_override type validation in process_message.
- Guarded cleanup() null-assignment with _chat_model_lock (TOCTOU fix).

## SSRF Prevention (ADR-2028)
- _validate_base_url(): https-only, userinfo rejection, localhost rejection,
  raw IP literal rejection, numeric-hostname (dotted-hex/octal/compact) rejection,
  percent-decode loop with iteration cap (10), trailing-dot FQDN strip.
- DNS resolution intentionally omitted (blocking call in async path).
- Canonical URL reconstruction uses lowercased scheme.

## Credential Leak Prevention
- All logger.exception(...) replaced with logger.debug(..., exc_info=True).
- Error messages use type(e).__name__ instead of str(e).
- process_message exception chains broken with from None to prevent LangChain
  authentication errors from leaking via __cause__.

## Runtime fixes
- Executor._execute_llm: shallow copy for setdefault mutations.
- Executor._execute_graph: restructured try/finally for resource cleanup.
- Uses DEFAULT_MODEL, DEFAULT_TEMPERATURE, DEFAULT_MAX_TOKENS from llm.py.
- Defensive copy of credentials dict in Executor.__init__ to prevent caller
  mutation after construction.

## Test improvements
- Added BDD scenarios for llm_imports.py ImportError fallback branches using
  sys.modules manipulation with a custom import hook, exercised via subprocess.
- Added BDD scenario for AgentFactory.create_agents_from_config() credential
  threading to all created agents.
- Removed tautological dataclass test scenarios (NodeUsage, ActorResult) from
  runtime_coverage.feature.
- Fixed misleading scenario title in runtime_coverage.feature (exception chain
  suppressed, not chained) and updated step to assert __cause__ is None.
- Added CHANGELOG entry for ReactiveAgentFactory backward-compatibility alias.
- Removed duplicate log emission in _check_known_provider_domain.

ISSUES CLOSED: #12
2026-06-08 11:10:46 +00:00
CoreRasurae ab17407576 test(runtime): add BDD coverage tests for runtime Executor API
CI / lint (pull_request) Successful in 40s
CI / typecheck (pull_request) Successful in 58s
CI / security (pull_request) Successful in 1m8s
CI / quality (pull_request) Successful in 1m7s
CI / integration_tests (pull_request) Successful in 1m20s
CI / build (pull_request) Successful in 39s
CI / unit_tests (pull_request) Successful in 3m47s
CI / coverage (pull_request) Successful in 3m44s
CI / status-check (pull_request) Successful in 4s
CI / quality (push) Successful in 39s
CI / lint (push) Successful in 47s
CI / security (push) Successful in 52s
CI / integration_tests (push) Successful in 1m2s
CI / typecheck (push) Successful in 1m5s
CI / build (push) Successful in 1m11s
CI / unit_tests (push) Successful in 4m27s
CI / coverage (push) Successful in 4m28s
CI / status-check (push) Successful in 5s
Add 16 Behave BDD scenarios covering:
- create_executor() factory function and Executor initialization
- Executor.execute() dispatch to LLM, graph, tool, multi-actor agents
- _execute_llm credential injection and error handling
- _execute_graph PureGraphConfig construction
- _execute_tool tool configuration
- _execute_multi_actor routing and error cases
- _build_factory_config credential/limit injection
- _estimate_tokens with tiktoken and fallback heuristic
- NodeUsage and ActorResult dataclasses

ISSUES CLOSED: #33
Refs: #33
2026-06-05 23:55:31 +01:00