symfonic.core.observability.budget¶
budget ¶
TokenBudgetTracker — per-tenant cost ceilings + usage aggregation.
Gate 2 of v5.5.0; Sprint B (v5.6.1) adds multi-replica hydration via an
optional :class:BudgetStore. In-memory-first on the hot path with an
asyncio.Lock around record() for correctness under
asyncio.gather. Persistence is optional; without a store the tracker
behaves exactly like pre-5.5 agents. Data types live in
:mod:.budget_models and hydration helpers in :mod:.budget_hydrate so
each file stays below the 300-LOC cap.
BudgetCheckResult
dataclass
¶
BudgetCheckResult(
allowed: bool,
reason: str | None = None,
daily_used_usd: float = 0.0,
daily_limit_usd: float | None = None,
monthly_used_usd: float = 0.0,
monthly_limit_usd: float | None = None,
)
Result of a pre-call budget check.
BudgetStore ¶
Bases: Protocol
Optional persistence layer for daily aggregates.
Implementations should be idempotent on conflict — the tracker calls
upsert_daily after every LLM call, so the underlying storage must
handle UPSERT semantics (ON CONFLICT ... DO UPDATE).
The optional read_today / read_month methods hydrate the
tracker's in-memory buckets on demand so that multiple app replicas
converge on the same view of a tenant's cumulative usage. A store
that only implements upsert_daily still works (writes only) —
multi-replica correctness requires all three.
read_month
async
¶
Return the aggregated month-to-date usage for tenant_id.
year_month is the YYYY-MM key produced by
:func:month_key. Optional — see :meth:read_today.
Source code in src/symfonic/core/observability/budget_models.py
read_today
async
¶
Return today's usage row for tenant_id (None if absent).
Optional — implementations without this method get write-through semantics only (single-process correctness).
Source code in src/symfonic/core/observability/budget_models.py
BudgetUsage
dataclass
¶
BudgetUsage(
tenant_id: str,
window: str,
window_key: str,
input_tokens: int = 0,
output_tokens: int = 0,
cached_tokens: int = 0,
cache_write_tokens: int = 0,
cost_usd: float = 0.0,
request_count: int = 0,
updated_at: datetime = (lambda: datetime.now(UTC))(),
)
Cumulative usage for a single (tenant, window) pair.
TokenBudgetTracker ¶
TokenBudgetTracker(
*,
default_daily_budget_usd: float | None = None,
default_monthly_budget_usd: float | None = None,
store: BudgetStore | None = None,
)
Tracks cumulative token usage + cost per tenant.
Two in-memory buckets per tenant (day, month) roll over
lazily on date/month boundaries. Limits configured globally via
default_*_budget_usd or per-tenant via meth:
set_tenant_budget;
None means unlimited. Admin callers bypass the breaker.
Source code in src/symfonic/core/observability/budget.py
check_budget
async
¶
Return whether tenant_id may incur another LLM call.
When a BudgetStore with read_today / read_month is
registered, the in-memory buckets are first hydrated from the
store so every replica shares one view of the running total.
Store errors are logged at WARNING and fall through to in-memory
state — the ledger is never allowed to break auth. Admin
callers bypass the breaker.
Source code in src/symfonic/core/observability/budget.py
clear_tenant_budget ¶
Remove per-tenant overrides so the defaults apply again.
get_tenant_limits ¶
Return (daily_limit, monthly_limit) applied to tenant_id.
Source code in src/symfonic/core/observability/budget.py
record
async
¶
Record token usage for a single LLM call; returns its cost_usd.
Source code in src/symfonic/core/observability/budget.py
reset
async
¶
Reset in-memory day + month counters for a tenant.
When a persistent store is wired, the shared Postgres row is NOT cleared — use the admin billing endpoint to zero the ledger.
Source code in src/symfonic/core/observability/budget.py
set_tenant_budget ¶
set_tenant_budget(
tenant_id: str,
*,
daily_usd: float | None = None,
monthly_usd: float | None = None,
) -> None
Override budget ceilings for a tenant; None = unlimited.
Source code in src/symfonic/core/observability/budget.py
get_budget_tracker ¶
set_budget_tracker ¶
Register (or clear) the process-wide budget tracker.
Consulted by check_budget_before_llm and SymfonicAgent.run();
when unset both skip the check.