3cf3f1f69e
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 15s
CI / lint (pull_request) Successful in 3m40s
CI / quality (pull_request) Successful in 4m13s
CI / security (pull_request) Successful in 4m16s
CI / typecheck (pull_request) Successful in 4m16s
CI / integration_tests (pull_request) Successful in 9m11s
CI / unit_tests (pull_request) Successful in 9m23s
CI / docker (pull_request) Successful in 1m18s
CI / coverage (pull_request) Successful in 11m48s
CI / status-check (pull_request) Successful in 7s
CI / e2e_tests (pull_request) Successful in 8m24s
CI / lint (push) Successful in 3m18s
CI / build (push) Failing after 13s
CI / typecheck (push) Successful in 3m54s
CI / benchmark-regression (push) Has been skipped
CI / quality (push) Successful in 4m15s
CI / security (push) Successful in 4m39s
CI / integration_tests (push) Successful in 7m21s
CI / unit_tests (push) Successful in 7m44s
CI / docker (push) Successful in 1m9s
CI / e2e_tests (push) Successful in 11m9s
CI / coverage (push) Successful in 13m41s
CI / status-check (push) Successful in 2s
CI / benchmark-regression (pull_request) Successful in 48m49s
CI / benchmark-publish (push) Successful in 26m25s
Implemented FixThenRevalidateOrchestrator with full diagnosis → self-fix → re-validation → retry limit → strategy revision → user escalation → terminal failure flow. Added retry counting per validation per plan, configurable retry limit (default 3), auto_strategy_revision flag support, and validation_fix_history recording in plan execution metadata. Review fixes applied (round 1): - Added try/except around fix_callback and revalidate_callback (spec: validation errors treated as required failure regardless of mode) - Wired optional EventBus for VALIDATION_FIX_ATTEMPTED/SUCCEEDED/EXHAUSTED events - Fixed total_attempts derivation from fix_history length instead of cumulative retry counts - Updated module docstring to reflect implemented vs caller-responsible steps - Added exhausted-retry logging on re-invocation - Replaced bare assert in Robot helper with explicit _check() function - Added 10 new Behave scenarios covering exception paths, multi-failure, reset, field validation, boundary values, event_bus property, and exhausted re-invocation Review fixes applied (round 2 — PR #711): - B1+B7: Fixed TOCTOU race in _fix_single_validation; atomic claim-per-iteration under RLock; eliminated defaultdict auto-vivification with .get() reads - B2: Added validation_name mismatch check on revalidate_callback return - B3: Added fix_description truncation to 2000 chars before FixAttemptRecord - T1: Switched threading.Lock to threading.RLock for defensive reentrancy - A1: Added model_validator preventing escalated+terminal_failure both True - R1: Added max_length=255 to FixAttemptRecord.validation_name - S7: Fixed misleading docstring about automation_profile integration - D4: Fixed timestamp field description to specify UTC - D1: Renamed misleading benchmark time_fix_after_two_retries - Added 5 new Behave scenarios for truncation, model constraints, name mismatch ISSUES CLOSED: #583
246 lines
7.6 KiB
Python
246 lines
7.6 KiB
Python
"""ASV benchmarks for the Fix-then-Revalidate orchestration loop.
|
|
|
|
Measures the performance of:
|
|
- Orchestrator creation
|
|
- Fix loop with immediate pass
|
|
- Fix loop with multiple retries
|
|
- Fix loop with terminal failure
|
|
- Model creation and serialization
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
try:
|
|
from cleveragents.application.services.fix_then_revalidate import (
|
|
FixAttemptRecord,
|
|
FixThenRevalidateOrchestrator,
|
|
FixThenRevalidateResult,
|
|
)
|
|
from cleveragents.application.services.validation_pipeline import (
|
|
ValidationPipeline,
|
|
ValidationResult,
|
|
)
|
|
from cleveragents.domain.models.core.tool import ValidationMode
|
|
except ModuleNotFoundError:
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
from cleveragents.application.services.fix_then_revalidate import (
|
|
FixAttemptRecord,
|
|
FixThenRevalidateOrchestrator,
|
|
FixThenRevalidateResult,
|
|
)
|
|
from cleveragents.application.services.validation_pipeline import (
|
|
ValidationPipeline,
|
|
ValidationResult,
|
|
)
|
|
from cleveragents.domain.models.core.tool import ValidationMode
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _mock_executor(validation_name: str, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
return {"passed": True, "message": f"{validation_name} ok"}
|
|
|
|
|
|
def _make_failed_result(name: str) -> ValidationResult:
|
|
return ValidationResult(
|
|
validation_name=name,
|
|
resource_id="RES0001",
|
|
resource_name="bench-resource",
|
|
mode=ValidationMode.REQUIRED,
|
|
passed=False,
|
|
message=f"{name} failed",
|
|
data={"hint": "bench"},
|
|
duration_ms=1.0,
|
|
)
|
|
|
|
|
|
def _always_fix(result: ValidationResult) -> str:
|
|
return f"Fixed {result.validation_name}"
|
|
|
|
|
|
class _CountingRevalidate:
|
|
def __init__(self, pass_after: int) -> None:
|
|
self._pass_after = pass_after
|
|
self._count = 0
|
|
|
|
def __call__(self, result: ValidationResult) -> ValidationResult:
|
|
self._count += 1
|
|
passed = self._count >= self._pass_after
|
|
return ValidationResult(
|
|
validation_name=result.validation_name,
|
|
resource_id=result.resource_id,
|
|
resource_name=result.resource_name,
|
|
mode=result.mode,
|
|
passed=passed,
|
|
message="passed" if passed else "failing",
|
|
data=result.data,
|
|
duration_ms=1.0,
|
|
)
|
|
|
|
|
|
class _NeverPassRevalidate:
|
|
def __call__(self, result: ValidationResult) -> ValidationResult:
|
|
return ValidationResult(
|
|
validation_name=result.validation_name,
|
|
resource_id=result.resource_id,
|
|
resource_name=result.resource_name,
|
|
mode=result.mode,
|
|
passed=False,
|
|
message="failing",
|
|
data=result.data,
|
|
duration_ms=1.0,
|
|
)
|
|
|
|
|
|
def _build_pipeline() -> ValidationPipeline:
|
|
return ValidationPipeline(commands=[], executor=_mock_executor)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Benchmark suites
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class FixRevalidateCreationSuite:
|
|
"""Benchmarks for orchestrator instantiation."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
self.pipeline = _build_pipeline()
|
|
|
|
def time_create_orchestrator_default(self) -> None:
|
|
"""Benchmark creating an orchestrator with default settings."""
|
|
FixThenRevalidateOrchestrator(
|
|
validation_pipeline=self.pipeline,
|
|
)
|
|
|
|
def time_create_orchestrator_custom(self) -> None:
|
|
"""Benchmark creating an orchestrator with custom settings."""
|
|
FixThenRevalidateOrchestrator(
|
|
validation_pipeline=self.pipeline,
|
|
max_retries=5,
|
|
auto_strategy_revision=0.0,
|
|
)
|
|
|
|
|
|
class FixRevalidateLoopSuite:
|
|
"""Benchmarks for the fix loop execution."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
self.pipeline = _build_pipeline()
|
|
|
|
def time_fix_first_attempt(self) -> None:
|
|
"""Benchmark fix loop that succeeds on first attempt."""
|
|
orchestrator = FixThenRevalidateOrchestrator(
|
|
validation_pipeline=self.pipeline,
|
|
max_retries=3,
|
|
)
|
|
failed = [_make_failed_result("bench-check")]
|
|
orchestrator.run_fix_loop(
|
|
plan_id="BENCH001",
|
|
failed_results=failed,
|
|
fix_callback=_always_fix,
|
|
revalidate_callback=_CountingRevalidate(pass_after=1),
|
|
)
|
|
|
|
def time_fix_on_second_attempt(self) -> None:
|
|
"""Benchmark fix loop that succeeds on the second attempt."""
|
|
orchestrator = FixThenRevalidateOrchestrator(
|
|
validation_pipeline=self.pipeline,
|
|
max_retries=3,
|
|
)
|
|
failed = [_make_failed_result("bench-check")]
|
|
orchestrator.run_fix_loop(
|
|
plan_id="BENCH002",
|
|
failed_results=failed,
|
|
fix_callback=_always_fix,
|
|
revalidate_callback=_CountingRevalidate(pass_after=2),
|
|
)
|
|
|
|
def time_terminal_failure(self) -> None:
|
|
"""Benchmark fix loop that results in terminal failure."""
|
|
orchestrator = FixThenRevalidateOrchestrator(
|
|
validation_pipeline=self.pipeline,
|
|
max_retries=3,
|
|
)
|
|
failed = [_make_failed_result("bench-check")]
|
|
orchestrator.run_fix_loop(
|
|
plan_id="BENCH003",
|
|
failed_results=failed,
|
|
fix_callback=_always_fix,
|
|
revalidate_callback=_NeverPassRevalidate(),
|
|
)
|
|
|
|
def time_empty_failures(self) -> None:
|
|
"""Benchmark fix loop with no failures (immediate pass)."""
|
|
orchestrator = FixThenRevalidateOrchestrator(
|
|
validation_pipeline=self.pipeline,
|
|
max_retries=3,
|
|
)
|
|
orchestrator.run_fix_loop(
|
|
plan_id="BENCH004",
|
|
failed_results=[],
|
|
fix_callback=_always_fix,
|
|
revalidate_callback=_CountingRevalidate(pass_after=1),
|
|
)
|
|
|
|
|
|
class FixRevalidateModelSuite:
|
|
"""Benchmarks for model creation and serialization."""
|
|
|
|
timeout = 60
|
|
|
|
def time_create_attempt_record(self) -> None:
|
|
"""Benchmark creating a FixAttemptRecord."""
|
|
FixAttemptRecord(
|
|
attempt_number=1,
|
|
validation_name="bench-check",
|
|
fix_description="Applied fix",
|
|
success=True,
|
|
)
|
|
|
|
def time_create_result_model(self) -> None:
|
|
"""Benchmark creating a FixThenRevalidateResult."""
|
|
records = [
|
|
FixAttemptRecord(
|
|
attempt_number=i + 1,
|
|
validation_name=f"check-{i}",
|
|
fix_description=f"Fix {i}",
|
|
success=i == 2,
|
|
)
|
|
for i in range(3)
|
|
]
|
|
FixThenRevalidateResult(
|
|
validation_attempts=3,
|
|
final_passed=True,
|
|
validation_fix_history=records,
|
|
escalated=False,
|
|
terminal_failure=False,
|
|
)
|
|
|
|
def time_result_model_dump(self) -> None:
|
|
"""Benchmark serializing FixThenRevalidateResult to dict."""
|
|
records = [
|
|
FixAttemptRecord(
|
|
attempt_number=1,
|
|
validation_name="check-a",
|
|
fix_description="Fix a",
|
|
success=True,
|
|
),
|
|
]
|
|
result = FixThenRevalidateResult(
|
|
validation_attempts=1,
|
|
final_passed=True,
|
|
validation_fix_history=records,
|
|
)
|
|
result.model_dump()
|