feat(spend): report guardrail spend separately in daily activity (#51010)

* feat(spend): report guardrail spend separately in daily activity

Model and key spend in /user/daily/activity/aggregated include the cost of
guardrails like Bedrock guardrails, and nothing in the response says how much.
A customer that bills tenants per key and per model could not get model cost
alone, so tenants that pay AWS for their own guardrails were charged for them
twice

Daily spend rows now carry a guardrail_spend column next to spend, and every
breakdown in the daily activity responses exposes it, so model cost is
spend - guardrail_spend. spend keeps its meaning, so budgets still count
guardrail cost as before

The value comes from the request's cost_breakdown.guardrail_cost, capped at
the row's spend because an allowance discount or a repeat job poll can charge
less than the guardrail cost the breakdown records. The delivery ledger
freezes it into the accounting payload next to accounting_savings, since the
stored payload does not keep cost_breakdown, and the legacy daily queue writes
the same value

The migration only adds the column with a 0 default, so rows written before it
read 0

* fix(spend): count guardrail spend on rows billed without a cost breakdown

A request blocked before the LLM call is billed for its guardrails straight from
guardrail_information, so it carries no cost_breakdown and its guardrail_spend
read 0. Both writers now price guardrail_spend from guardrail_information with
guardrail_information_cost, the same function success and failure rows use to add
guardrail cost to spend

Also fills guardrail_spend in the client usage view built from request logs,
qualifies the field description for rows recorded before it existed, and adds
the lock timeout the migration checks require

* fix(ui): avoid a nested ternary when reading guardrail entries from request logs

---------

Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
This commit is contained in:
joshua-berri 2026-11-10 13:02:26 -08:00 • committed by GitHub
parent 8fb2b97d5b
commit 894552205b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 432 additions and 7 deletions

View file

@ -0,0 +1,22 @@
SET LOCAL lock_timeout = '10s';
-- AlterTable
ALTER TABLE "LiteLLM_DailyUserSpend" ADD COLUMN IF NOT EXISTS "guardrail_spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0;
-- AlterTable
ALTER TABLE "LiteLLM_DailyTeamSpend" ADD COLUMN IF NOT EXISTS "guardrail_spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0;
-- AlterTable
ALTER TABLE "LiteLLM_DailyOrganizationSpend" ADD COLUMN IF NOT EXISTS "guardrail_spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0;
-- AlterTable
ALTER TABLE "LiteLLM_DailyEndUserSpend" ADD COLUMN IF NOT EXISTS "guardrail_spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0;
-- AlterTable
ALTER TABLE "LiteLLM_DailyAgentSpend" ADD COLUMN IF NOT EXISTS "guardrail_spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0;
-- AlterTable
ALTER TABLE "LiteLLM_DailyTagSpend" ADD COLUMN IF NOT EXISTS "guardrail_spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0;
-- AlterTable
ALTER TABLE "LiteLLM_DailyGlobalSpend" ADD COLUMN IF NOT EXISTS "guardrail_spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0;

View file

@ -883,6 +883,7 @@ model LiteLLM_DailyUserSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -919,6 +920,7 @@ model LiteLLM_DailyGlobalSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -952,6 +954,7 @@ model LiteLLM_DailyOrganizationSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -990,6 +993,7 @@ model LiteLLM_DailyEndUserSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -1027,6 +1031,7 @@ model LiteLLM_DailyAgentSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -1064,6 +1069,7 @@ model LiteLLM_DailyTeamSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -1104,6 +1110,7 @@ model LiteLLM_DailyTagSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)

View file

@ -133,6 +133,7 @@ def _daily_counters(row: Mapping[str, object], metadata: Mapping[str, object]) -
("prompt_caching_savings_spend", _number(savings.get("prompt_caching"))),
("gateway_injected_caching_savings_spend", _number(savings.get("gateway_injected_caching"))),
("autorouter_savings_spend", 0.0 if internal else _number(savings.get("autorouter"))),
("guardrail_spend", _number(metadata.get("accounting_guardrail_spend"))),
("total_response_time_ms", _number(duration) if timed else 0),
("timed_requests", int(timed)),
)

View file

@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Final
from pydantic import TypeAdapter, ValidationError
from litellm._logging import verbose_proxy_logger
from litellm.litellm_core_utils.llm_cost_calc.guardrail_cost import guardrail_share_of_spend
from litellm.litellm_core_utils.safe_json_dumps import safe_json_structure
from litellm.proxy._types import SpendLogsPayload
from litellm.proxy.spend_tracking.compression_savings import extract_compression_saved_tokens
@ -73,6 +74,7 @@ def build_accounting_payload(
**{key: _accounting_metadata_field(key, value) for key, value in metadata.items() if key in _METADATA_FIELDS},
"accounting_entities": entities,
"accounting_savings": savings._asdict(),
"accounting_guardrail_spend": guardrail_share_of_spend(metadata, payload["spend"]),
"accounting_model_access_groups": model_access_groups,
"accounting_windows": windows,
}

View file

@ -311,6 +311,12 @@ def guardrail_information_cost(guardrail_information: object) -> float:
return _validated_entry_cost(guardrail_information)
def guardrail_share_of_spend(spend_log_metadata: Mapping[str, object], spend: float) -> float:
"""The part of a spend row's ``spend`` billed for its guardrails, priced the way success and failure rows add
it to spend and capped at the row's spend, since an allowance discount or a repeat job poll can charge less."""
return min(guardrail_information_cost(spend_log_metadata.get("guardrail_information")), max(spend, 0.0))
def cost_breakdown_with_guardrail(cost_breakdown: CostBreakdown | None, guardrail_cost: float) -> CostBreakdown | None:
if guardrail_cost <= 0.0:
return cost_breakdown

