487e16a9f0
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 13s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 17s
CI / security (pull_request) Successful in 32s
CI / typecheck (pull_request) Successful in 33s
CI / unit_tests (pull_request) Successful in 2m7s
CI / docker (pull_request) Successful in 37s
CI / integration_tests (pull_request) Successful in 2m56s
CI / coverage (pull_request) Successful in 4m26s
CI / benchmark-regression (pull_request) Successful in 28m12s
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 34s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m4s
CI / docker (push) Successful in 38s
CI / integration_tests (push) Successful in 3m7s
CI / coverage (push) Successful in 5m4s
CI / benchmark-publish (push) Has been cancelled
Implement the first three built-in context strategies for the ACMS v1 context assembly pipeline: 1. SimpleKeywordStrategy (quality 0.3) - Keyword matching on fragment content with word-density fallback. Universal fallback strategy. 2. SemanticEmbeddingStrategy (quality 0.6) - Jaccard word-overlap similarity scoring between query and fragment content. 3. BreadthDepthNavigatorStrategy (quality 0.85) - UKO node hierarchy navigation prioritising fragments near focus nodes with higher detail depths. Primary strategy for code projects. All strategies implement the v1 ContextStrategy Protocol from acms_service.py and can be registered with ACMSPipeline via register_strategy(). Includes: - 28 Behave BDD scenarios covering ranking, budget, capabilities, can_handle confidence, explain, empty input, and pipeline registration - 9 Robot Framework integration tests - ASV benchmarks at 10/100/1000 fragment scales for all 3 strategies - Vulture whitelist entries for public API symbols - 100% coverage on context_strategies.py ISSUES CLOSED: #541
289 lines
9.6 KiB
Python
289 lines
9.6 KiB
Python
"""Robot Framework integration helper for context strategies batch 1.
|
|
|
|
Each ``_cmd_*`` function exercises one strategy end-to-end and prints a
|
|
sentinel string on success. The Robot ``.robot`` file invokes this script
|
|
as a subprocess via ``Run Process`` and asserts exit-code + sentinel.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Bootstrap src/ so domain imports resolve when executed standalone.
|
|
_src = str(Path(__file__).resolve().parent.parent / "src")
|
|
if _src not in sys.path:
|
|
sys.path.insert(0, _src)
|
|
|
|
from cleveragents.application.services.acms_service import ACMSPipeline # noqa: E402
|
|
from cleveragents.application.services.context_strategies import ( # noqa: E402
|
|
BreadthDepthNavigatorStrategy,
|
|
SemanticEmbeddingStrategy,
|
|
SimpleKeywordStrategy,
|
|
)
|
|
from cleveragents.domain.models.core.context_fragment import ( # noqa: E402
|
|
ContextBudget,
|
|
ContextFragment,
|
|
FragmentProvenance,
|
|
)
|
|
|
|
|
|
def _make_frag(
|
|
uko_node: str,
|
|
content: str,
|
|
*,
|
|
score: float = 0.5,
|
|
tokens: int = 20,
|
|
depth: int = 3,
|
|
) -> ContextFragment:
|
|
return ContextFragment(
|
|
uko_node=uko_node,
|
|
content=content,
|
|
relevance_score=score,
|
|
token_count=tokens,
|
|
detail_depth=depth,
|
|
provenance=FragmentProvenance(resource_uri=uko_node),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Command functions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _cmd_simple_keyword_rank() -> int:
|
|
"""SimpleKeywordStrategy ranks keyword-matching fragments first."""
|
|
strategy = SimpleKeywordStrategy()
|
|
strategy.set_query("async IO")
|
|
|
|
frags = [
|
|
_make_frag("project://app/io.py", "Use async IO pattern"),
|
|
_make_frag("project://app/main.py", "Main entry point"),
|
|
_make_frag("project://app/net.py", "Async network handler"),
|
|
]
|
|
budget = ContextBudget(max_tokens=1000, reserved_tokens=0)
|
|
|
|
result = list(strategy.assemble(frags, budget))
|
|
if result[0].uko_node != "project://app/io.py":
|
|
print(f"FAIL: expected io.py first, got {result[0].uko_node}")
|
|
return 1
|
|
print("context-strategies-ok: simple-keyword-rank")
|
|
return 0
|
|
|
|
|
|
def _cmd_simple_keyword_budget() -> int:
|
|
"""SimpleKeywordStrategy respects token budget."""
|
|
strategy = SimpleKeywordStrategy()
|
|
strategy.set_query("hello")
|
|
|
|
frags = [
|
|
_make_frag("project://app/a.py", "hello world", tokens=100),
|
|
_make_frag("project://app/b.py", "hello there", tokens=100),
|
|
_make_frag("project://app/c.py", "hello again", tokens=100),
|
|
]
|
|
budget = ContextBudget(max_tokens=250, reserved_tokens=0)
|
|
|
|
result = list(strategy.assemble(frags, budget))
|
|
if len(result) != 2:
|
|
print(f"FAIL: expected 2 fragments, got {len(result)}")
|
|
return 1
|
|
print("context-strategies-ok: simple-keyword-budget")
|
|
return 0
|
|
|
|
|
|
def _cmd_semantic_embedding_rank() -> int:
|
|
"""SemanticEmbeddingStrategy ranks by word similarity."""
|
|
strategy = SemanticEmbeddingStrategy()
|
|
strategy.set_query("database connection pool")
|
|
|
|
frags = [
|
|
_make_frag("project://app/db.py", "Database connection pool manager"),
|
|
_make_frag("project://app/io.py", "File input output handler"),
|
|
_make_frag("project://app/sql.py", "SQL database query executor"),
|
|
]
|
|
budget = ContextBudget(max_tokens=1000, reserved_tokens=0)
|
|
|
|
result = list(strategy.assemble(frags, budget))
|
|
if result[0].uko_node != "project://app/db.py":
|
|
print(f"FAIL: expected db.py first, got {result[0].uko_node}")
|
|
return 1
|
|
print("context-strategies-ok: semantic-embedding-rank")
|
|
return 0
|
|
|
|
|
|
def _cmd_semantic_embedding_filter() -> int:
|
|
"""SemanticEmbeddingStrategy filters unrelated fragments."""
|
|
strategy = SemanticEmbeddingStrategy()
|
|
strategy.set_query("quantum computing")
|
|
|
|
frags = [
|
|
_make_frag("project://app/db.py", "database handler"),
|
|
_make_frag("project://app/io.py", "file io module"),
|
|
]
|
|
budget = ContextBudget(max_tokens=1000, reserved_tokens=0)
|
|
|
|
result = list(strategy.assemble(frags, budget))
|
|
if len(result) != 0:
|
|
print(f"FAIL: expected 0 fragments, got {len(result)}")
|
|
return 1
|
|
print("context-strategies-ok: semantic-embedding-filter")
|
|
return 0
|
|
|
|
|
|
def _cmd_breadth_depth_rank() -> int:
|
|
"""BreadthDepthNavigatorStrategy prioritises near focus."""
|
|
strategy = BreadthDepthNavigatorStrategy()
|
|
strategy.set_focus(["project://app/io.py"])
|
|
|
|
frags = [
|
|
_make_frag("project://app/io.py", "io module", depth=5),
|
|
_make_frag("project://app/main.py", "main entry", score=0.9, depth=3),
|
|
_make_frag("project://other/lib.py", "library", score=0.7, depth=9),
|
|
]
|
|
budget = ContextBudget(max_tokens=1000, reserved_tokens=0)
|
|
|
|
result = list(strategy.assemble(frags, budget))
|
|
if result[0].uko_node != "project://app/io.py":
|
|
print(f"FAIL: expected io.py first, got {result[0].uko_node}")
|
|
return 1
|
|
print("context-strategies-ok: breadth-depth-rank")
|
|
return 0
|
|
|
|
|
|
def _cmd_breadth_depth_budget() -> int:
|
|
"""BreadthDepthNavigatorStrategy respects budget."""
|
|
strategy = BreadthDepthNavigatorStrategy()
|
|
strategy.set_focus(["project://app"])
|
|
|
|
frags = [
|
|
_make_frag("project://app/a.py", "alpha", score=0.9, tokens=100, depth=5),
|
|
_make_frag("project://app/b.py", "beta", score=0.7, tokens=100, depth=5),
|
|
_make_frag("project://app/c.py", "gamma", score=0.5, tokens=100, depth=5),
|
|
]
|
|
budget = ContextBudget(max_tokens=250, reserved_tokens=0)
|
|
|
|
result = list(strategy.assemble(frags, budget))
|
|
if len(result) != 2:
|
|
print(f"FAIL: expected 2 fragments, got {len(result)}")
|
|
return 1
|
|
print("context-strategies-ok: breadth-depth-budget")
|
|
return 0
|
|
|
|
|
|
def _cmd_pipeline_register() -> int:
|
|
"""Register all batch 1 strategies with pipeline."""
|
|
pipeline = ACMSPipeline()
|
|
pipeline.register_strategy("simple-keyword", SimpleKeywordStrategy())
|
|
pipeline.register_strategy("semantic-embedding", SemanticEmbeddingStrategy())
|
|
pipeline.register_strategy(
|
|
"breadth-depth-navigator", BreadthDepthNavigatorStrategy()
|
|
)
|
|
|
|
registered = pipeline._strategies
|
|
for name in ("simple-keyword", "semantic-embedding", "breadth-depth-navigator"):
|
|
if name not in registered:
|
|
print(f"FAIL: strategy '{name}' not registered")
|
|
return 1
|
|
print("context-strategies-ok: pipeline-register")
|
|
return 0
|
|
|
|
|
|
def _cmd_can_handle() -> int:
|
|
"""Verify can_handle confidence values for all strategies."""
|
|
sk = SimpleKeywordStrategy()
|
|
se = SemanticEmbeddingStrategy()
|
|
bd = BreadthDepthNavigatorStrategy()
|
|
|
|
# SimpleKeyword always returns 0.3
|
|
if abs(sk.can_handle({"query": "test"}) - 0.3) > 1e-6:
|
|
print("FAIL: SimpleKeyword can_handle != 0.3")
|
|
return 1
|
|
|
|
# SemanticEmbedding returns 0.6 with query
|
|
if abs(se.can_handle({"query": "test"}) - 0.6) > 1e-6:
|
|
print("FAIL: SemanticEmbedding can_handle with query != 0.6")
|
|
return 1
|
|
|
|
# SemanticEmbedding returns 0.1 without query
|
|
if abs(se.can_handle({}) - 0.1) > 1e-6:
|
|
print("FAIL: SemanticEmbedding can_handle without query != 0.1")
|
|
return 1
|
|
|
|
# BreadthDepth returns 0.85 with focus
|
|
if abs(bd.can_handle({"focus": ["project://app"]}) - 0.85) > 1e-6:
|
|
print("FAIL: BreadthDepth can_handle with focus != 0.85")
|
|
return 1
|
|
|
|
# BreadthDepth returns 0.2 without focus
|
|
if abs(bd.can_handle({}) - 0.2) > 1e-6:
|
|
print("FAIL: BreadthDepth can_handle without focus != 0.2")
|
|
return 1
|
|
|
|
print("context-strategies-ok: can-handle")
|
|
return 0
|
|
|
|
|
|
def _cmd_capabilities() -> int:
|
|
"""Verify capability flags for all strategies."""
|
|
sk = SimpleKeywordStrategy()
|
|
se = SemanticEmbeddingStrategy()
|
|
bd = BreadthDepthNavigatorStrategy()
|
|
|
|
if sk.capabilities.supports_semantic_search:
|
|
print("FAIL: SimpleKeyword should not support semantic search")
|
|
return 1
|
|
if not se.capabilities.supports_semantic_search:
|
|
print("FAIL: SemanticEmbedding should support semantic search")
|
|
return 1
|
|
if not bd.capabilities.supports_graph_navigation:
|
|
print("FAIL: BreadthDepth should support graph navigation")
|
|
return 1
|
|
if sk.name != "simple-keyword":
|
|
print(f"FAIL: SimpleKeyword name = {sk.name}")
|
|
return 1
|
|
if se.name != "semantic-embedding":
|
|
print(f"FAIL: SemanticEmbedding name = {se.name}")
|
|
return 1
|
|
if bd.name != "breadth-depth-navigator":
|
|
print(f"FAIL: BreadthDepth name = {bd.name}")
|
|
return 1
|
|
|
|
print("context-strategies-ok: capabilities")
|
|
return 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dispatch
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_COMMANDS: dict[str, object] = {
|
|
"simple-keyword-rank": _cmd_simple_keyword_rank,
|
|
"simple-keyword-budget": _cmd_simple_keyword_budget,
|
|
"semantic-embedding-rank": _cmd_semantic_embedding_rank,
|
|
"semantic-embedding-filter": _cmd_semantic_embedding_filter,
|
|
"breadth-depth-rank": _cmd_breadth_depth_rank,
|
|
"breadth-depth-budget": _cmd_breadth_depth_budget,
|
|
"pipeline-register": _cmd_pipeline_register,
|
|
"can-handle": _cmd_can_handle,
|
|
"capabilities": _cmd_capabilities,
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) < 2:
|
|
print(f"Usage: {sys.argv[0]} <command>")
|
|
print(f"Commands: {', '.join(sorted(_COMMANDS))}")
|
|
return 1
|
|
|
|
cmd = sys.argv[1]
|
|
handler = _COMMANDS.get(cmd)
|
|
if handler is None:
|
|
print(f"Unknown command: {cmd}")
|
|
return 1
|
|
|
|
return handler() # type: ignore[operator]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|