SOLWYN
Guides

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:

ModeValueBehavior
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 cloud

The 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

AttributeTypeDescription
budget_limitfloatConfigured spending cap in dollars.
current_usagefloatAmount already consumed in the current period.
estimated_costfloatEstimated cost of the blocked request.
budget_periodstrBudget window (e.g. "daily", "weekly", "monthly", or "unknown" if not provided by the API).
modestrActive 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.

  • SolwynConfig -- All budget-related config fields and env vars
  • Troubleshooting -- Diagnose unexpected BudgetExceededError raises
  • CLI: Scripting and CI -- Gate a deploy on budget utilization, and guard un-instrumented spend with budget check / confirm

On this page