Files
freemo 876217d0ca
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 16s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 24s
CI / typecheck (pull_request) Successful in 37s
CI / security (pull_request) Successful in 44s
CI / unit_tests (pull_request) Successful in 2m40s
CI / integration_tests (pull_request) Successful in 3m19s
CI / docker (pull_request) Successful in 42s
CI / coverage (pull_request) Successful in 5m10s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 16s
CI / quality (push) Successful in 21s
CI / security (push) Successful in 32s
CI / typecheck (push) Successful in 37s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Failing after 2m36s
CI / docker (push) Has been skipped
CI / integration_tests (push) Successful in 3m16s
CI / coverage (push) Successful in 5m3s
CI / benchmark-publish (push) Successful in 17m54s
CI / benchmark-regression (pull_request) Successful in 31m55s
feat(guardrails): implement Per-Session and Per-Org Cost Budgets
Implements Forgejo issue #584: three-tier budget hierarchy
(per-plan -> per-session -> per-org) with tightest limit winning.

Domain models:
- BudgetLevel enum (PLAN, SESSION, ORG)
- BudgetCheckResult (frozen Pydantic model with exceeded_level, warning)
- SessionCostBudget (tracks per-session accumulated cost)
- OrgCostAccumulator (tracks per-org accumulated cost)
- ThreadSafeOrgCostAccumulator (thread-safe wrapper with snapshot)

Application services:
- CostBudgetService: manages budget state, enforces hierarchy,
  emits BUDGET_WARNING (once per session) and BUDGET_EXCEEDED events
- AutonomyGuardrailService: extended with associate_plan_with_session,
  check_budget_hierarchy, record_plan_cost_to_session methods

Configuration:
- Settings: session_max_cost_usd, org_max_cost_usd, budget_warning_threshold

Integration:
- Session model: cost_budget field, as_cli_dict includes budget data
- DI container: CostBudgetService registered as Singleton
- CLI session show: cost budget panel display

Tests:
- 54 Behave scenarios (features/cost_budgets.feature)
- 11 Robot Framework integration tests (robot/cost_budgets.robot)
- ASV benchmarks (benchmarks/bench_budget_check.py)

All nox stages pass: lint, typecheck, unit_tests, coverage_report (98%).

CLOSES #584
2026-03-10 14:18:04 -04:00

153 lines
5.4 KiB
Python

"""ASV benchmarks for per-session and per-org cost budget checks.
Measures the performance of:
- SessionCostBudget creation and cost recording
- OrgCostAccumulator creation and cost recording
- CostBudgetService.check_budget_hierarchy with varying configurations
- AutonomyGuardrailService.check_budget_hierarchy integration
"""
from __future__ import annotations
import importlib
import sys
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.autonomy_guardrail_service import ( # noqa: E402
AutonomyGuardrailService,
)
from cleveragents.application.services.cost_budget_service import ( # noqa: E402
CostBudgetService,
)
from cleveragents.domain.models.core.autonomy_guardrails import ( # noqa: E402
AutonomyGuardrails,
)
from cleveragents.domain.models.core.cost_budget import ( # noqa: E402
OrgCostAccumulator,
SessionCostBudget,
ThreadSafeOrgCostAccumulator,
)
class SessionCostBudgetSuite:
"""Benchmark SessionCostBudget operations."""
def time_creation_defaults(self) -> None:
"""Benchmark creating a SessionCostBudget with defaults."""
SessionCostBudget()
def time_creation_with_cap(self) -> None:
"""Benchmark creating a SessionCostBudget with a cap."""
SessionCostBudget(max_cost_usd=100.0)
def time_record_cost(self) -> None:
"""Benchmark recording cost to a session budget."""
b = SessionCostBudget(max_cost_usd=1000.0)
for _ in range(100):
b.record_cost(1.0)
def time_utilization(self) -> None:
"""Benchmark utilization calculation."""
b = SessionCostBudget(max_cost_usd=100.0, total_cost=50.0)
for _ in range(1000):
b.utilization()
def time_would_exceed(self) -> None:
"""Benchmark would_exceed check."""
b = SessionCostBudget(max_cost_usd=100.0, total_cost=50.0)
for _ in range(1000):
b.would_exceed(10.0)
class OrgCostAccumulatorSuite:
"""Benchmark OrgCostAccumulator operations."""
def time_creation(self) -> None:
"""Benchmark creating an OrgCostAccumulator."""
OrgCostAccumulator(org_id="bench-org", max_cost_usd=1000.0)
def time_record_cost(self) -> None:
"""Benchmark recording cost to an org accumulator."""
acc = OrgCostAccumulator(org_id="bench-org", max_cost_usd=10000.0)
for _ in range(100):
acc.record_cost(1.0)
def time_thread_safe_record(self) -> None:
"""Benchmark thread-safe cost recording."""
inner = OrgCostAccumulator(org_id="bench-org", max_cost_usd=10000.0)
ts = ThreadSafeOrgCostAccumulator(inner)
for _ in range(100):
ts.record_cost(1.0)
class CostBudgetServiceSuite:
"""Benchmark CostBudgetService hierarchy checks."""
def setup(self) -> None:
"""Configure service with session and org budgets."""
self._svc = CostBudgetService(warning_threshold=0.8)
self._svc.configure_session_budget(
"bench-sess", max_cost_usd=1000.0, org_id="bench-org"
)
self._svc.configure_org_budget("bench-org", max_cost_usd=5000.0)
def time_hierarchy_check_allowed(self) -> None:
"""Benchmark hierarchy check that is allowed."""
self._svc.check_budget_hierarchy(
session_id="bench-sess",
plan_cost=1.0,
)
def time_hierarchy_check_with_plan_budget(self) -> None:
"""Benchmark hierarchy check with plan budget."""
self._svc.check_budget_hierarchy(
session_id="bench-sess",
plan_cost=1.0,
plan_budget=100.0,
plan_spent=10.0,
)
def time_record_plan_cost(self) -> None:
"""Benchmark recording plan cost through the service."""
svc = CostBudgetService()
svc.configure_session_budget("rs", max_cost_usd=100000.0)
for _ in range(100):
svc.record_plan_cost("rs", 1.0)
class GuardrailBudgetIntegrationSuite:
"""Benchmark AutonomyGuardrailService budget hierarchy integration."""
def setup(self) -> None:
"""Configure guardrail service with budget hierarchy."""
self._cbs = CostBudgetService(warning_threshold=0.8)
self._cbs.configure_session_budget(
"g-sess", max_cost_usd=1000.0, org_id="g-org"
)
self._cbs.configure_org_budget("g-org", max_cost_usd=5000.0)
self._gs = AutonomyGuardrailService(cost_budget_service=self._cbs)
self._gs.configure_guardrails("g-plan", AutonomyGuardrails(tool_budget=500.0))
self._gs.associate_plan_with_session("g-plan", "g-sess")
def time_check_budget_hierarchy(self) -> None:
"""Benchmark guardrail service budget hierarchy check."""
self._gs.check_budget_hierarchy("g-plan", 1.0)
def time_record_plan_cost_to_session(self) -> None:
"""Benchmark recording plan cost through guardrail service."""
cbs = CostBudgetService()
cbs.configure_session_budget("rps", max_cost_usd=100000.0)
gs = AutonomyGuardrailService(cost_budget_service=cbs)
gs.configure_guardrails("rp", AutonomyGuardrails(tool_budget=100000.0))
gs.associate_plan_with_session("rp", "rps")
for _ in range(100):
gs.record_plan_cost_to_session("rp", 1.0)