febea8950f
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 14s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 18s
CI / security (pull_request) Successful in 32s
CI / typecheck (pull_request) Successful in 36s
CI / unit_tests (pull_request) Successful in 2m0s
CI / docker (pull_request) Successful in 51s
CI / integration_tests (pull_request) Successful in 3m5s
CI / coverage (pull_request) Successful in 4m20s
CI / benchmark-regression (pull_request) Successful in 28m14s
Implement the 10-component pluggable ACMS context assembly pipeline with three built-in strategies (relevance, recency, tiered), DI-based component injection, ULID-validated plan_id, largest-remainder budget allocation, and frozen Pydantic v2 domain models. Closes #188
214 lines
6.8 KiB
Python
214 lines
6.8 KiB
Python
"""ASV benchmarks for ACMS v1 context assembly pipeline.
|
|
|
|
Measures the performance of:
|
|
- ContextFragment creation (with and without metadata)
|
|
- ACMSPipeline.assemble with varying fragment counts
|
|
- Tiered fusion strategy ranking
|
|
- Recency strategy ranking
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
import cleveragents # noqa: E402
|
|
|
|
importlib.reload(cleveragents)
|
|
|
|
from cleveragents.application.services.acms_service import ACMSPipeline # noqa: E402
|
|
from cleveragents.domain.models.core.context_fragment import ( # noqa: E402
|
|
ContextBudget,
|
|
ContextFragment,
|
|
FragmentProvenance,
|
|
compute_context_hash,
|
|
)
|
|
|
|
# Default provenance for benchmark fragments.
|
|
_DEFAULT_PROV = FragmentProvenance(resource_uri="bench://default")
|
|
|
|
|
|
class ContextFragmentSuite:
|
|
"""Benchmark ContextFragment creation throughput."""
|
|
|
|
def time_fragment_creation(self) -> None:
|
|
"""Benchmark creating a ContextFragment with defaults."""
|
|
ContextFragment(
|
|
uko_node="bench://file",
|
|
content="benchmark content",
|
|
token_count=10,
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
|
|
def time_fragment_with_metadata(self) -> None:
|
|
"""Benchmark creating a ContextFragment with metadata."""
|
|
ContextFragment(
|
|
uko_node="bench://decision",
|
|
content="benchmark with metadata",
|
|
relevance_score=0.85,
|
|
token_count=150,
|
|
tier="hot",
|
|
metadata={"author": "bench", "priority": "high"},
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
|
|
|
|
class ACMSPipelineSuite:
|
|
"""Benchmark ACMSPipeline.assemble throughput."""
|
|
|
|
def setup(self) -> None:
|
|
"""Set up pipeline and fragment lists for assembly benchmarks."""
|
|
self._pipeline = ACMSPipeline()
|
|
self._budget = ContextBudget(max_tokens=100_000, reserved_tokens=0)
|
|
self._frags_10 = [
|
|
ContextFragment(
|
|
uko_node=f"bench://file/{i}",
|
|
content=f"fragment {i}",
|
|
relevance_score=round(i / 10, 1),
|
|
token_count=50,
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
for i in range(10)
|
|
]
|
|
self._frags_100 = [
|
|
ContextFragment(
|
|
uko_node=f"bench://file/{i}",
|
|
content=f"fragment {i}",
|
|
relevance_score=round((i % 10) / 10, 1),
|
|
token_count=50,
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
for i in range(100)
|
|
]
|
|
self._frags_1000 = [
|
|
ContextFragment(
|
|
uko_node=f"bench://file/{i}",
|
|
content=f"fragment {i}",
|
|
relevance_score=round((i % 10) / 10, 1),
|
|
token_count=50,
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
for i in range(1000)
|
|
]
|
|
tiers = ("hot", "warm", "cold")
|
|
self._tiered_frags = [
|
|
ContextFragment(
|
|
uko_node=f"bench://file/{i}",
|
|
content=f"fragment {i}",
|
|
relevance_score=round((i % 10) / 10, 1),
|
|
token_count=50,
|
|
tier=tiers[i % 3],
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
for i in range(100)
|
|
]
|
|
# Recency benchmark fragments with distinct timestamps
|
|
self._recency_frags = [
|
|
ContextFragment(
|
|
uko_node=f"bench://file/{i}",
|
|
content=f"fragment {i}",
|
|
token_count=50,
|
|
created_at=datetime(2024, 1, 1 + (i % 28), tzinfo=UTC),
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
for i in range(100)
|
|
]
|
|
|
|
def time_assemble_10_fragments(self) -> None:
|
|
"""Benchmark assembling 10 fragments with relevance strategy."""
|
|
self._pipeline.assemble(
|
|
plan_id="01JQBENCHM00000000000000AA",
|
|
fragments=self._frags_10,
|
|
budget=self._budget,
|
|
)
|
|
|
|
def time_assemble_100_fragments(self) -> None:
|
|
"""Benchmark assembling 100 fragments with relevance strategy."""
|
|
self._pipeline.assemble(
|
|
plan_id="01JQBENCHM00000000000000AA",
|
|
fragments=self._frags_100,
|
|
budget=self._budget,
|
|
)
|
|
|
|
def time_assemble_1000_fragments(self) -> None:
|
|
"""Benchmark assembling 1000 fragments with relevance strategy."""
|
|
self._pipeline.assemble(
|
|
plan_id="01JQBENCHM00000000000000AA",
|
|
fragments=self._frags_1000,
|
|
budget=self._budget,
|
|
)
|
|
|
|
def time_tiered_strategy(self) -> None:
|
|
"""Benchmark assembling with tiered strategy."""
|
|
self._pipeline.assemble(
|
|
plan_id="01JQBENCHM00000000000000AA",
|
|
fragments=self._tiered_frags,
|
|
budget=self._budget,
|
|
strategy="tiered",
|
|
)
|
|
|
|
def time_recency_strategy(self) -> None:
|
|
"""Benchmark assembling with recency strategy."""
|
|
self._pipeline.assemble(
|
|
plan_id="01JQBENCHM00000000000000AA",
|
|
fragments=self._recency_frags,
|
|
budget=self._budget,
|
|
strategy="recency",
|
|
)
|
|
|
|
|
|
class ContextHashSuite:
|
|
"""Benchmark compute_context_hash in isolation.
|
|
|
|
Measures the length-prefixed SHA-256 hash function at various
|
|
fragment counts to detect performance regressions.
|
|
"""
|
|
|
|
def setup(self) -> None:
|
|
"""Build fragment tuples of varying sizes."""
|
|
self._frags_10 = tuple(
|
|
ContextFragment(
|
|
uko_node=f"bench://hash/{i}",
|
|
content=f"fragment content {i}" * 10,
|
|
token_count=50,
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
for i in range(10)
|
|
)
|
|
self._frags_100 = tuple(
|
|
ContextFragment(
|
|
uko_node=f"bench://hash/{i}",
|
|
content=f"fragment content {i}" * 10,
|
|
token_count=50,
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
for i in range(100)
|
|
)
|
|
self._frags_1000 = tuple(
|
|
ContextFragment(
|
|
uko_node=f"bench://hash/{i}",
|
|
content=f"fragment content {i}" * 10,
|
|
token_count=50,
|
|
provenance=_DEFAULT_PROV,
|
|
)
|
|
for i in range(1000)
|
|
)
|
|
|
|
def time_hash_10_fragments(self) -> None:
|
|
"""Benchmark hashing 10 fragments."""
|
|
compute_context_hash(self._frags_10)
|
|
|
|
def time_hash_100_fragments(self) -> None:
|
|
"""Benchmark hashing 100 fragments."""
|
|
compute_context_hash(self._frags_100)
|
|
|
|
def time_hash_1000_fragments(self) -> None:
|
|
"""Benchmark hashing 1000 fragments."""
|
|
compute_context_hash(self._frags_1000)
|