Files
freemo 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
feat(correcting-plans): implement Predictive Error Prevention (Layer 4) with Error Pattern Database
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
2026-03-08 21:53:21 -04:00

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)