583e6b7ea2
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 17s
CI / security (pull_request) Successful in 36s
CI / typecheck (pull_request) Successful in 54s
CI / unit_tests (pull_request) Successful in 2m39s
CI / integration_tests (pull_request) Successful in 3m10s
CI / docker (pull_request) Successful in 40s
CI / coverage (pull_request) Successful in 5m0s
CI / lint (push) Successful in 14s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 34s
CI / typecheck (push) Successful in 35s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m23s
CI / docker (push) Successful in 38s
CI / integration_tests (push) Successful in 3m8s
CI / coverage (push) Successful in 5m10s
CI / benchmark-publish (push) Successful in 16m26s
CI / benchmark-regression (pull_request) Successful in 30m12s
Implement spec-mandated Layer 4 Predictive Error Prevention system: - ErrorPattern domain model with pattern text, historical failures, preventive checks, frequency tracking, and keyword-based matching. - ErrorPatternRepository with in-memory CRUD + context-matching query. - ErrorPatternService with record_failure(), match_patterns(), and get_statistics() methods. - Wire into plan execution via plan_lifecycle_service pre-execution hook. - Add error pattern statistics to CLI diagnostics output. Behave BDD: 11 scenarios covering recording, matching, formatting, stats. Robot Framework: 3 integration smoke tests. ASV benchmarks: pattern matching performance. ISSUES CLOSED: #571
126 lines
3.7 KiB
Python
126 lines
3.7 KiB
Python
"""ASV benchmarks for LLM trace observability overhead.
|
|
|
|
Measures the time to create LLMTrace models, record traces via
|
|
TraceService, compute metrics, and exercise lifecycle hooks.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
_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.trace_service import TraceService # noqa: E402
|
|
from cleveragents.domain.models.observability.llm_trace import LLMTrace # noqa: E402
|
|
from cleveragents.domain.models.observability.metrics import ( # noqa: E402
|
|
MetricCollector,
|
|
OperationalMetricKey,
|
|
)
|
|
|
|
VALID_PLAN_ID = "01HX0000000000PPPPPPPPPPPP"
|
|
|
|
|
|
class InMemoryRepo:
|
|
"""Minimal in-memory repo for benchmarking."""
|
|
|
|
def __init__(self) -> None:
|
|
self._store: dict[str, LLMTrace] = {}
|
|
|
|
def save(self, trace: LLMTrace) -> None:
|
|
self._store[trace.trace_id] = trace
|
|
|
|
def get(self, trace_id: str) -> LLMTrace | None:
|
|
return self._store.get(trace_id)
|
|
|
|
def list_by_plan(self, plan_id: str) -> list[LLMTrace]:
|
|
return [t for t in self._store.values() if t.plan_id == plan_id]
|
|
|
|
def list_by_decision(self, decision_id: str) -> list[LLMTrace]:
|
|
return [t for t in self._store.values() if t.decision_id == decision_id]
|
|
|
|
|
|
def _make_trace(idx: int) -> LLMTrace:
|
|
idx_str = f"{idx:08d}"
|
|
ulid = f"01HX{'A' * (22 - len(idx_str))}{idx_str}"
|
|
return LLMTrace(
|
|
trace_id=ulid,
|
|
plan_id=VALID_PLAN_ID,
|
|
actor="planner",
|
|
provider="openai",
|
|
model="gpt-4o",
|
|
prompt_tokens=500 + idx,
|
|
completion_tokens=200 + idx,
|
|
cost_usd=0.01 + idx * 0.001,
|
|
latency_ms=300.0 + idx,
|
|
)
|
|
|
|
|
|
class LLMTraceModelCreationSuite:
|
|
"""Benchmark LLMTrace model creation."""
|
|
|
|
def time_create_single_trace(self) -> None:
|
|
"""Create a single LLMTrace model."""
|
|
_make_trace(0)
|
|
|
|
def time_create_100_traces(self) -> None:
|
|
"""Create 100 LLMTrace models."""
|
|
for i in range(100):
|
|
_make_trace(i)
|
|
|
|
|
|
class LLMTraceServiceSuite:
|
|
"""Benchmark TraceService operations."""
|
|
|
|
def setup(self) -> None:
|
|
self._svc = TraceService(
|
|
settings=MagicMock(),
|
|
repository=InMemoryRepo(), # type: ignore[arg-type]
|
|
)
|
|
self._traces = [_make_trace(i) for i in range(100)]
|
|
|
|
def time_record_100_traces(self) -> None:
|
|
"""Record 100 traces."""
|
|
for t in self._traces:
|
|
self._svc.record_trace(t)
|
|
|
|
def time_compute_metrics_100(self) -> None:
|
|
"""Compute metrics over 100 traces."""
|
|
for t in self._traces:
|
|
self._svc.record_trace(t)
|
|
self._svc.compute_metrics(VALID_PLAN_ID)
|
|
|
|
|
|
class MetricCollectorSuite:
|
|
"""Benchmark MetricCollector operations."""
|
|
|
|
def time_record_metric(self) -> None:
|
|
"""Create a single metric entry."""
|
|
MetricCollector.record(
|
|
OperationalMetricKey.PLAN_DURATION_MS,
|
|
1500.0,
|
|
VALID_PLAN_ID,
|
|
)
|
|
|
|
def time_plan_duration_convenience(self) -> None:
|
|
"""Use plan_duration convenience method."""
|
|
MetricCollector.plan_duration(VALID_PLAN_ID, 1500.0)
|
|
|
|
def time_lifecycle_hooks(self) -> None:
|
|
"""Exercise all lifecycle hooks."""
|
|
svc = TraceService(
|
|
settings=MagicMock(),
|
|
repository=InMemoryRepo(), # type: ignore[arg-type]
|
|
)
|
|
svc.on_plan_start(VALID_PLAN_ID)
|
|
svc.on_actor_invocation(VALID_PLAN_ID, "planner", 250.0)
|
|
svc.on_tool_execution(VALID_PLAN_ID, "file_write", errored=False)
|