10abf8985e
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 19s
CI / build (pull_request) Successful in 29s
CI / security (pull_request) Successful in 33s
CI / typecheck (pull_request) Successful in 35s
CI / unit_tests (pull_request) Successful in 2m15s
CI / docker (pull_request) Successful in 39s
CI / integration_tests (pull_request) Successful in 3m4s
CI / coverage (pull_request) Successful in 5m1s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 31s
CI / typecheck (push) Successful in 33s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 3m20s
CI / integration_tests (push) Successful in 4m10s
CI / docker (push) Successful in 1m3s
CI / coverage (push) Successful in 4m20s
CI / benchmark-publish (push) Successful in 16m0s
CI / benchmark-regression (pull_request) Successful in 31m40s
Created ScoredFragment frozen model wrapping ContextFragment with composite_score, score_breakdown, and rank fields. Added pipeline- specific fragment models in domain/contexts/ with proper equality based on uko_uri + detail_depth for deduplication support. ISSUES CLOSED: #538
406 lines
12 KiB
Python
406 lines
12 KiB
Python
"""Step definitions for features/context_fragment_models.feature.
|
|
|
|
Tests the ScoredFragment pipeline model and ContextFragment strategy_source
|
|
field directly in-memory -- no database required.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
from pydantic import ValidationError
|
|
|
|
from cleveragents.domain.contexts.fragment import ScoredFragment
|
|
from cleveragents.domain.models.core.context_fragment import (
|
|
ContextFragment,
|
|
FragmentProvenance,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_DEFAULT_PROVENANCE = FragmentProvenance(resource_uri="test://default")
|
|
|
|
|
|
def _make_fragment(**kwargs: Any) -> ContextFragment:
|
|
"""Create a ContextFragment with sensible test defaults."""
|
|
kwargs.setdefault("uko_node", "test://default")
|
|
kwargs.setdefault("content", "test content")
|
|
kwargs.setdefault("token_count", 0)
|
|
kwargs.setdefault("provenance", _DEFAULT_PROVENANCE)
|
|
return ContextFragment(**kwargs)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given(
|
|
'a context fragment with uko_node "{uko}" and content "{content}"'
|
|
" and token_count {tokens:d} and relevance {rel:g}"
|
|
)
|
|
def step_given_fragment_with_relevance(
|
|
context: Context,
|
|
uko: str,
|
|
content: str,
|
|
tokens: int,
|
|
rel: float,
|
|
) -> None:
|
|
context.fragment = _make_fragment(
|
|
uko_node=uko,
|
|
content=content,
|
|
token_count=tokens,
|
|
relevance_score=rel,
|
|
)
|
|
|
|
|
|
@given(
|
|
'a context fragment with uko_node "{uko}" and content "{content}"'
|
|
' and token_count {tokens:d} and strategy_source "{strategy}"'
|
|
)
|
|
def step_given_fragment_with_strategy(
|
|
context: Context,
|
|
uko: str,
|
|
content: str,
|
|
tokens: int,
|
|
strategy: str,
|
|
) -> None:
|
|
context.fragment = _make_fragment(
|
|
uko_node=uko,
|
|
content=content,
|
|
token_count=tokens,
|
|
strategy_source=strategy,
|
|
)
|
|
|
|
|
|
@given('two scored fragments with uko_node "{uko}" and detail_depth {depth:d}')
|
|
def step_given_two_same_scored_fragments(
|
|
context: Context,
|
|
uko: str,
|
|
depth: int,
|
|
) -> None:
|
|
frag_a = _make_fragment(
|
|
uko_node=uko,
|
|
content="content A",
|
|
detail_depth=depth,
|
|
token_count=10,
|
|
)
|
|
frag_b = _make_fragment(
|
|
uko_node=uko,
|
|
content="content B",
|
|
detail_depth=depth,
|
|
token_count=20,
|
|
)
|
|
context.scored_a = ScoredFragment.from_fragment(frag_a, composite_score=0.5)
|
|
context.scored_b = ScoredFragment.from_fragment(frag_b, composite_score=0.9)
|
|
|
|
|
|
@given('a scored fragment with uko_node "{uko}" and detail_depth {depth:d}')
|
|
def step_given_scored_fragment_first(
|
|
context: Context,
|
|
uko: str,
|
|
depth: int,
|
|
) -> None:
|
|
frag = _make_fragment(
|
|
uko_node=uko,
|
|
content="content",
|
|
detail_depth=depth,
|
|
token_count=10,
|
|
)
|
|
context.scored_a = ScoredFragment.from_fragment(frag, composite_score=0.5)
|
|
|
|
|
|
@given('another scored fragment with uko_node "{uko}" and detail_depth {depth:d}')
|
|
def step_given_scored_fragment_second(
|
|
context: Context,
|
|
uko: str,
|
|
depth: int,
|
|
) -> None:
|
|
frag = _make_fragment(
|
|
uko_node=uko,
|
|
content="content",
|
|
detail_depth=depth,
|
|
token_count=10,
|
|
)
|
|
context.scored_b = ScoredFragment.from_fragment(frag, composite_score=0.7)
|
|
|
|
|
|
@given(
|
|
'a scored fragment with uko_node "{uko}" and detail_depth {depth:d}'
|
|
" and score {score:g}"
|
|
)
|
|
def step_given_scored_fragment_with_score(
|
|
context: Context,
|
|
uko: str,
|
|
depth: int,
|
|
score: float,
|
|
) -> None:
|
|
frag = _make_fragment(
|
|
uko_node=uko,
|
|
content="content",
|
|
detail_depth=depth,
|
|
token_count=10,
|
|
)
|
|
context.scored_a = ScoredFragment.from_fragment(frag, composite_score=score)
|
|
|
|
|
|
@given(
|
|
'another scored fragment with uko_node "{uko}" and detail_depth {depth:d}'
|
|
" and score {score:g}"
|
|
)
|
|
def step_given_another_scored_fragment_with_score(
|
|
context: Context,
|
|
uko: str,
|
|
depth: int,
|
|
score: float,
|
|
) -> None:
|
|
frag = _make_fragment(
|
|
uko_node=uko,
|
|
content="content",
|
|
detail_depth=depth,
|
|
token_count=10,
|
|
)
|
|
context.scored_b = ScoredFragment.from_fragment(frag, composite_score=score)
|
|
|
|
|
|
@given('three scored fragments where two share uko_node "{uko}" detail_depth {depth:d}')
|
|
def step_given_three_scored_fragments(
|
|
context: Context,
|
|
uko: str,
|
|
depth: int,
|
|
) -> None:
|
|
frag_a = _make_fragment(
|
|
uko_node=uko,
|
|
content="content A",
|
|
detail_depth=depth,
|
|
token_count=10,
|
|
)
|
|
frag_b = _make_fragment(
|
|
uko_node=uko,
|
|
content="content B",
|
|
detail_depth=depth,
|
|
token_count=20,
|
|
)
|
|
frag_c = _make_fragment(
|
|
uko_node="project://app/other.py",
|
|
content="content C",
|
|
detail_depth=depth,
|
|
token_count=30,
|
|
)
|
|
context.scored_list = [
|
|
ScoredFragment.from_fragment(frag_a, composite_score=0.5),
|
|
ScoredFragment.from_fragment(frag_b, composite_score=0.9),
|
|
ScoredFragment.from_fragment(frag_c, composite_score=0.7),
|
|
]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I create a scored fragment with composite_score {score:g}")
|
|
def step_when_create_scored(context: Context, score: float) -> None:
|
|
try:
|
|
context.scored = ScoredFragment(
|
|
fragment=context.fragment,
|
|
composite_score=score,
|
|
)
|
|
context.error = None
|
|
except (ValidationError, ValueError) as exc:
|
|
context.scored = None
|
|
context.error = exc
|
|
|
|
|
|
@when(
|
|
"I create a scored fragment with composite_score {score:g}"
|
|
" and rank {rank:d} and breakdown:"
|
|
)
|
|
def step_when_create_scored_with_breakdown(
|
|
context: Context,
|
|
score: float,
|
|
rank: int,
|
|
) -> None:
|
|
breakdown: dict[str, float] = {}
|
|
assert context.table is not None
|
|
for row in context.table:
|
|
breakdown[row["component"]] = float(row["score"])
|
|
context.scored = ScoredFragment(
|
|
fragment=context.fragment,
|
|
composite_score=score,
|
|
score_breakdown=breakdown,
|
|
rank=rank,
|
|
)
|
|
|
|
|
|
@when("I create a scored fragment from relevance")
|
|
def step_when_create_from_relevance(context: Context) -> None:
|
|
context.scored = ScoredFragment.from_relevance(context.fragment)
|
|
|
|
|
|
@when("I create a scored fragment from factory with score {score:g} and rank {rank:d}")
|
|
def step_when_create_from_factory(
|
|
context: Context,
|
|
score: float,
|
|
rank: int,
|
|
) -> None:
|
|
context.scored = ScoredFragment.from_fragment(
|
|
context.fragment,
|
|
composite_score=score,
|
|
rank=rank,
|
|
)
|
|
|
|
|
|
@when("I create a scored fragment with an invalid breakdown component {score:g}")
|
|
def step_when_create_invalid_breakdown(context: Context, score: float) -> None:
|
|
try:
|
|
context.scored = ScoredFragment(
|
|
fragment=context.fragment,
|
|
composite_score=0.5,
|
|
score_breakdown={"bad": score},
|
|
)
|
|
context.error = None
|
|
except (ValidationError, ValueError) as exc:
|
|
context.scored = None
|
|
context.error = exc
|
|
|
|
|
|
@when("I create a scored fragment with composite_score {score:g} and rank {rank:d}")
|
|
def step_when_create_scored_with_rank(
|
|
context: Context,
|
|
score: float,
|
|
rank: int,
|
|
) -> None:
|
|
try:
|
|
context.scored = ScoredFragment(
|
|
fragment=context.fragment,
|
|
composite_score=score,
|
|
rank=rank,
|
|
)
|
|
context.error = None
|
|
except (ValidationError, ValueError) as exc:
|
|
context.scored = None
|
|
context.error = exc
|
|
|
|
|
|
@when("I add them to a set")
|
|
def step_when_add_to_set(context: Context) -> None:
|
|
context.scored_set = set(context.scored_list)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("the scored fragment composite_score should be {expected:g}")
|
|
def step_then_composite_score(context: Context, expected: float) -> None:
|
|
assert context.scored is not None, "No scored fragment created"
|
|
assert context.scored.composite_score == expected, (
|
|
f"Expected composite_score={expected}, got {context.scored.composite_score}"
|
|
)
|
|
|
|
|
|
@then("the scored fragment rank should be {expected:d}")
|
|
def step_then_rank(context: Context, expected: int) -> None:
|
|
assert context.scored is not None, "No scored fragment created"
|
|
assert context.scored.rank == expected, (
|
|
f"Expected rank={expected}, got {context.scored.rank}"
|
|
)
|
|
|
|
|
|
@then("the scored fragment score_breakdown should be empty")
|
|
def step_then_breakdown_empty(context: Context) -> None:
|
|
assert context.scored is not None, "No scored fragment created"
|
|
assert context.scored.score_breakdown == {}, (
|
|
f"Expected empty breakdown, got {context.scored.score_breakdown}"
|
|
)
|
|
|
|
|
|
@then('the scored fragment uko_node should be "{expected}"')
|
|
def step_then_scored_uko_node(context: Context, expected: str) -> None:
|
|
assert context.scored is not None, "No scored fragment created"
|
|
assert context.scored.uko_node == expected, (
|
|
f"Expected uko_node={expected}, got {context.scored.uko_node}"
|
|
)
|
|
|
|
|
|
@then("the scored fragment detail_depth should be {expected:d}")
|
|
def step_then_scored_detail_depth(context: Context, expected: int) -> None:
|
|
assert context.scored is not None, "No scored fragment created"
|
|
assert context.scored.detail_depth == expected, (
|
|
f"Expected detail_depth={expected}, got {context.scored.detail_depth}"
|
|
)
|
|
|
|
|
|
@then("the scored fragment token_count should be {expected:d}")
|
|
def step_then_scored_token_count(context: Context, expected: int) -> None:
|
|
assert context.scored is not None, "No scored fragment created"
|
|
assert context.scored.token_count == expected, (
|
|
f"Expected token_count={expected}, got {context.scored.token_count}"
|
|
)
|
|
|
|
|
|
@then('the scored fragment breakdown "{key}" should be {expected:g}')
|
|
def step_then_breakdown_value(context: Context, key: str, expected: float) -> None:
|
|
assert context.scored is not None, "No scored fragment created"
|
|
assert key in context.scored.score_breakdown, (
|
|
f"Key '{key}' not in score_breakdown: {context.scored.score_breakdown}"
|
|
)
|
|
actual = context.scored.score_breakdown[key]
|
|
assert actual == expected, f"Expected breakdown['{key}']={expected}, got {actual}"
|
|
|
|
|
|
@then("the two scored fragments should be equal")
|
|
def step_then_scored_equal(context: Context) -> None:
|
|
assert context.scored_a == context.scored_b, "Expected scored fragments to be equal"
|
|
|
|
|
|
@then("the two scored fragments should not be equal")
|
|
def step_then_scored_not_equal(context: Context) -> None:
|
|
assert context.scored_a != context.scored_b, (
|
|
"Expected scored fragments to NOT be equal"
|
|
)
|
|
|
|
|
|
@then("the two scored fragments should have the same hash")
|
|
def step_then_scored_same_hash(context: Context) -> None:
|
|
assert hash(context.scored_a) == hash(context.scored_b), (
|
|
"Expected scored fragments to have the same hash"
|
|
)
|
|
|
|
|
|
@then("the set should contain {expected:d} unique scored fragments")
|
|
def step_then_set_size(context: Context, expected: int) -> None:
|
|
assert len(context.scored_set) == expected, (
|
|
f"Expected {expected} unique fragments, got {len(context.scored_set)}"
|
|
)
|
|
|
|
|
|
@then("modifying the scored fragment composite_score should raise an error")
|
|
def step_then_frozen(context: Context) -> None:
|
|
try:
|
|
context.scored.composite_score = 0.99 # type: ignore[misc]
|
|
raise AssertionError("Expected ValidationError for frozen model")
|
|
except ValidationError:
|
|
pass
|
|
|
|
|
|
@then("the fragment strategy_source should be empty")
|
|
def step_then_strategy_source_empty(context: Context) -> None:
|
|
assert context.fragment.strategy_source == "", (
|
|
f"Expected empty strategy_source, got '{context.fragment.strategy_source}'"
|
|
)
|
|
|
|
|
|
@then('the fragment strategy_source should be "{expected}"')
|
|
def step_then_strategy_source(context: Context, expected: str) -> None:
|
|
assert context.fragment.strategy_source == expected, (
|
|
f"Expected strategy_source='{expected}', "
|
|
f"got '{context.fragment.strategy_source}'"
|
|
)
|