c91a6a252d
CI / lint (pull_request) Successful in 38s
CI / build (pull_request) Successful in 58s
CI / quality (pull_request) Successful in 1m18s
CI / typecheck (pull_request) Successful in 1m18s
CI / security (pull_request) Successful in 1m34s
CI / helm (pull_request) Successful in 54s
CI / push-validation (pull_request) Successful in 29s
CI / unit_tests (pull_request) Successful in 5m39s
CI / docker (pull_request) Successful in 1m37s
CI / integration_tests (pull_request) Successful in 9m40s
CI / coverage (pull_request) Successful in 12m19s
CI / status-check (pull_request) Successful in 4s
Applied ruff format to sliding_window_strategy.py and sliding_window_strategy_steps.py to fix CI lint format check failure. ISSUES CLOSED: #9995
257 lines
8.5 KiB
Python
257 lines
8.5 KiB
Python
"""Step definitions for ``features/sliding_window_strategy.feature``.
|
|
|
|
Covers the SlidingWindowStrategy:
|
|
|
|
* Protocol conformance
|
|
* Messages mode truncation
|
|
* Tokens mode truncation
|
|
* System prompt preservation
|
|
* Budget enforcement
|
|
* can_handle confidence
|
|
* Plugin registry registration
|
|
* Configuration validation
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.application.services.acms_service import (
|
|
ACMSPipeline,
|
|
ContextStrategy,
|
|
)
|
|
from cleveragents.application.services.sliding_window_strategy import (
|
|
SlidingWindowStrategy,
|
|
)
|
|
from cleveragents.domain.models.core.context_fragment import (
|
|
ContextBudget,
|
|
ContextFragment,
|
|
FragmentProvenance,
|
|
)
|
|
|
|
__all__: list[str] = []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_sw_fragment(
|
|
uko_node: str,
|
|
content: str,
|
|
score: float,
|
|
tokens: int,
|
|
depth: int,
|
|
role: str = "user",
|
|
) -> ContextFragment:
|
|
"""Build a ``ContextFragment`` for sliding window tests."""
|
|
metadata: dict[str, str] = {}
|
|
if role:
|
|
metadata["role"] = role
|
|
return ContextFragment(
|
|
uko_node=uko_node,
|
|
content=content,
|
|
relevance_score=score,
|
|
token_count=tokens,
|
|
detail_depth=depth,
|
|
provenance=FragmentProvenance(resource_uri=uko_node),
|
|
metadata=metadata,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('a SlidingWindowStrategy with window_size {size:d} and window_mode "{mode}"')
|
|
def step_sliding_window_strategy(context: Context, size: int, mode: str) -> None:
|
|
context.sw_strategy = SlidingWindowStrategy(
|
|
window_size=size,
|
|
window_mode=mode, # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
@given("the following sliding window fragments:")
|
|
def step_sw_fragments_table(context: Context) -> None:
|
|
context.sw_fragments = []
|
|
for row in context.table:
|
|
role = row["role"] if "role" in row.headings else "user"
|
|
frag = _make_sw_fragment(
|
|
uko_node=row["uko_node"],
|
|
content=row["content"],
|
|
score=float(row["score"]),
|
|
tokens=int(row["tokens"]),
|
|
depth=int(row["depth"]),
|
|
role=role,
|
|
)
|
|
context.sw_fragments.append(frag)
|
|
|
|
|
|
@given("an empty sliding window fragment list")
|
|
def step_sw_empty_fragments(context: Context) -> None:
|
|
context.sw_fragments = []
|
|
|
|
|
|
@given(
|
|
"a sliding window budget with max_tokens {max_tokens:d} and reserved_tokens {reserved:d}"
|
|
)
|
|
def step_sw_budget(context: Context, max_tokens: int, reserved: int) -> None:
|
|
context.sw_budget = ContextBudget(max_tokens=max_tokens, reserved_tokens=reserved)
|
|
|
|
|
|
@given("an ACMS pipeline for sliding window tests")
|
|
def step_sw_pipeline(context: Context) -> None:
|
|
# Create the pipeline and register SlidingWindowStrategy under "sliding_window".
|
|
# This satisfies the requirement to register the strategy in the plugin
|
|
# registry under key "sliding_window".
|
|
pipeline = ACMSPipeline()
|
|
pipeline.register_strategy("sliding_window", SlidingWindowStrategy())
|
|
context.sw_pipeline = pipeline
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I apply the SlidingWindowStrategy")
|
|
def step_apply_sw(context: Context) -> None:
|
|
context.sw_result = list(
|
|
context.sw_strategy.assemble(context.sw_fragments, context.sw_budget)
|
|
)
|
|
|
|
|
|
@when("I check can_handle on SlidingWindowStrategy")
|
|
def step_sw_can_handle(context: Context) -> None:
|
|
request: dict[str, Any] = {}
|
|
context.sw_confidence = context.sw_strategy.can_handle(request)
|
|
|
|
|
|
@when("I register a custom SlidingWindowStrategy with the pipeline")
|
|
def step_register_custom_sw(context: Context) -> None:
|
|
custom_strategy = SlidingWindowStrategy(window_size=50, window_mode="messages")
|
|
context.sw_pipeline.register_strategy("custom_sliding_window", custom_strategy)
|
|
|
|
|
|
@when("I attempt to create a SlidingWindowStrategy with window_size {size:d}")
|
|
def step_attempt_create_sw_bad_size(context: Context, size: int) -> None:
|
|
context.sw_error = None
|
|
try:
|
|
SlidingWindowStrategy(window_size=size, window_mode="messages")
|
|
except ValueError as exc:
|
|
context.sw_error = exc
|
|
|
|
|
|
@when('I attempt to create a SlidingWindowStrategy with window_mode "{mode}"')
|
|
def step_attempt_create_sw_bad_mode(context: Context, mode: str) -> None:
|
|
context.sw_error = None
|
|
try:
|
|
SlidingWindowStrategy(window_size=10, window_mode=mode) # type: ignore[arg-type]
|
|
except ValueError as exc:
|
|
context.sw_error = exc
|
|
|
|
|
|
@when(
|
|
'I create a SlidingWindowStrategy with window_size {size:d} and window_mode "{mode}"'
|
|
)
|
|
def step_create_sw_valid(context: Context, size: int, mode: str) -> None:
|
|
context.sw_error = None
|
|
try:
|
|
context.sw_strategy = SlidingWindowStrategy(
|
|
window_size=size,
|
|
window_mode=mode, # type: ignore[arg-type]
|
|
)
|
|
except ValueError as exc:
|
|
context.sw_error = exc
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("the sw strategy should satisfy the ContextStrategy protocol")
|
|
def step_sw_satisfies_protocol(context: Context) -> None:
|
|
assert isinstance(context.sw_strategy, ContextStrategy), (
|
|
f"SlidingWindowStrategy does not satisfy ContextStrategy protocol: "
|
|
f"{type(context.sw_strategy)}"
|
|
)
|
|
|
|
|
|
@then('the sw strategy name should be "sliding_window"')
|
|
def step_sw_name_sliding_window(context: Context) -> None:
|
|
actual = context.sw_strategy.name
|
|
assert actual == "sliding_window", f"Expected name 'sliding_window', got '{actual}'"
|
|
|
|
|
|
@then("the sw strategy should have a capabilities object")
|
|
def step_sw_has_capabilities(context: Context) -> None:
|
|
caps = context.sw_strategy.capabilities
|
|
assert caps is not None, "Expected capabilities object, got None"
|
|
|
|
|
|
@then('the sw strategy explain should contain "{text}"')
|
|
def step_sw_explain_contains(context: Context, text: str) -> None:
|
|
explanation = context.sw_strategy.explain()
|
|
assert text.lower() in explanation.lower(), (
|
|
f"Expected explain to contain '{text}', got: {explanation}"
|
|
)
|
|
|
|
|
|
@then("{count:d} fragments should be returned by sliding window strategy")
|
|
def step_sw_fragment_count(context: Context, count: int) -> None:
|
|
actual = len(context.sw_result)
|
|
assert actual == count, (
|
|
f"Expected {count} fragments from sliding window, got {actual}. "
|
|
f"Fragments: {[f.uko_node for f in context.sw_result]}"
|
|
)
|
|
|
|
|
|
@then('the sliding window result should contain uko_node "{uko_node}"')
|
|
def step_sw_result_contains(context: Context, uko_node: str) -> None:
|
|
nodes = [f.uko_node for f in context.sw_result]
|
|
assert uko_node in nodes, f"Expected uko_node '{uko_node}' in result, got: {nodes}"
|
|
|
|
|
|
@then('the sliding window result should not contain uko_node "{uko_node}"')
|
|
def step_sw_result_not_contains(context: Context, uko_node: str) -> None:
|
|
nodes = [f.uko_node for f in context.sw_result]
|
|
assert uko_node not in nodes, (
|
|
f"Expected uko_node '{uko_node}' NOT in result, but it was. Nodes: {nodes}"
|
|
)
|
|
|
|
|
|
@then("the sliding window confidence should be greater than 0.0")
|
|
def step_sw_confidence_positive(context: Context) -> None:
|
|
actual = context.sw_confidence
|
|
assert actual > 0.0, f"Expected confidence > 0.0, got {actual}"
|
|
|
|
|
|
@then('the sw pipeline should have strategy "{name}"')
|
|
def step_sw_pipeline_has_strategy(context: Context, name: str) -> None:
|
|
registered = context.sw_pipeline._strategies
|
|
assert name in registered, (
|
|
f"Pipeline does not have strategy '{name}'. "
|
|
f"Registered: {list(registered.keys())}"
|
|
)
|
|
|
|
|
|
@then("a ValueError should be raised for sliding window")
|
|
def step_sw_value_error_raised(context: Context) -> None:
|
|
assert context.sw_error is not None, (
|
|
"Expected a ValueError to be raised, but no error was raised"
|
|
)
|
|
assert isinstance(context.sw_error, ValueError), (
|
|
f"Expected ValueError, got {type(context.sw_error)}: {context.sw_error}"
|
|
)
|
|
|
|
|
|
@then("no error should be raised for sliding window")
|
|
def step_sw_no_error(context: Context) -> None:
|
|
assert context.sw_error is None, f"Expected no error, but got: {context.sw_error}"
|