Budget Enforcement
Set spending caps on your AI agents — alert-only mode vs hard-deny mode
import os
from openai import OpenAI
from solwyn import BudgetExceededError, Solwyn
client = Solwyn(
OpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
budget_mode="hard_deny",
)
try:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
except BudgetExceededError as e:
print(f"Blocked: ${e.current_usage:.4f} of ${e.budget_limit:.2f} used")
client.close()Budget limits are configured in the Solwyn dashboard per project. The SDK enforces those limits before every LLM call.
Budget modes
The budget_mode setting controls what happens when a call would exceed the budget:
| Mode | Value | Behavior |
|---|---|---|
| Alert only | "alert_only" (default) | Logs a warning but allows the call to proceed. |
| Hard deny | "hard_deny" | Raises BudgetExceededError and blocks the call. |
Alert-only mode (default)
In the default mode, the SDK logs a warning when budget is exhausted but never blocks calls:
import os
from openai import OpenAI
from solwyn import Solwyn
client = Solwyn(
OpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
budget_mode="alert_only", # this is the default
)
# This call will succeed even if budget is exhausted.
# A warning is logged: "Budget limit reached (alert_only mode)"
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
client.close()Hard-deny mode
In hard-deny mode, the SDK raises BudgetExceededError before the LLM call is made:
import os
from openai import OpenAI
from solwyn import BudgetExceededError, Solwyn
client = Solwyn(
OpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
budget_mode="hard_deny",
)
try:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
except BudgetExceededError as e:
print(f"Budget exhausted: ${e.current_usage:.4f} used of ${e.budget_limit:.2f} cap")
print(f"Blocked request estimated cost: ${e.estimated_cost:.4f}")
print(f"Period: {e.budget_period}")
print(f"Mode: {e.mode}")
client.close()Pre-call flow
Before every LLM call, the SDK runs this sequence:
1. Estimate the call's billable basis:
- chat and embeddings: input tokens, from local message-text length
- media surfaces: request-derived quantities — image count x size/quality,
requested video duration and resolution, TTS input character count
2. Send budget check to Solwyn Cloud API
3. If cloud says "allowed" --> proceed with the LLM call
4. If cloud says "denied":
- alert_only mode --> log warning, proceed anyway
- hard_deny mode --> raise BudgetExceededError, block the call
5. After successful LLM call, confirm actual usage with cloudThe media basis travels as estimated_media — quantities and selectors only, never content; see Privacy. Video's pre-flight is exact by construction (the requested duration and resolution are in the request), so an over-budget generation is denied before the provider is called even though its settlement is an estimated, settles-at-initiation over-count.
# Media calls pre-flight identically — deny happens before the provider is reached.
try:
client.images.generate(model="gpt-image-1", prompt="...", n=4, size="1024x1024")
except BudgetExceededError as e:
print(f"Over budget: ${e.current_usage:.2f} / ${e.budget_limit:.2f}")Budget checks are cached locally for a configurable TTL (default: 5 seconds) to reduce API round-trips. Only "allowed" responses are cached -- "denied" responses are never cached to ensure prompt enforcement.
Checks inside an agent run
New in 0.3.0. A check made inside a solwyn.run(...) scope carries the run's agent_run_id, so the API can deny it against a per-run cap in addition to the project budget. Both denials arrive through the same check and raise the same BudgetExceededError in hard_deny mode -- nothing in your except block changes. Caps are configured in Cloud; see Per-run budget caps.
Run-scoped calls differ from ordinary ones in two ways:
- They bypass the allow cache entirely, on both read and write -- every call inside a scope performs a real check rather than riding a cached "allowed".
- A hard deny for a run is sticky: it is remembered for that run id (an LRU over the 128 most recent runs), and if Cloud then goes unreachable the denial is preserved instead of failing open. Project-period hard denies remain global as before. Each preserved deny logs a WARNING naming the usage and limit, on every call it applies to -- see Logging.
Absent an authoritative deny, the fail_open default below is unchanged.
BudgetExceededError attributes
| Attribute | Type | Description |
|---|---|---|
budget_limit | float | Configured spending cap in dollars. |
current_usage | float | Amount already consumed in the current period. |
estimated_cost | float | Estimated cost of the blocked request. |
budget_period | str | Budget window (e.g. "daily", "weekly", "monthly", or "unknown" if not provided by the API). |
mode | str | Active budget mode when the error was raised. |
Local fallback
When Solwyn Cloud is unreachable (the budget check cannot be completed), fail_open decides what happens:
fail_open=True(default): Calls proceed. Local tracking continues, and reports are sent when connectivity is restored.fail_open=False: Calls are denied until the budget service is reachable again.
import os
from openai import OpenAI
from solwyn import Solwyn
# Strict mode: enforce budgets even when cloud is unreachable
client = Solwyn(
OpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
budget_mode="hard_deny",
fail_open=False,
)Budget cache TTL
Budget check results are cached locally to reduce API calls. Adjust the TTL with budget_check_cache_ttl:
import os
from openai import OpenAI
from solwyn import Solwyn
client = Solwyn(
OpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
budget_check_cache_ttl=10, # cache for 10 seconds instead of default 5
)Lower values give more responsive enforcement at the cost of more API calls. Higher values reduce API traffic but may allow brief overspend before a limit takes effect.
Related
- SolwynConfig -- All budget-related config fields and env vars
- Troubleshooting -- Diagnose unexpected
BudgetExceededErrorraises - CLI: Scripting and CI -- Gate a deploy on budget utilization, and guard un-instrumented spend with
budget check/confirm
OpenAI-compatible providers
Use Solwyn with any OpenAI-compatible endpoint — xAI, DeepSeek, Mistral, Groq, Z.AI, Together, Azure OpenAI, OpenRouter, Ollama, vLLM, and more
Provider Failover
Cross-provider and same-provider failover — the fallback chain, selection policies, request translation, and tuning