134dce896a
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 17s
CI / lint (pull_request) Successful in 18s
CI / quality (pull_request) Successful in 20s
CI / security (pull_request) Successful in 34s
CI / typecheck (pull_request) Successful in 37s
CI / integration_tests (pull_request) Successful in 3m46s
CI / unit_tests (pull_request) Successful in 8m32s
CI / docker (pull_request) Successful in 39s
CI / benchmark-regression (pull_request) Successful in 19m26s
CI / coverage (pull_request) Successful in 31m23s
CI / lint (push) Successful in 13s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 16s
CI / typecheck (push) Successful in 30s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 33s
CI / integration_tests (push) Successful in 3m44s
CI / unit_tests (push) Successful in 8m53s
CI / docker (push) Successful in 1m1s
CI / benchmark-publish (push) Successful in 10m35s
CI / coverage (push) Successful in 37m55s
Add token/cost tracking, budget enforcement (per-plan and per-day), provider fallback selection with capability filtering, and cost metadata for plan models. New modules: - providers/cost_table.py: CostEntry + ProviderCostTable with default pricing for OpenAI, Anthropic, Google, Groq, Together, Cohere, Mock - providers/cost_tracker.py: BudgetStatus enum, BudgetCheckResult, CostTracker with warn-at-90%/block-at-100% enforcement - providers/fallback_selector.py: FallbackSelector with capability filtering (tool_calls, streaming, vision, json_mode) and budget - domain/models/core/cost_metadata.py: CostMetadata + BudgetExhaustionEvent Modified: - config/settings.py: budget_per_plan, budget_per_day, fallback_providers - domain/models/core/plan.py: cost_metadata field + CLI dict surfacing - providers/__init__.py: public exports for new classes Testing: - 71 Behave scenarios in features/cost_controls.feature (all pass) - 6 Robot Framework integration tests (all pass) - ASV benchmarks in benchmarks/cost_controls_bench.py - nox lint, typecheck, format, docs, build, dead_code, security_scan pass Closes #324
145 lines
4.1 KiB
Python
145 lines
4.1 KiB
Python
"""ASV benchmarks for cost controls overhead.
|
|
|
|
Measures performance of:
|
|
- CostEntry creation and estimation
|
|
- ProviderCostTable lookups
|
|
- CostTracker budget checks and recording
|
|
- FallbackSelector provider selection
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Ensure the local source tree is importable
|
|
_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.config.settings import Settings # noqa: E402
|
|
from cleveragents.domain.models.core.cost_metadata import CostMetadata # noqa: E402
|
|
from cleveragents.providers.cost_table import ( # noqa: E402
|
|
CostEntry,
|
|
ProviderCostTable,
|
|
)
|
|
from cleveragents.providers.cost_tracker import CostTracker # noqa: E402
|
|
from cleveragents.providers.fallback_selector import FallbackSelector # noqa: E402
|
|
from cleveragents.providers.registry import ProviderRegistry # noqa: E402
|
|
|
|
|
|
class TimeCostEntry:
|
|
"""Benchmark CostEntry creation and estimation."""
|
|
|
|
def time_create_cost_entry(self) -> None:
|
|
CostEntry(input_cost_per_token=0.00001, output_cost_per_token=0.00003)
|
|
|
|
def time_estimate_cost(self) -> None:
|
|
entry = CostEntry(input_cost_per_token=0.00001, output_cost_per_token=0.00003)
|
|
entry.estimate_cost(1000, 500)
|
|
|
|
|
|
class TimeCostTable:
|
|
"""Benchmark ProviderCostTable lookups."""
|
|
|
|
def setup(self) -> None:
|
|
self.table = ProviderCostTable()
|
|
|
|
def time_lookup_known_model(self) -> None:
|
|
self.table.get_cost_entry("openai", "gpt-4o")
|
|
|
|
def time_lookup_unknown_model(self) -> None:
|
|
self.table.get_cost_entry("openai", "unknown-model")
|
|
|
|
def time_list_providers(self) -> None:
|
|
self.table.list_providers()
|
|
|
|
def time_list_models(self) -> None:
|
|
self.table.list_models("openai")
|
|
|
|
|
|
class TimeCostTracker:
|
|
"""Benchmark CostTracker budget checks and recording."""
|
|
|
|
def setup(self) -> None:
|
|
self.tracker = CostTracker(budget_per_plan=10.0, budget_per_day=50.0)
|
|
self.tracker_unlimited = CostTracker()
|
|
|
|
def time_record_usage_with_budget(self) -> None:
|
|
metadata = CostMetadata()
|
|
self.tracker.record_usage(
|
|
metadata,
|
|
provider="openai",
|
|
model="gpt-4o",
|
|
input_tokens=1000,
|
|
output_tokens=500,
|
|
)
|
|
|
|
def time_record_usage_unlimited(self) -> None:
|
|
metadata = CostMetadata()
|
|
self.tracker_unlimited.record_usage(
|
|
metadata,
|
|
provider="openai",
|
|
model="gpt-4o",
|
|
input_tokens=1000,
|
|
output_tokens=500,
|
|
)
|
|
|
|
def time_check_plan_budget(self) -> None:
|
|
metadata = CostMetadata()
|
|
self.tracker.check_plan_budget(metadata)
|
|
|
|
def time_check_daily_budget(self) -> None:
|
|
self.tracker.check_daily_budget()
|
|
|
|
def time_estimate_cost(self) -> None:
|
|
self.tracker.estimate_cost("openai", "gpt-4o", 1000, 500)
|
|
|
|
|
|
class TimeFallbackSelector:
|
|
"""Benchmark FallbackSelector provider selection."""
|
|
|
|
def setup(self) -> None:
|
|
settings = Settings()
|
|
self.registry = ProviderRegistry(settings)
|
|
self.selector = FallbackSelector(registry=self.registry)
|
|
|
|
def time_select_no_requirements(self) -> None:
|
|
self.selector.select()
|
|
|
|
def time_select_with_tool_calls(self) -> None:
|
|
self.selector.select(require_tool_calls=True)
|
|
|
|
def time_select_with_all_requirements(self) -> None:
|
|
self.selector.select(
|
|
require_tool_calls=True,
|
|
require_streaming=True,
|
|
require_vision=True,
|
|
require_json_mode=True,
|
|
)
|
|
|
|
|
|
class TimeCostMetadata:
|
|
"""Benchmark CostMetadata operations."""
|
|
|
|
def time_create_metadata(self) -> None:
|
|
CostMetadata()
|
|
|
|
def time_record_usage(self) -> None:
|
|
meta = CostMetadata()
|
|
meta.record_usage(
|
|
input_tokens=1000,
|
|
output_tokens=500,
|
|
cost=0.025,
|
|
provider="openai",
|
|
)
|
|
|
|
def time_display_dict(self) -> None:
|
|
meta = CostMetadata(total_tokens=1500, total_cost=0.025)
|
|
meta.as_display_dict()
|