025d379946
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 14s
CI / quality (pull_request) Successful in 17s
CI / build (pull_request) Successful in 15s
CI / typecheck (pull_request) Successful in 33s
CI / security (pull_request) Successful in 42s
CI / unit_tests (pull_request) Successful in 2m36s
CI / docker (pull_request) Successful in 40s
CI / integration_tests (pull_request) Successful in 3m17s
CI / coverage (pull_request) Successful in 4m21s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 30s
CI / typecheck (push) Successful in 33s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m36s
CI / docker (push) Successful in 40s
CI / integration_tests (push) Successful in 3m57s
CI / coverage (push) Successful in 5m22s
CI / benchmark-publish (push) Has been cancelled
CI / benchmark-regression (pull_request) Successful in 25m12s
Implemented the Backend Abstraction Layer (BAL) for the Advanced Context Management System, following the specification in docs/specification.md Section ACMS > Backend Abstraction Layer and ADR-014. Key additions: - TextBackend protocol with search(query, scope, max_results) returning list[TextResult], and TextResult frozen dataclass (uko_uri, content, score, metadata fields) - VectorBackend protocol with similarity_search(embedding, scope, top_k) returning list[VectorResult], and VectorResult frozen dataclass - GraphBackend protocol with sparql_query(query, scope), get_triples(subject), and traverse(start, depth) methods returning GraphResult frozen dataclass (triples, metadata fields) - In-memory stub backends (InMemoryTextBackend, InMemoryVectorBackend, InMemoryGraphBackend) that validate arguments and return empty results, serving as development placeholders and test doubles - DI container registration as configurable Singletons with provider selection via override_providers() - Behave BDD feature (35 scenarios / 83 steps) covering protocol compliance, argument validation, result immutability, and DI resolution - Robot Framework smoke tests (6 tests) for integration verification - ASV benchmarks for stub query overhead and instantiation time - Reference documentation at docs/reference/acms_backends.md Design decisions: - Used @runtime_checkable Protocol for structural subtyping, consistent with existing ResourceHandler pattern - Used frozen dataclasses (not Pydantic) for result types to minimize overhead in the hot path of context assembly - scope parameter typed as frozenset[str] for immutability and hashability - Stubs registered as default Singletons; production backends swap via DI ISSUES CLOSED: #498
160 lines
5.0 KiB
Python
160 lines
5.0 KiB
Python
"""Robot Framework helper for ACMS backend protocol smoke tests.
|
|
|
|
Provides a CLI-style interface for Robot to invoke backend creation,
|
|
protocol compliance, and DI resolution. Exit code 0 = success, 1 = failure.
|
|
|
|
Usage:
|
|
python robot/helper_acms_backends.py <command>
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Ensure the src directory is on the import path.
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
from cleveragents.application.container import ( # noqa: E402
|
|
get_container,
|
|
reset_container,
|
|
)
|
|
from cleveragents.domain.models.acms.backends import ( # noqa: E402
|
|
GraphBackend,
|
|
GraphResult,
|
|
TextBackend,
|
|
TextResult,
|
|
VectorBackend,
|
|
VectorResult,
|
|
)
|
|
from cleveragents.domain.models.acms.stubs import ( # noqa: E402
|
|
InMemoryGraphBackend,
|
|
InMemoryTextBackend,
|
|
InMemoryVectorBackend,
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
"""Entry point called by Robot Framework ``Run Process``."""
|
|
if len(sys.argv) < 2:
|
|
print("Usage: helper_acms_backends.py <command>")
|
|
return 1
|
|
|
|
command: str = sys.argv[1]
|
|
|
|
if command == "text-backend":
|
|
try:
|
|
backend = InMemoryTextBackend()
|
|
assert isinstance(backend, TextBackend)
|
|
results = backend.search("test", scope=frozenset({"RES01"}))
|
|
assert results == []
|
|
print("acms-text-backend-ok")
|
|
return 0
|
|
except Exception as exc:
|
|
print(f"acms-text-backend-fail: {exc}")
|
|
return 1
|
|
|
|
if command == "vector-backend":
|
|
try:
|
|
backend = InMemoryVectorBackend()
|
|
assert isinstance(backend, VectorBackend)
|
|
results = backend.similarity_search([0.1, 0.2], scope=frozenset({"RES01"}))
|
|
assert results == []
|
|
print("acms-vector-backend-ok")
|
|
return 0
|
|
except Exception as exc:
|
|
print(f"acms-vector-backend-fail: {exc}")
|
|
return 1
|
|
|
|
if command == "graph-backend":
|
|
try:
|
|
backend = InMemoryGraphBackend()
|
|
assert isinstance(backend, GraphBackend)
|
|
result = backend.sparql_query(
|
|
"SELECT ?s WHERE { ?s a uko:Container }",
|
|
scope=frozenset({"RES01"}),
|
|
)
|
|
assert result.triples == []
|
|
result2 = backend.get_triples("uko:subject")
|
|
assert result2.triples == []
|
|
result3 = backend.traverse("uko:start", depth=2)
|
|
assert result3.triples == []
|
|
print("acms-graph-backend-ok")
|
|
return 0
|
|
except Exception as exc:
|
|
print(f"acms-graph-backend-fail: {exc}")
|
|
return 1
|
|
|
|
if command == "result-types":
|
|
try:
|
|
tr = TextResult(uko_uri="uko:test", content="hello", score=0.5)
|
|
assert tr.uko_uri == "uko:test"
|
|
vr = VectorResult(uko_uri="uko:vec", content="embed", score=0.8)
|
|
assert vr.uko_uri == "uko:vec"
|
|
gr = GraphResult(triples=[("a", "b", "c")])
|
|
assert len(gr.triples) == 1
|
|
print("acms-result-types-ok")
|
|
return 0
|
|
except Exception as exc:
|
|
print(f"acms-result-types-fail: {exc}")
|
|
return 1
|
|
|
|
if command == "di-resolution":
|
|
try:
|
|
reset_container()
|
|
container = get_container()
|
|
tb = container.text_backend()
|
|
assert isinstance(tb, TextBackend)
|
|
vb = container.vector_backend()
|
|
assert isinstance(vb, VectorBackend)
|
|
gb = container.graph_backend()
|
|
assert isinstance(gb, GraphBackend)
|
|
print("acms-di-resolution-ok")
|
|
return 0
|
|
except Exception as exc:
|
|
print(f"acms-di-resolution-fail: {exc}")
|
|
return 1
|
|
|
|
if command == "validation":
|
|
try:
|
|
# Empty query should raise
|
|
backend = InMemoryTextBackend()
|
|
raised = False
|
|
try:
|
|
backend.search("", scope=frozenset({"RES01"}))
|
|
except ValueError:
|
|
raised = True
|
|
assert raised, "Expected ValueError for empty query"
|
|
|
|
# Empty embedding should raise
|
|
vb = InMemoryVectorBackend()
|
|
raised = False
|
|
try:
|
|
vb.similarity_search([], scope=frozenset({"RES01"}))
|
|
except ValueError:
|
|
raised = True
|
|
assert raised, "Expected ValueError for empty embedding"
|
|
|
|
# Invalid score should raise
|
|
raised = False
|
|
try:
|
|
TextResult(uko_uri="uko:x", content="x", score=2.0)
|
|
except ValueError:
|
|
raised = True
|
|
assert raised, "Expected ValueError for score > 1.0"
|
|
|
|
print("acms-validation-ok")
|
|
return 0
|
|
except Exception as exc:
|
|
print(f"acms-validation-fail: {exc}")
|
|
return 1
|
|
|
|
print(f"Unknown command: {command}")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|