Changelog
Solwyn Python SDK release history
All notable changes to the Solwyn Python SDK are documented here. This project follows Semantic Versioning.
0.3.0 — 2026-07-16
Attribution and enforcement get finer-grained: agent runs carry labels you author and can be capped individually, circuit-breaker health reaches the dashboard, and custom failover tuning becomes a plan entitlement — one new configuration field, no breaking changes.
Added
- Per-run budget caps — every pre-flight budget check made inside a
solwyn.run(...)scope now carries the run's stableagent_run_id, so a budget can cap an individual run rather than only the project. Caps are server-enforced and configured in Cloud — there is no cap argument onrun(); the SDK stamps the run id on the check and enforces the answer, raising the sameBudgetExceededErrorinhard_denymode. Run-scoped checks bypass the allow cache, so every call inside a scope performs a real check, and an authoritative hard deny is remembered per run id — preserved rather than failing open if Cloud then goes unreachable. A preserved deny is never silent: each call it applies to logs a WARNING on thesolwyn.budgetlogger naming the usage and limit (Cloud API unreachable; preserving prior hard deny: $99.50/$100.00 used), so a sustained outage under a hard deny stays visible for its whole duration rather than announcing itself once. The same holds for the older global project-period deny. See Agent Runs and Logging. - Customer tags on runs and calls — attach your own key/value labels for cost attribution and dashboard filtering, per scope via
solwyn.run(name, tags={...})or per call via the reservedsolwyn_tags={...}keyword, which the SDK strips before the request reaches the provider. The merge is shallow and per-call keys win; nested scopes replace rather than merge. Bounds are 10 keys across the merged set, keys 1–64 characters, values 0–256 — and violations are rejected, never truncated, raising before the budget check and before provider dispatch. Tags are the single deliberate exception to the zero-content guarantee: free-form text you author, transmitted as provided, never derived from your prompts — so never put prompt text, PII, or secrets in one. See Agent Runs and Privacy. - Circuit-breaker state reporting — the SDK periodically snapshots each provider's breaker and reports it — provider, state, failure and success counts, snapshot time, and a per-instance SDK id — giving the dashboard fleet-wide provider-health visibility. Advisory and one-way: Cloud snapshots never drive admission, state is never shared between instances, and every failover decision stays in-process. Reports ride the background reporter off the request path, never blocking a call, and begin only once a successful budget check has established the project. Disable with
breaker_reporting_enabled=FalseorSOLWYN_BREAKER_REPORTING_ENABLED=false— the only new configuration field in this release. See Provider Failover, Privacy, and SolwynConfig. - Plan-scoped failover tuning entitlement — each budget check opts into a versioned failover directive, and Solwyn Cloud may answer with one governing a closed set of seven fields: the four failover knobs and the three circuit-breaker parameters. An absent directive is a no-op that retains your tuning,
true(re)applies it, andfalseapplies SDK defaults in its place — reversible in both directions, never a one-way ratchet. Strictly advisory: provider order never changes, breakers keep their identity and accumulated health state, afailover_total_timeoutchange preserves the original call start rather than restarting the clock, and calls never fail for lack of a directive. Suppressed tuning logs one WARNING per client instance, and only when your tuning actually differs from the defaults. See Provider Failover. - OpenAI prompt-cache write extraction —
usage.prompt_tokens_details.cache_write_tokens(Chat Completions) andusage.input_tokens_details.cache_write_tokens(Responses API) are now extracted and reported ascache_creation_5m_tokens. OpenAI's cache writes carry a 30-minute default/minimum TTL, not 5 minutes — the field name is a wire-contract artifact mapping OpenAI's writes onto the existing bucket, not a claim about its TTL;cache_creation_1h_tokensstays 0 for OpenAI. Independent of cache reads, no opt-in or configuration, and unusable values degrade to 0 rather than raising. See OpenAI. - Read-only key diagnostics — when Solwyn Cloud refuses the SDK's writes because the configured key is read-only, the SDK now recognizes that structured response and logs one ERROR per process naming the cause, instead of a stream of generic Cloud-API warnings. Detection is exact — other 401/403 responses still surface as the ordinary warnings. Reporting is dropped, and budget checks fail open on the default
fail_open=True, so your provider calls keep working while enforcement and attribution do not. See Troubleshooting. UnsupportedSurfaceErrorin the package root —from solwyn import UnsupportedSurfaceErrornow works, and the class joins every other exception in the package's__all__. It shipped in 0.2.0 as a deep import only; the addition is additive, sofrom solwyn.exceptions import UnsupportedSurfaceErrorkeeps working. See Exceptions.
One new configuration option, breaker_reporting_enabled. No breaking changes: tags is omitted from the wire entirely when you set none, so payloads for untagged calls are unchanged.
0.2.0 — 2026-07-11
The SDK goes multimodal: embeddings, images, audio, and video are now intercepted, budget-checked, and priced with the same wrapper-not-a-proxy privacy boundary as chat — quantities and selectors leave your process, never content.
Added
- Embeddings interception —
embeddings.create(OpenAI dialect, native Together) andmodels.embed_content(Google) are budget-checked and recorded as cost events with modalityembedding, priced from input tokens; Google exposes no usage on embeddings, so its counts are length-estimated and flaggedis_estimated. See Surface coverage. - Image interception —
images.generateandimages.edit(OpenAI, compat, native Together) andmodels.generate_images(Google Imagen). Token-billed image models price from usage token buckets; per-image endpoints return no usage, so billing is request-derived (image count × per-image rate) with the count, resolution, and quality selectors measured locally. See OpenAI and Google Gemini. - Audio interception —
audio.transcriptions.createandaudio.speech.create(OpenAI, compat incl. Groq, native Together). Transcriptions price from usage token buckets or whole-second durations on a JSONresponse_format— a non-JSON format is recorded unpriced, never guessed; speech prices from the input character count measured inside the privacy firewall.audio.translationsand usage-less TTS models (gpt-4o-mini-tts) warn once and pass through. See Surface coverage. - Video interception —
videos.create(OpenAI Sora) andmodels.generate_videos(Google Veo), priced per second on resolution-gridded cards. Video jobs carry no usage, so billing settles at initiation as a flagged, conservative over-count — while the pre-flight check is exact, denying an over-budget generation before the provider is called. On providers with no video seam,videos.createfails loud instead of passing through untracked. MediaUsageon the wire — non-token billable quantities (image_count,generation_count,video_seconds,audio_seconds,input_characters) plus theresolution/qualityselectors, and amodalityfield on every cost event. Pre-flight budget checks carryestimated_media, the same quantities derived from the request. Unobservable quantities are omitted, routing the call to the unpriced lane — never a fabricated $0. See Privacy.UnsupportedSurfaceError— raised when a wrapped client's adapter serves no seam for a requested media surface, instead of letting a spend surface run untracked. A deep import (from solwyn.exceptions import UnsupportedSurfaceError) until 0.3.0, which also exported it from the package root. See Exceptions.
No new configuration options. Pre-media wire payloads are byte-identical; the new fields are omitted from the wire for text calls.
0.1.12 — 2026-07-10
Added
- First-class Together AI provider — wrap a native
TogetherorAsyncTogetherclient directly; detection is duck-typed by module and class, and Together-hosted OpenAI clients (base_url="https://api.together.xyz/v1") are still admitted via the compat profile. Billable surfaces Solwyn does not yet meter on the native client —completions,rerank,code_interpreter,evals— warn once and pass through. Thesolwyn[together]extra installstogether>=2.0as a convenience; the SDK never imports it. See Together AI.
0.1.11 — 2026-07-09
Added
- Z.AI compatibility profile — an OpenAI client pointed at
api.z.ai(or callingglm-*models) is detected and attributed asprovider="zai"for budgets, attribution, and failover. See OpenAI-compatible providers.
Fixed
- Z.AI streaming usage injection — Z.AI documents
stream_options.include_usage, so Solwyn now injects it on streamed calls and reads exact usage from the final chunk instead of falling back to estimation. - Type-checking — OpenAI usage values now narrow correctly under mypy.
0.1.10 — 2026-07-06
Added
CostPolicyno-op warning —CostPolicyis selectable but inert until the server sends relative price hints; when no candidate carries a hint it degrades to health-based ordering. That fallback now logs a one-time, per-process warning naming the condition — visible, and carrying no request content.
0.1.9 — 2026-07-06
Fixed
- Privacy: no tracebacks on the budget-denied report path — two
logger.warningcalls on the budget-denied report-failure path logged full tracebacks (exc_info=True), breaching the firewall convention that only exception class names are ever logged. Both now log the class name only, and a firewall gate asserts the pattern cannot regress. See Privacy.
0.1.8 — 2026-07-02
Fixed
- Hard deny survives Cloud outages — After an authoritative Cloud
hard_deny, if a subsequent live budget check fails because Solwyn Cloud is unreachable, the SDK keeps denying instead of falling through to its outage path — even whenfail_open=True. This is sticky enforcement state, not a cache: live Cloud checks still run on every call, and the first Cloud response that allows clears the sticky deny, so recovery is automatic with no client restart. Ordinary pre-deny outages continue to honorfail_open/ local enforcement as before. Applies to both sync and async budget enforcement, at the provider-call boundary — a blocked call raises before reaching the underlying LLM SDK. - Cloud response mode is authoritative for denials — Denial behavior now keys off the
modein the Cloud budget response rather than the localbudget_modeconfig, fixing local/Cloud mode drift (e.g. localalert_onlyvs. a Cloudhard_deny, or the reverse). The served decision now matches what Cloud actually decided.
No breaking changes, no wire-contract changes.
0.1.7 — 2026-06-12
Added
- Amazon Bedrock support (Converse API) — Wrap a boto3 or aioboto3
bedrock-runtimeclient:Solwyn(boto3.client("bedrock-runtime")). Interceptsconverse/converse_stream, which work uniformly across every chat model Bedrock hosts, with exact cache accounting (additive input formula, 5-minute/1-hour cache-write TTL split viausage.cacheDetails), service/latency tier capture, and per-region cost attribution. Bedrock participates in cross-provider failover in both directions. Zero new runtime dependencies — boto3 is never imported (detection is duck-typed); thesolwyn[bedrock]extra installsboto3>=1.34as a convenience.invoke_model/invoke_model_with_response_streamraiseConfigurationErrorinstead of silently bypassing budget tracking. See Amazon Bedrock. - OpenAI-compatible providers — Point an
openai.OpenAIclient at xAI, DeepSeek, Mistral, Qwen, Groq, Together, Fireworks, Perplexity, Azure OpenAI, OpenRouter, Ollama, vLLM, LM Studio, or any compatible endpoint viabase_url; Solwyn detects the real provider for attribution, budgets, and failover instead of mislabeling itopenai. Streaming usage (stream_options.include_usage) is requested only where documented-safe per provider. See OpenAI-compatible providers. provider=constructor argument — Explicit provider identity for the primary client when auto-detection cannot name the endpoint (e.g.provider="vllm"on a non-default port). Fallback specs accept a matching 4th element:(client, model, default_params, provider). An override relabels within the same API dialect only; unknown or dialect-mismatched values raiseConfigurationErrorat construction.- Flagged estimated usage — When a provider returns no usage data, the SDK reports length-based estimates explicitly marked
token_details.is_estimated=true(serialized only when true) and logs a one-time WARNING. Budgets still enforce; degraded accounting is loud, never silently zero. - Same-dialect failover passthrough — Hops between OpenAI-dialect providers (e.g. Groq → OpenRouter) are native passthrough: tools, JSON mode, and streaming survive. On cross-provider hops, endpoint-scoped
extra_headers/extra_query/extra_bodyare stripped andmax_completion_tokens⇄max_tokensis rewritten per target. See Provider Failover. - Per-event ingest rejection logging — Solwyn Cloud now accepts well-formed metadata batches with a 202 and reports per-event rejections in the response body instead of failing whole batches. The SDK surfaces these as one aggregated WARNING per distinct (code, model) per batch on the
solwyn.reporterlogger (reporter.ingest_events_rejected), with the server's guidance message logged verbatim. Rejected events are terminal (never re-queued); accepted events in the same batch remain recorded. Malformed response bodies fail open to the previous count-only acknowledgment, escalating to ERROR after 10 consecutive occurrences. See Logging. - New wire fields — Fifteen new
providervalues (bedrockplus fourteen OpenAI-compatible identifiers), optionalprovider_region(omitted when unset),service_tieron budget confirms,token_details.is_estimated, and model identifiers up to 2048 characters (Bedrock ARNs).
0.1.6 — 2026-06-06
Added
- True cross-provider failover — Configure a failover chain with
fallback=[(client, model), (client, model, default_params)]spanning OpenAI, Anthropic, and Google. When a provider fails or its circuit is open, Solwyn translates the request and retries it on the next provider in-process. See Provider Failover. - Selection policies —
selection_policy=acceptsHealthBasedPolicy(default) orLatencyPolicyto control the order in which healthy candidates are attempted. - Request translation — Cross-provider hops translate the request shape through a canonical subset (structural only — never content). Untranslatable shapes fail loud before any network call.
- New constructor arguments —
model=(primary entry model),fallback=,default_params=(global fill-absent params), andselection_policy=. - Failover tuning —
failover_total_timeout,failover_idempotency("safe"/"never"/"always"),same_provider_retries, andcircuit_breaker_recovery_timeout_jitter. Per-call override with thesolwyn_idempotentkeyword. - New exceptions —
UntranslatableRequestErrorandUntranslatableModelError, both carrying structural labels only. - New metadata fields —
is_provider_fallback,requested_provider,requested_model,failover_reason,failover_error_class,attempt_index,possibly_succeeded, andcall_idon the reported event.
Changed (BREAKING)
fallback_modelandprimary_providerremoved. Same-provider model fallback is now expressed as a same-provider entry in thefallback=chain (e.g.fallback=[(OpenAI(), "gpt-4o-mini")]); the primary provider is always auto-detected from the wrapped client. TheSOLWYN_FALLBACK_MODELandSOLWYN_PRIMARY_PROVIDERenvironment variables are no longer read. Migration: replacefallback_model="x"withfallback=[(SameClient(), "x")], and delete anyprimary_provider=argument.
0.1.5 — 2026-05-16
Added
- Agent runs —
solwyn.run(name)(sync and async context manager) attributes every LLM call inside the scope to a single agent run, so the dashboard groups cost and latency by run. Calls outside a scope are grouped into a synthetic per-day run. See Agent Runs. solwyn.run_in_executor(...)— Submit threaded work to aThreadPoolExecutorwith the active run tag preserved (contextvarsdo not propagate into worker threads automatically).solwyn.current_run()— Read the active(agent_run_id, agent_run_name)for correlating your own logs.- Wire fields —
agent_run_idandagent_run_nameadded to the reported metadata event.
0.1.4 — 2026-05-13
Added
- Full token-extraction parity — Complete normalized token details across providers.
- OpenAI Responses API — Token details extracted from both the Chat Completions and Responses API response shapes.
- Anthropic cache TTL split — Cache-creation tokens are captured separately as
cache_creation_5m_tokens(5-minute TTL, 1.25× rate) andcache_creation_1h_tokens(1-hour TTL, 2× rate), with an aggregate fallback for older responses. - OpenAI service tier — The response
service_tieris captured and reported on the metadata event when present.
0.1.3 — 2026-05-01
Changed (BREAKING)
- Single-credential SDK contract.
project_idremoved fromSolwyn()/AsyncSolwyn()constructors and theSOLWYN_PROJECT_IDenvironment variable is no longer read. The project is resolved server-side from the API key. Migration: delete theproject_id=kwarg and unsetSOLWYN_PROJECT_ID. - Project key format tightened. Project keys now match
^sk_proj_[a-f0-9]{64}$(was^sk_solwyn_[a-zA-Z0-9]{32,64}$). Existing keys are not migrated; create a new key from the dashboard.
0.1.2 — 2026-04-17
Added
- Same-provider model fallback (
fallback_model). Retry a failed call once with a different model on the same provider client. (Superseded in 0.1.6 by thefallback=chain.)
0.1.1 — 2026-04-15
Changed
- Tag-derived versioning — Migrated to
hatch-vcsso the package version is derived from the git tag. - Expanded test coverage for environment-variable construction and credential validation. No user-facing API changes.
0.1.0 — 2026-04-09
Initial release of the Solwyn Python SDK.
Added
- Zero-config construction —
Solwyn(OpenAI())andAsyncSolwyn(AsyncOpenAI())work with no kwargs whenSOLWYN_API_KEYis set in the environment. SolwynandAsyncSolwynclient wrappers — Drop-in wrappers for OpenAI, Anthropic, and Google Gemini clients with budget enforcement, circuit breaking, and metadata reporting.- Provider auto-detection — Automatically detects the LLM provider from the client instance. No
provider=argument needed. - Normalized token extraction — Extracts and normalizes token usage across all three providers into a consistent
TokenDetailsformat. - Budget enforcement — Pre-call budget checks via Solwyn Cloud API with local fallback. Supports
alert_only(default) andhard_denymodes. - Circuit breaker — Process-local circuit breaker with CLOSED/OPEN/HALF_OPEN states for provider health tracking.
- Metadata reporting — Non-blocking batch reporting of usage events to Solwyn Cloud. Background thread (sync) or
asyncio.create_task(async). SolwynConfig— Pydantic-validated configuration with environment variable loading (SOLWYN_*prefix).- Exception hierarchy —
BudgetExceededError,ProviderUnavailableError,ConfigurationErrorwith structured attributes. - OpenAI support — Chat Completions and Responses API response shapes. Optional
tiktokenextra for accurate token estimation. - Anthropic support — Cache normalization (additive
input_tokens). Reasoning tokens documented as always 0. - Google Gemini support — Thinking token normalization (
candidates + thoughts).usage_metadatahandling. - Fail-open default — LLM calls proceed when Solwyn Cloud is unreachable. Local tracking until connectivity is restored.
- Context manager support —
with Solwyn(...)andasync with AsyncSolwyn(...)for automatic resource cleanup.