Error Handling
Solwyn SDK error categories — pre-flight, provider, and silent errors
import os
from openai import OpenAI
from solwyn import BudgetExceededError, ConfigurationError, ProviderUnavailableError, Solwyn
try:
client = Solwyn(
OpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
budget_mode="hard_deny",
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
except ConfigurationError as e:
print(f"Bad config ({e.field}): {e.message}")
except BudgetExceededError as e:
print(f"Budget blocked: ${e.current_usage:.2f} / ${e.budget_limit:.2f}")
except ProviderUnavailableError as e:
print(f"Provider down: {e.provider} ({e.circuit_state})")
finally:
client.close()Solwyn SDK errors fall into three categories: pre-flight errors that prevent construction, provider health errors during calls, and silent errors that are logged but never raised.
Error categories
1. Pre-flight errors (construction time)
These are raised when Solwyn() or AsyncSolwyn() is constructed with invalid configuration:
| Exception | When raised | Example |
|---|---|---|
ConfigurationError | Invalid or missing configuration | Bad API key format, malformed fallback spec, unknown or dialect-mismatched provider= override |
from openai import OpenAI
from solwyn import ConfigurationError, Solwyn
try:
client = Solwyn(
OpenAI(),
api_key="not_a_valid_key",
)
except ConfigurationError as e:
print(f"Field: {e.field}") # "api_key"
print(f"Error: {e.message}") # format validation messageConfigurationError is raised immediately -- no LLM call is attempted.
2. Provider errors (call time)
These are raised during LLM calls when the SDK cannot proceed:
| Exception | When raised | Requires |
|---|---|---|
BudgetExceededError | Budget exhausted before a call | budget_mode="hard_deny" |
ProviderUnavailableError | Every provider in the chain is unavailable | Circuit breaker triggered / chain exhausted |
UntranslatableRequestError | A cross-provider failover hop cannot translate the request shape | fallback= chain with mixed providers |
UntranslatableModelError | A fallback entry has no model configured for its provider | fallback= chain with mixed providers |
The two translation errors are raised before any network call and abort the whole failover chain. They carry structural labels only (source, target, feature / model, provider) — never prompt content. See Provider Failover.
One ConfigurationError is raised at call time rather than construction: calling invoke_model or invoke_model_with_response_stream on a Bedrock-wrapped client raises it immediately (field="invoke_model") instead of letting the call bypass budget tracking. Use converse() / converse_stream(), or the unwrapped boto3 client for deliberately untracked calls. See Amazon Bedrock.
Provider errors from the underlying SDK (e.g. openai.APIError, anthropic.APIError, botocore.exceptions.ClientError) are not caught by Solwyn. When the failover chain cannot recover, they propagate directly to your code, just as they would without the wrapper.
3. Silent errors (logged only)
These are internal errors that the SDK handles gracefully without raising:
| Situation | Behavior |
|---|---|
| Solwyn Cloud API unreachable (budget check) | fail_open=True: proceed with local tracking. fail_open=False: enforce locally. |
| Solwyn Cloud API unreachable (reporting) | Best-effort: events wait in a bounded in-process queue between flushes, but a batch whose delivery fails is logged (Failed to send metadata batch) and dropped, never retried. Reporting never blocks LLM calls. |
| Per-event ingest rejection (202 with rejection dispositions, SDK v0.1.7+) | Accepted events in the batch are durable; rejected events (e.g. unknown_model) are terminal — logged and dropped, never re-queued. One WARNING per distinct (code, model) per batch: reporter.ingest_events_rejected. See Logging. |
| Budget confirmation failure | Logged as warning, does not affect the LLM call. |
Silent errors are logged via the standard Python logging module at the WARNING level. Enable logging to see them:
import logging
logging.basicConfig(level=logging.WARNING)Exception hierarchy
All Solwyn exceptions inherit from SolwynError:
SolwynError (base -- catch this to handle any SDK error)
+-- BudgetExceededError
+-- ProviderUnavailableError
+-- ConfigurationError
+-- UntranslatableRequestError
+-- UntranslatableModelErrorSolwynError is exported from the top-level package alongside its subclasses. Use it when you want a single except clause for any Solwyn error:
from solwyn import SolwynError
try:
response = client.chat.completions.create(...)
except SolwynError:
# catches all Solwyn-raised errors
...fail_open behavior
The fail_open setting controls what happens when Solwyn Cloud is unreachable:
fail_open | Cloud unreachable behavior |
|---|---|
True (default) | LLM calls proceed. Usage tracked locally. Warning logged. |
False | Budget enforced locally using last known limit. If no limit is known, calls are denied. |
import os
from openai import OpenAI
from solwyn import Solwyn
# Default: fail_open=True -- calls always proceed
client = Solwyn(
OpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
)
# Strict: fail_open=False -- enforce locally when cloud is down
client = Solwyn(
OpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
fail_open=False,
budget_mode="hard_deny",
)Exception attribute reference
Every exception's attributes — budget_limit, current_usage, circuit_state, attempted, field, the translation-error source/target/feature/model/provider labels, and the rest — live in one place: Exceptions. That reference is the single source for what each exception carries.