View file

@ -4882,6 +4882,12 @@
"title": "Gateway Injected Caching Savings Spend",
"type": "number"
},
"guardrail_spend": {
"default": 0.0,
"description": "Guardrail spend in USD, already included in spend, so model spend is spend - guardrail_spend. Rows recorded before this field existed report 0 even when their spend includes guardrail cost",
"title": "Guardrail Spend",
"type": "number"
},
"prompt_caching_savings_spend": {
"default": 0.0,
"title": "Prompt Caching Savings Spend",

View file

@ -5335,6 +5335,7 @@ class BaseDailySpendTransaction(TypedDict):
# missing value to zero, so requiring it here would describe a shape the aggregation
# is explicitly tested against.
autorouter_savings_spend: NotRequired[float]
guardrail_spend: NotRequired[float] # writable-ok: the rollup queue accumulates into this key in place
# request level metrics
spend: float

View file

@ -98,6 +98,7 @@ _SPEND_COLUMNS: Final = (
"prompt_caching_savings_spend",
"gateway_injected_caching_savings_spend",
"autorouter_savings_spend",
"guardrail_spend",
)
_CASTS: Final[Mapping[str, str]] = MappingProxyType(

View file

@ -39,6 +39,7 @@ from litellm.constants import (
from litellm.litellm_core_utils.batch_utils import BATCH_COST_REQUEST_ID_SUFFIX
from litellm.litellm_core_utils.core_helpers import finite_cost, single_log_line
from litellm.litellm_core_utils.litellm_logging import coerce_model_access_groups
from litellm.litellm_core_utils.llm_cost_calc.guardrail_cost import guardrail_share_of_spend
from litellm.litellm_core_utils.safe_json_loads import safe_json_loads
from litellm.proxy._types import (
DB_RETRY_SAFE_ERROR_TYPES,
@ -3164,6 +3165,10 @@ class DBSpendUpdateWriter:
prompt_caching_savings_spend=savings_spend.prompt_caching,
gateway_injected_caching_savings_spend=savings_spend.gateway_injected_caching,
autorouter_savings_spend=0.0 if is_internal_call else savings_spend.autorouter,
guardrail_spend=guardrail_share_of_spend(
_metadata,
payload["spend"], # pyright: ignore[reportUnknownArgumentType] # legacy dict | SpendLogsPayload union
),
total_response_time_ms=timed_duration_ms or 0,
timed_requests=0 if timed_duration_ms is None else 1,
)

View file

@ -154,6 +154,10 @@ class DailySpendUpdateQueue(BaseUpdateQueue):
payload.get("autorouter_savings_spend", 0) or 0
) + daily_transaction.get("autorouter_savings_spend", 0)
daily_transaction["guardrail_spend"] = (
payload.get("guardrail_spend", 0) or 0
) + daily_transaction.get("guardrail_spend", 0)
daily_transaction["total_response_time_ms"] = (
payload.get("total_response_time_ms", 0) or 0
) + daily_transaction.get("total_response_time_ms", 0)

View file

@ -163,6 +163,9 @@ class DailySpendRecord(Protocol):
@property
def autorouter_savings_spend(self) -> float: ...
@property
def guardrail_spend(self) -> float: ...
@property
def api_requests(self) -> int: ...
@ -250,6 +253,7 @@ def update_metrics(existing_metrics: SpendMetrics, record: DailySpendRecord) ->
existing_metrics.prompt_caching_savings_spend += record.prompt_caching_savings_spend or 0
existing_metrics.gateway_injected_caching_savings_spend += record.gateway_injected_caching_savings_spend or 0
existing_metrics.autorouter_savings_spend += record.autorouter_savings_spend or 0
existing_metrics.guardrail_spend += record.guardrail_spend or 0 # rebind-ok: callers rely on in-place accumulation
existing_metrics.api_requests += record.api_requests or 0
existing_metrics.successful_requests += record.successful_requests or 0
existing_metrics.failed_requests += record.failed_requests or 0
@ -736,6 +740,7 @@ def sum_spend_metrics(metrics: Sequence[SpendMetrics]) -> SpendMetrics:
prompt_caching_savings_spend=sum(metric.prompt_caching_savings_spend for metric in metrics),
gateway_injected_caching_savings_spend=sum(metric.gateway_injected_caching_savings_spend for metric in metrics),
autorouter_savings_spend=sum(metric.autorouter_savings_spend for metric in metrics),
guardrail_spend=sum(metric.guardrail_spend for metric in metrics),
total_tokens=sum(metric.total_tokens for metric in metrics),
successful_requests=sum(metric.successful_requests for metric in metrics),
failed_requests=sum(metric.failed_requests for metric in metrics),
@ -766,6 +771,7 @@ def _record_to_spend_metrics(record: RollupMetricsRow) -> SpendMetrics:
prompt_caching_savings_spend=record.prompt_caching_savings_spend or 0,
gateway_injected_caching_savings_spend=record.gateway_injected_caching_savings_spend or 0,
autorouter_savings_spend=record.autorouter_savings_spend or 0,
guardrail_spend=record.guardrail_spend or 0,
api_requests=record.api_requests or 0,
successful_requests=record.successful_requests or 0,
failed_requests=record.failed_requests or 0,

View file

@ -883,6 +883,7 @@ model LiteLLM_DailyUserSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -919,6 +920,7 @@ model LiteLLM_DailyGlobalSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -952,6 +954,7 @@ model LiteLLM_DailyOrganizationSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -990,6 +993,7 @@ model LiteLLM_DailyEndUserSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -1027,6 +1031,7 @@ model LiteLLM_DailyAgentSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -1064,6 +1069,7 @@ model LiteLLM_DailyTeamSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -1104,6 +1110,7 @@ model LiteLLM_DailyTagSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)

View file

@ -132,6 +132,7 @@ class _CustomerDailyRow(BaseModel):
prompt_caching_savings_spend: float
gateway_injected_caching_savings_spend: float
autorouter_savings_spend: float
guardrail_spend: float
spend: float
api_requests: int
successful_requests: int

View file

@ -223,6 +223,7 @@ def _rollup_metric_select(table: DailyActivityTable) -> str:
SUM(prompt_caching_savings_spend)::float AS prompt_caching_savings_spend,
SUM(gateway_injected_caching_savings_spend)::float AS gateway_injected_caching_savings_spend,
SUM(autorouter_savings_spend)::float AS autorouter_savings_spend,
SUM(guardrail_spend)::float AS guardrail_spend,
SUM(api_requests)::bigint AS api_requests,
SUM(successful_requests)::bigint AS successful_requests,
SUM(failed_requests)::bigint AS failed_requests,

View file

@ -31,6 +31,11 @@ class SpendMetrics(LiteLLMBaseModel):
prompt_caching_savings_spend: float = Field(default=0.0)
gateway_injected_caching_savings_spend: float = Field(default=0.0)
autorouter_savings_spend: float = Field(default=0.0)
guardrail_spend: float = Field(
default=0.0,
description="Guardrail spend in USD, already included in spend, so model spend is spend - guardrail_spend. "
"Rows recorded before this field existed report 0 even when their spend includes guardrail cost",
)
total_tokens: int = Field(default=0)
successful_requests: int = Field(default=0)
failed_requests: int = Field(default=0)
@ -259,6 +264,7 @@ class LiteLLM_DailyUserSpend(LiteLLMBaseModel):
prompt_caching_savings_spend: float = 0.0
gateway_injected_caching_savings_spend: float = 0.0
autorouter_savings_spend: float = 0.0
guardrail_spend: float = 0.0
spend: float = 0.0
api_requests: int = 0
successful_requests: int = 0

View file

@ -144,6 +144,7 @@ class RollupMetricsRow:
prompt_caching_savings_spend: float | None
gateway_injected_caching_savings_spend: float | None
autorouter_savings_spend: float | None
guardrail_spend: float | None = field(default=None, kw_only=True)
api_requests: int | None
successful_requests: int | None
failed_requests: int | None
@ -219,6 +220,7 @@ class DailyActivityRow(Protocol):
prompt_caching_savings_spend: float
gateway_injected_caching_savings_spend: float
autorouter_savings_spend: float
guardrail_spend: float
spend: float
api_requests: int
successful_requests: int

View file

@ -883,6 +883,7 @@ model LiteLLM_DailyUserSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -919,6 +920,7 @@ model LiteLLM_DailyGlobalSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -952,6 +954,7 @@ model LiteLLM_DailyOrganizationSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -990,6 +993,7 @@ model LiteLLM_DailyEndUserSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -1027,6 +1031,7 @@ model LiteLLM_DailyAgentSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -1064,6 +1069,7 @@ model LiteLLM_DailyTeamSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)
@ -1104,6 +1110,7 @@ model LiteLLM_DailyTagSpend {
prompt_caching_savings_spend Float @default(0.0)
gateway_injected_caching_savings_spend Float @default(0.0)
autorouter_savings_spend Float @default(0.0)
guardrail_spend Float @default(0.0)
spend Float @default(0.0)
api_requests BigInt @default(0)
successful_requests BigInt @default(0)

View file

@ -424,6 +424,7 @@ async def _assert_legacy_database_parity(
"guardrail_status": "success",
"guardrail_usage": {"text": 2},
"guardrail_cost_by_unit": {"text": 0.02},
"guardrail_cost": 0.02,
"guardrail_cost_in_spend": True,
}
],
@ -579,6 +580,7 @@ async def _assert_legacy_database_parity(
assert daily["autorouter_savings_spend"] == pytest.approx(
(0 if origin == "auto_router" else 0.125) + (0.2 if correction_order else 0)
)
assert daily["guardrail_spend"] == pytest.approx(min(0.02, cost))
group_spend = {row["access_group_name"]: row["spend"] for row in results[-1]["LiteLLM_ModelAccessGroupBudgetTable"]}
assert group_spend == {"group": cost + prior_spend, **({"unserved-group": prior_spend} if fallback_narrowing else {})}
assert results[-1]["LiteLLM_DailyGuardrailUsageUnits"]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -39,6 +39,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -68,6 +69,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -97,6 +99,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -126,6 +129,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -155,6 +159,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -188,6 +193,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -217,6 +223,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -246,6 +253,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -275,6 +283,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -304,6 +313,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -327,6 +337,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -360,6 +371,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -389,6 +401,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -418,6 +431,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -447,6 +461,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -476,6 +491,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -501,6 +517,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -535,6 +552,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -558,6 +576,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -589,6 +608,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -618,6 +638,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -647,6 +668,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -670,6 +692,7 @@
"metrics": {
"api_requests": 3,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 3,
"completion_tokens": 6,
@ -694,6 +717,7 @@
"metrics": {
"api_requests": 0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"completion_tokens": 0,
@ -725,6 +749,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -748,6 +773,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -781,6 +807,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -804,6 +831,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -835,6 +863,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -864,6 +893,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -893,6 +923,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -916,6 +947,7 @@
"metrics": {
"api_requests": 3,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 3,
"completion_tokens": 6,
@ -940,6 +972,7 @@
"metrics": {
"api_requests": 0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"completion_tokens": 0,
@ -971,6 +1004,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -994,6 +1028,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -1027,6 +1062,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1056,6 +1092,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1085,6 +1122,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1114,6 +1152,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1143,6 +1182,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -1166,6 +1206,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -1190,6 +1231,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,

View file

@ -39,6 +39,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -68,6 +69,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -97,6 +99,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -126,6 +129,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -155,6 +159,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -188,6 +193,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -217,6 +223,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -246,6 +253,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -275,6 +283,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -304,6 +313,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -327,6 +337,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -360,6 +371,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -389,6 +401,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -418,6 +431,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -447,6 +461,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -476,6 +491,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -501,6 +517,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -535,6 +552,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -558,6 +576,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -589,6 +608,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -618,6 +638,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -647,6 +668,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -670,6 +692,7 @@
"metrics": {
"api_requests": 3,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 3,
"completion_tokens": 6,
@ -694,6 +717,7 @@
"metrics": {
"api_requests": 0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"completion_tokens": 0,
@ -725,6 +749,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -748,6 +773,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -781,6 +807,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -804,6 +831,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -835,6 +863,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -864,6 +893,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -893,6 +923,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -916,6 +947,7 @@
"metrics": {
"api_requests": 3,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 3,
"completion_tokens": 6,
@ -940,6 +972,7 @@
"metrics": {
"api_requests": 0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"completion_tokens": 0,
@ -971,6 +1004,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -994,6 +1028,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -1027,6 +1062,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1056,6 +1092,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1085,6 +1122,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1114,6 +1152,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1143,6 +1182,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -1166,6 +1206,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -1190,6 +1231,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,

View file

@ -39,6 +39,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -68,6 +69,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -97,6 +99,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -126,6 +129,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -155,6 +159,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -188,6 +193,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -217,6 +223,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -246,6 +253,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -275,6 +283,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -304,6 +313,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -327,6 +337,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -360,6 +371,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -389,6 +401,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -418,6 +431,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -447,6 +461,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -476,6 +491,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -502,6 +518,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -536,6 +553,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -559,6 +577,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -590,6 +609,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -619,6 +639,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -648,6 +669,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -671,6 +693,7 @@
"metrics": {
"api_requests": 3,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 3,
"completion_tokens": 6,
@ -695,6 +718,7 @@
"metrics": {
"api_requests": 0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"completion_tokens": 0,
@ -726,6 +750,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -749,6 +774,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -782,6 +808,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -805,6 +832,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -836,6 +864,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -865,6 +894,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -894,6 +924,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -917,6 +948,7 @@
"metrics": {
"api_requests": 3,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 3,
"completion_tokens": 6,
@ -941,6 +973,7 @@
"metrics": {
"api_requests": 0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"completion_tokens": 0,
@ -972,6 +1005,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -995,6 +1029,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -1028,6 +1063,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1057,6 +1093,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1086,6 +1123,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1115,6 +1153,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1144,6 +1183,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -1167,6 +1207,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -1191,6 +1232,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,

View file

@ -39,6 +39,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -68,6 +69,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -97,6 +99,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -126,6 +129,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -155,6 +159,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -188,6 +193,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -217,6 +223,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -246,6 +253,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -275,6 +283,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -304,6 +313,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -327,6 +337,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -360,6 +371,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -389,6 +401,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -418,6 +431,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -447,6 +461,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -476,6 +491,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -502,6 +518,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -536,6 +553,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -559,6 +577,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -590,6 +609,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -619,6 +639,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -648,6 +669,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -671,6 +693,7 @@
"metrics": {
"api_requests": 3,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 3,
"completion_tokens": 6,
@ -695,6 +718,7 @@
"metrics": {
"api_requests": 0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"completion_tokens": 0,
@ -726,6 +750,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -749,6 +774,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -782,6 +808,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -805,6 +832,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -836,6 +864,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -865,6 +894,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -894,6 +924,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -917,6 +948,7 @@
"metrics": {
"api_requests": 3,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 3,
"completion_tokens": 6,
@ -941,6 +973,7 @@
"metrics": {
"api_requests": 0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"completion_tokens": 0,
@ -972,6 +1005,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -995,6 +1029,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -1028,6 +1063,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1057,6 +1093,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1086,6 +1123,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1115,6 +1153,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1,
"completion_tokens": 2,
@ -1144,6 +1183,7 @@
"metrics": {
"api_requests": 1,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 2,
"completion_tokens": 2,
@ -1167,6 +1207,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,
@ -1191,6 +1232,7 @@
"metrics": {
"api_requests": 5,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6,
"completion_tokens": 10,

View file

@ -402,6 +402,7 @@ def _daily_tag_metrics(
"prompt_caching_savings_spend": 0.0,
"gateway_injected_caching_savings_spend": 0.0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"total_tokens": prompt_tokens + completion_tokens,
"successful_requests": api_requests,
"failed_requests": 0,

View file

@ -152,6 +152,7 @@ def test_failed_daily_user_rollup_commit_is_retried_so_spend_report_and_daily_ac
"prompt_caching_savings_spend": pytest.approx(0.0),
"gateway_injected_caching_savings_spend": pytest.approx(0.0),
"autorouter_savings_spend": pytest.approx(0.0),
"guardrail_spend": pytest.approx(0.0),
"total_tokens": 40,
"successful_requests": 1,
"failed_requests": 0,

View file

@ -23,8 +23,23 @@ _FIELDS: Final = TypeAdapter(dict[str, object])
@pytest.mark.parametrize("status", ["success", "failure"])
@pytest.mark.parametrize("origin", [None, "auto_router", "billed_retry"])
@pytest.mark.parametrize("spend", [0.0, 0.25])
@pytest.mark.parametrize(
("guardrail_information", "billed_guardrail_cost"),
[
(None, 0.0),
([{"guardrail_name": "guard", "guardrail_cost": 0.1}], 0.1),
([{"guardrail_name": "guard", "guardrail_cost": 0.5}], 0.5),
([{"guardrail_name": "guard", "guardrail_cost": 0.1, "guardrail_cost_in_spend": False}], 0.0),
],
)
@pytest.mark.asyncio
async def test_daily_projection_matches_legacy_writer(status: str, origin: str | None, spend: float) -> None:
async def test_daily_projection_matches_legacy_writer(
status: str,
origin: str | None,
spend: float,
guardrail_information: list[dict[str, object]] | None,
billed_guardrail_cost: float,
) -> None:
payload: Final = get_logging_payload(
kwargs={
"model": "projection-test",
@ -43,6 +58,7 @@ async def test_daily_projection_matches_legacy_writer(status: str, origin: str |
**_FIELDS.validate_json(payload["metadata"]),
**({BILLED_RETRY_ATTEMPT_METADATA_KEY: True} if origin == "billed_retry" else {}),
**({INTERNAL_CALL_ORIGIN_METADATA_KEY: origin} if origin not in (None, "billed_retry") else {}),
**({"guardrail_information": guardrail_information} if guardrail_information is not None else {}),
}
row: Final = {**payload, "spend": spend, "metadata": json.dumps(metadata), "request_duration_ms": 25}
legacy: Final = await DBSpendUpdateWriter()._common_add_spend_log_transaction_to_daily_transaction(
@ -60,6 +76,7 @@ async def test_daily_projection_matches_legacy_writer(status: str, origin: str |
restored: Final = _FIELDS.validate_json(json.dumps(accounting, default=str))
stored_daily: Final = next(item for item in project(restored) if item.table == "LiteLLM_DailyUserSpend")
assert dict(stored_daily.increments) == {name: legacy[name] for name, _ in stored_daily.increments}
assert dict(stored_daily.increments)["guardrail_spend"] == min(billed_guardrail_cost, spend)
assert dict(stored_daily.key)["date"] == legacy["date"]
assert dict(stored_daily.key)["api_key"] == legacy["api_key"]
assert not {"messages", "response", "proxy_server_request", "requester_ip_address"}.intersection(accounting)

View file

@ -13,6 +13,7 @@ from litellm.litellm_core_utils.llm_cost_calc.guardrail_cost import (
cost_breakdown_with_guardrail,
guardrail_cost_total,
guardrail_information_cost,
guardrail_share_of_spend,
)
@ -349,3 +350,15 @@ def test_native_trace_entry_redacts_the_pii_matches_before_they_reach_spend_logs
assert entry["guardrail_status"] == "guardrail_intervened"
assert "123-45-6789" not in json.dumps(entry["guardrail_response"])
assert entry["guardrail_usage"]["sensitiveInformationPolicyUnits"] == 2
@pytest.mark.parametrize(("spend", "expected"), [(1.0, 0.3), (0.2, 0.2), (-1.0, 0.0)])
def test_guardrail_share_of_spend_is_the_billed_guardrail_cost_capped_at_the_row_spend(
spend: float, expected: float
) -> None:
information: Final = [
{"guardrail_name": "billed", "guardrail_cost": 0.3},
{"guardrail_name": "report-only", "guardrail_cost": 0.4, "guardrail_cost_in_spend": False},
]
assert guardrail_share_of_spend({"guardrail_information": information}, spend) == pytest.approx(expected)

View file

@ -209,6 +209,7 @@ async def test_get_aggregated_daily_spend_update_transactions_same_key():
"prompt_caching_savings_spend": 0,
"gateway_injected_caching_savings_spend": 0,
"autorouter_savings_spend": 0,
"guardrail_spend": 0,
"total_response_time_ms": 0,
"timed_requests": 0,
}
@ -263,6 +264,7 @@ async def test_flush_and_get_aggregated_daily_spend_update_transactions(
"prompt_caching_savings_spend": 0,
"gateway_injected_caching_savings_spend": 0,
"autorouter_savings_spend": 0,
"guardrail_spend": 0,
"total_response_time_ms": 0,
"timed_requests": 0,
}

View file

@ -88,10 +88,10 @@ def test_one_statement_carries_every_row_in_the_batch():
assert sql.count("INSERT INTO") == 1
assert len(re.findall(r"ON CONFLICT", sql)) == 1
# 25 bound columns per row plus the inlined updated_at, so the row count is what
# 26 bound columns per row plus the inlined updated_at, so the row count is what
# separates one multi-row statement from a hundred single-row ones.
assert len(params) == 100 * 25
assert "$2500::text" in sql
assert len(params) == 100 * 26
assert "$2600::text" in sql
assert sql.count("(NOW() AT TIME ZONE 'UTC')") == 100 + 1
@ -111,6 +111,7 @@ def test_conflict_target_is_the_full_unique_constraint():
"prompt_tokens",
"completion_tokens",
"spend",
"guardrail_spend",
"api_requests",
"successful_requests",
"failed_requests",

View file

@ -710,6 +710,7 @@ async def test_get_daily_activity_aggregated_with_endpoint_breakdown():
"prompt_caching_savings_spend": 0.0,
"gateway_injected_caching_savings_spend": 0.0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"total_response_time_ms": 0,
"timed_requests": 0,
"failed_requests": 0,
@ -1294,6 +1295,7 @@ def test_update_breakdown_metrics_includes_user_email():
prompt_caching_savings_spend=0,
gateway_injected_caching_savings_spend=0,
autorouter_savings_spend=0,
guardrail_spend=0,
total_response_time_ms=0,
timed_requests=0,
total_tokens=2,
@ -1375,6 +1377,7 @@ async def test_tag_daily_activity_metadata_totals_not_zero():
mock_record_1.prompt_caching_savings_spend = 0.0
mock_record_1.gateway_injected_caching_savings_spend = 0.0
mock_record_1.autorouter_savings_spend = 0.0
mock_record_1.guardrail_spend = 0.0
mock_record_1.total_response_time_ms = 18_000
mock_record_1.timed_requests = 9
mock_record_1.api_requests = 10
@ -1401,6 +1404,7 @@ async def test_tag_daily_activity_metadata_totals_not_zero():
mock_record_2.prompt_caching_savings_spend = 0.0
mock_record_2.gateway_injected_caching_savings_spend = 0.0
mock_record_2.autorouter_savings_spend = 0.0
mock_record_2.guardrail_spend = 0.0
mock_record_2.total_response_time_ms = 2_500
mock_record_2.timed_requests = 5
mock_record_2.api_requests = 5
@ -1473,6 +1477,7 @@ async def test_aggregated_activity_preserves_metadata_for_deleted_keys():
"prompt_caching_savings_spend": 0.0,
"gateway_injected_caching_savings_spend": 0.0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"total_response_time_ms": 0,
"timed_requests": 0,
"failed_requests": 0,
@ -1569,6 +1574,7 @@ async def test_aggregated_activity_flags_only_keys_that_key_info_can_still_resol
"prompt_caching_savings_spend": 0.0,
"gateway_injected_caching_savings_spend": 0.0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"total_response_time_ms": 0,
"timed_requests": 0,
"api_requests": 1,
@ -1618,7 +1624,7 @@ async def test_aggregated_activity_flags_only_keys_that_key_info_can_still_resol
assert key_breakdown["deleted-key"].metadata.key_alias == "deleted"
def _daily_user_spend_record(*, user_id, api_key, spend, model="gpt-4", model_group="gpt-4"):
def _daily_user_spend_record(*, user_id, api_key, spend, model="gpt-4", model_group="gpt-4", guardrail_spend=0.0):
"""A LiteLLM_DailyUserSpend row as the per-user breakdown reads it."""
return SimpleNamespace(
date="2024-01-01",
@ -1639,6 +1645,7 @@ def _daily_user_spend_record(*, user_id, api_key, spend, model="gpt-4", model_gr
prompt_caching_savings_spend=0.0,
gateway_injected_caching_savings_spend=0.0,
autorouter_savings_spend=0.0,
guardrail_spend=guardrail_spend,
total_response_time_ms=0,
timed_requests=0,
api_requests=1,
@ -1647,6 +1654,38 @@ def _daily_user_spend_record(*, user_id, api_key, spend, model="gpt-4", model_gr
)
@pytest.mark.asyncio
async def test_paginated_activity_separates_guardrail_spend_per_model_and_key():
records = [
_daily_user_spend_record(user_id="u", api_key="key", spend=0.0076916, model="guarded", guardrail_spend=0.00015),
_daily_user_spend_record(user_id="u", api_key="key", spend=0.002, model="unguarded"),
]
mock_prisma = MagicMock()
mock_prisma.db.litellm_dailyuserspend.count = AsyncMock(return_value=len(records))
mock_prisma.db.litellm_dailyuserspend.find_many = AsyncMock(return_value=records)
mock_prisma.db.litellm_verificationtoken.find_many = AsyncMock(return_value=[])
result = await get_daily_activity(
prisma_client=mock_prisma,
table_name="litellm_dailyuserspend",
entity_id_field="user_id",
entity_id=None,
entity_metadata_field=None,
start_date="2024-01-01",
end_date="2024-01-01",
model=None,
api_key=None,
page=1,
page_size=1000,
)
models = result.results[0].breakdown.models
guarded = models["guarded"].api_key_breakdown["key"].metrics
assert guarded.spend - guarded.guardrail_spend == pytest.approx(0.0075416)
assert models["unguarded"].api_key_breakdown["key"].metrics.guardrail_spend == 0.0
assert result.results[0].metrics.guardrail_spend == pytest.approx(0.00015)
@pytest.mark.asyncio
async def test_get_daily_activity_applies_resolve_entity_metadata_to_breakdown():
"""Regression for LIT-3889: the Spend Per User chart showed raw UUIDs.
@ -1788,6 +1827,7 @@ async def test_get_daily_activity_aggregated_empty_result_set():
"prompt_caching_savings_spend": None,
"gateway_injected_caching_savings_spend": None,
"autorouter_savings_spend": None,
"guardrail_spend": None,
"total_response_time_ms": None,
"timed_requests": None,
"api_requests": None,
@ -1836,6 +1876,7 @@ def _no_spend_record():
prompt_caching_savings_spend=None,
gateway_injected_caching_savings_spend=None,
autorouter_savings_spend=None,
guardrail_spend=None,
total_response_time_ms=None,
timed_requests=None,
api_requests=None,
@ -1975,6 +2016,7 @@ def _spend_record(api_key, *, model="gpt-4o-mini-ptu", spend=0.0, ptu_flat_cost=
prompt_caching_savings_spend=0,
gateway_injected_caching_savings_spend=0,
autorouter_savings_spend=0,
guardrail_spend=0,
total_response_time_ms=0,
timed_requests=0,
total_tokens=0,
@ -2021,6 +2063,7 @@ def _grouping_row(
endpoint=None,
spend=0.0,
ptu_flat_cost=0.0,
guardrail_spend=None,
):
return GroupingSetsRow(
date="2024-01-01",
@ -2043,6 +2086,7 @@ def _grouping_row(
prompt_caching_savings_spend=0.0,
gateway_injected_caching_savings_spend=0.0,
autorouter_savings_spend=0.0,
guardrail_spend=guardrail_spend,
total_response_time_ms=0,
timed_requests=0,
api_requests=0,
@ -2088,6 +2132,38 @@ def test_grouping_sets_dispatcher_excludes_ptu_sentinel_from_key_breakdowns(ptu_
assert "real-key" in model_bucket.api_key_breakdown
def test_model_key_breakdown_separates_guardrail_spend_from_model_spend():
from litellm.proxy.management_endpoints.common_daily_activity import (
_GROUP_DATE_API_KEY,
_GROUP_DATE_MODEL,
_GROUP_DATE_MODEL_API_KEY,
_GROUP_GRAND_TOTAL,
_aggregate_grouping_sets_records_sync,
)
records = [
_grouping_row(_GROUP_DATE_MODEL, model="guarded", spend=0.0076916, guardrail_spend=0.00015),
_grouping_row(_GROUP_DATE_MODEL, model="unguarded", spend=0.002),
_grouping_row(
_GROUP_DATE_MODEL_API_KEY, model="guarded", api_key="key", spend=0.0076916, guardrail_spend=0.00015
),
_grouping_row(_GROUP_DATE_MODEL_API_KEY, model="unguarded", api_key="key", spend=0.002),
_grouping_row(_GROUP_DATE_API_KEY, api_key="key", spend=0.0096916, guardrail_spend=0.00015),
_grouping_row(_GROUP_GRAND_TOTAL, spend=0.0096916, guardrail_spend=0.00015),
]
aggregated = _aggregate_grouping_sets_records_sync(records=records, api_key_metadata={})
day = aggregated["results"][0]
guarded = day.breakdown.models["guarded"].api_key_breakdown["key"].metrics
unguarded = day.breakdown.models["unguarded"].api_key_breakdown["key"].metrics
assert guarded.spend - guarded.guardrail_spend == pytest.approx(0.0075416)
assert unguarded.guardrail_spend == 0.0
assert day.breakdown.models["guarded"].metrics.guardrail_spend == pytest.approx(0.00015)
assert day.breakdown.api_keys["key"].metrics.guardrail_spend == pytest.approx(0.00015)
assert aggregated["totals"].guardrail_spend == pytest.approx(0.00015)
def test_grouping_sets_dispatcher_populates_every_breakdown_level(ptu_cost_attribution_enabled):
"""Every GROUPING SETS level lands in its bucket, and the flat-cost sentinel
is kept out of the model_group and provider api_key sub-breakdowns too."""
@ -2205,6 +2281,7 @@ def test_update_breakdown_metrics_covers_mcp_endpoint_and_entity(ptu_cost_attrib
prompt_caching_savings_spend=0,
gateway_injected_caching_savings_spend=0,
autorouter_savings_spend=0,
guardrail_spend=0,
total_response_time_ms=0,
timed_requests=0,
total_tokens=0,
@ -2560,6 +2637,7 @@ async def test_get_daily_activity_aggregated_with_entity_breakdown():
"prompt_caching_savings_spend": 0.0,
"gateway_injected_caching_savings_spend": 0.0,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"total_response_time_ms": 0,
"timed_requests": 0,
"failed_requests": 0,

View file

@ -67,6 +67,7 @@ class _Activity:
prompt_caching_savings_spend: float
gateway_injected_caching_savings_spend: float
autorouter_savings_spend: float
guardrail_spend: float
api_requests: int
successful_requests: int
failed_requests: int
@ -109,6 +110,7 @@ def _activity_for_entity(
prompt_caching_savings_spend=0.2,
gateway_injected_caching_savings_spend=0.3,
autorouter_savings_spend=0.4,
guardrail_spend=0.5,
api_requests=1,
successful_requests=1,
failed_requests=0,
@ -165,6 +167,7 @@ def _seeded_activity() -> tuple[_Activity, ...]:
prompt_caching_savings_spend=0.0,
gateway_injected_caching_savings_spend=0.0,
autorouter_savings_spend=0.0,
guardrail_spend=0.0,
api_requests=1,
successful_requests=1,
failed_requests=0,
@ -188,6 +191,7 @@ def _metrics(rows: Sequence[_Activity]) -> Mapping[str, int | float]:
"prompt_caching_savings_spend": sum(row.prompt_caching_savings_spend for row in rows),
"gateway_injected_caching_savings_spend": sum(row.gateway_injected_caching_savings_spend for row in rows),
"autorouter_savings_spend": sum(row.autorouter_savings_spend for row in rows),
"guardrail_spend": sum(row.guardrail_spend for row in rows),
"api_requests": sum(row.api_requests for row in rows),
"successful_requests": sum(row.successful_requests for row in rows),
"failed_requests": sum(row.failed_requests for row in rows),
@ -791,6 +795,7 @@ def test_search_folds_each_entity_key_across_days(
"prompt_caching_savings_spend": 0.4,
"gateway_injected_caching_savings_spend": 0.6,
"autorouter_savings_spend": 0.8,
"guardrail_spend": 1.0,
"total_tokens": 30,
"successful_requests": 2,
"failed_requests": 0,

View file

@ -19308,6 +19308,7 @@ def _db_with_one_ptu_hour_of_team_spend(team_id: str) -> MagicMock:
prompt_caching_savings_spend=0.0,
gateway_injected_caching_savings_spend=0.0,
autorouter_savings_spend=0.0,
guardrail_spend=0.0,
total_response_time_ms=0,
timed_requests=0,
api_requests=3,

View file

@ -675,6 +675,7 @@ async def test_customer_team_page_returns_loaded_metrics_and_retains_count_on_em
"prompt_caching_savings_spend": 0.25,
"gateway_injected_caching_savings_spend": 0.125,
"autorouter_savings_spend": 0.0,
"guardrail_spend": 0.0,
"total_response_time_ms": 10,
"timed_requests": 1,
"created_at": "2026-01-01T00:00:00Z",

View file

@ -284,6 +284,7 @@ def test_aggregated_query_uses_the_caller_date_bounds(offset_minutes: int | None
def test_aggregate_query_sums_all_savings_drivers_and_response_time() -> None:
query = build_aggregated_sql(_scope(), api_key_limit=constants.USAGE_TOP_API_KEYS_DEFAULT)
fields: Final = tuple(field for field in SpendMetrics.model_fields if field.endswith("_savings_spend")) + (
"guardrail_spend",
"total_response_time_ms",
"timed_requests",
)

View file

@ -36,6 +36,7 @@ const baseMetrics = (overrides: Partial<SpendMetrics>): components["schemas"]["S
prompt_caching_savings_spend: 0,
gateway_injected_caching_savings_spend: 0,
autorouter_savings_spend: 0,
guardrail_spend: 0,
total_response_time_ms: 0,
timed_requests: 0,
...overrides,

View file

@ -88,6 +88,26 @@ describe("client usage from request logs", () => {
expect(day?.date).toBe(localDay);
});
it("should report guardrail spend inside model spend, capped at what each request was charged", () => {
const guarded = (request_id: string, spend: number, guardrail_information: unknown) =>
log({ request_id, spend, metadata: { user_agent: desktopAgent, guardrail_information } });
const result = clientUsageFromLogs(
page([
guarded("billed", 0.0076916, [
{ guardrail_cost: 0.00015 },
{ guardrail_cost: 0.0004, guardrail_cost_in_spend: false },
]),
guarded("capped", 0.0001, { guardrail_cost: 0.0003 }),
guarded("malformed", 0.002, [{ guardrail_cost: "free" }]),
log(),
]),
scope,
);
expect(result.error).toBeNull();
const metrics = result.data.results?.[0]?.breakdown?.models?.["provider-model"].metrics;
expect(metrics?.guardrail_spend).toBeCloseTo(0.00025, 10);
});
it("should deduplicate request ids without double counting bare and full user-agent tags", () => {
const result = clientUsageFromLogs(page([log(), log({ spend: 0.02 })]), scope);
expect(result.data.metadata?.total_api_requests).toBe(1);

View file

@ -31,11 +31,19 @@ const usageFields = {
input_tokens_details: cacheDetailsSchema.nullish(),
};
const usageSchema = z.object(usageFields);
const guardrailEntrySchema = z.object({
guardrail_cost: z.number().finite().nullish().catch(null),
guardrail_cost_in_spend: z.boolean().nullish().catch(null),
});
const metadataFields = {
user_agent: z.string().nullish(),
status: z.string().nullish(),
additional_usage_values: usageSchema.nullish(),
usage_object: usageSchema.nullish(),
guardrail_information: z
.union([z.array(guardrailEntrySchema), guardrailEntrySchema])
.nullish()
.catch(null),
};
const logFields = {
request_id: z.string().min(1),
@ -127,6 +135,19 @@ const cacheTokens = (row: ClientLog) => {
cacheCreationTokens: additional.cacheCreationTokens ?? usage.cacheCreationTokens,
};
};
const billedGuardrailCost = (entry: z.infer<typeof guardrailEntrySchema>) =>
entry.guardrail_cost_in_spend === false ? 0 : Math.max(entry.guardrail_cost ?? 0, 0);
type GuardrailInformation = NonNullable<NonNullable<ClientLog["metadata"]>["guardrail_information"]>;
const guardrailEntries = (information: GuardrailInformation) =>
Array.isArray(information) ? information : [information];
const guardrailSpend = (row: ClientLog) => {
const information = row.metadata?.guardrail_information;
const entries = information == null ? [] : guardrailEntries(information);
return Math.min(
entries.reduce((total, entry) => total + billedGuardrailCost(entry), 0),
Math.max(row.spend, 0),
);
};
const sum = (rows: readonly ClientLog[], value: (row: ClientLog) => number) =>
rows.reduce((total, row) => total + value(row), 0);
const metricsFor = (rows: readonly ClientLog[]): SpendMetrics => ({
@ -137,6 +158,7 @@ const metricsFor = (rows: readonly ClientLog[]): SpendMetrics => ({
prompt_caching_savings_spend: 0,
gateway_injected_caching_savings_spend: 0,
autorouter_savings_spend: 0,
guardrail_spend: sum(rows, guardrailSpend),
spend: sum(rows, (row) => row.spend),
prompt_tokens: sum(rows, (row) => row.prompt_tokens),
completion_tokens: sum(rows, (row) => row.completion_tokens),

View file

@ -47697,6 +47697,12 @@ export interface components {
* @default 0
*/
gateway_injected_caching_savings_spend: number;
/**
* Guardrail Spend
* @description Guardrail spend in USD, already included in spend, so model spend is spend - guardrail_spend. Rows recorded before this field existed report 0 even when their spend includes guardrail cost
* @default 0
*/
guardrail_spend: number;
/**
* Prompt Caching Savings Spend
* @default 0