SOLWYN

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 stable agent_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 on run(); the SDK stamps the run id on the check and enforces the answer, raising the same BudgetExceededError in hard_deny mode. 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 the solwyn.budget logger 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 reserved solwyn_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=False or SOLWYN_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, and false applies 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, a failover_total_timeout change 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 extractionusage.prompt_tokens_details.cache_write_tokens (Chat Completions) and usage.input_tokens_details.cache_write_tokens (Responses API) are now extracted and reported as cache_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_tokens stays 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.
  • UnsupportedSurfaceError in the package rootfrom solwyn import UnsupportedSurfaceError now 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, so from solwyn.exceptions import UnsupportedSurfaceError keeps 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 interceptionembeddings.create (OpenAI dialect, native Together) and models.embed_content (Google) are budget-checked and recorded as cost events with modality embedding, priced from input tokens; Google exposes no usage on embeddings, so its counts are length-estimated and flagged is_estimated. See Surface coverage.
  • Image interceptionimages.generate and images.edit (OpenAI, compat, native Together) and models.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 interceptionaudio.transcriptions.create and audio.speech.create (OpenAI, compat incl. Groq, native Together). Transcriptions price from usage token buckets or whole-second durations on a JSON response_format — a non-JSON format is recorded unpriced, never guessed; speech prices from the input character count measured inside the privacy firewall. audio.translations and usage-less TTS models (gpt-4o-mini-tts) warn once and pass through. See Surface coverage.
  • Video interceptionvideos.create (OpenAI Sora) and models.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.create fails loud instead of passing through untracked.
  • MediaUsage on the wire — non-token billable quantities (image_count, generation_count, video_seconds, audio_seconds, input_characters) plus the resolution / quality selectors, and a modality field on every cost event. Pre-flight budget checks carry estimated_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 Together or AsyncTogether client 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. The solwyn[together] extra installs together>=2.0 as 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 calling glm-* models) is detected and attributed as provider="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

  • CostPolicy no-op warningCostPolicy is 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.warning calls 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 when fail_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 honor fail_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 mode in the Cloud budget response rather than the local budget_mode config, fixing local/Cloud mode drift (e.g. local alert_only vs. a Cloud hard_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-runtime client: Solwyn(boto3.client("bedrock-runtime")). Intercepts converse / 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 via usage.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); the solwyn[bedrock] extra installs boto3>=1.34 as a convenience. invoke_model / invoke_model_with_response_stream raise ConfigurationError instead of silently bypassing budget tracking. See Amazon Bedrock.
  • OpenAI-compatible providers — Point an openai.OpenAI client at xAI, DeepSeek, Mistral, Qwen, Groq, Together, Fireworks, Perplexity, Azure OpenAI, OpenRouter, Ollama, vLLM, LM Studio, or any compatible endpoint via base_url; Solwyn detects the real provider for attribution, budgets, and failover instead of mislabeling it openai. 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 raise ConfigurationError at 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_body are stripped and max_completion_tokensmax_tokens is 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.reporter logger (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 provider values (bedrock plus fourteen OpenAI-compatible identifiers), optional provider_region (omitted when unset), service_tier on 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 policiesselection_policy= accepts HealthBasedPolicy (default) or LatencyPolicy to 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 argumentsmodel= (primary entry model), fallback=, default_params= (global fill-absent params), and selection_policy=.
  • Failover tuningfailover_total_timeout, failover_idempotency ("safe" / "never" / "always"), same_provider_retries, and circuit_breaker_recovery_timeout_jitter. Per-call override with the solwyn_idempotent keyword.
  • New exceptionsUntranslatableRequestError and UntranslatableModelError, both carrying structural labels only.
  • New metadata fieldsis_provider_fallback, requested_provider, requested_model, failover_reason, failover_error_class, attempt_index, possibly_succeeded, and call_id on the reported event.

Changed (BREAKING)

  • fallback_model and primary_provider removed. Same-provider model fallback is now expressed as a same-provider entry in the fallback= chain (e.g. fallback=[(OpenAI(), "gpt-4o-mini")]); the primary provider is always auto-detected from the wrapped client. The SOLWYN_FALLBACK_MODEL and SOLWYN_PRIMARY_PROVIDER environment variables are no longer read. Migration: replace fallback_model="x" with fallback=[(SameClient(), "x")], and delete any primary_provider= argument.

0.1.5 — 2026-05-16

Added

  • Agent runssolwyn.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 a ThreadPoolExecutor with the active run tag preserved (contextvars do not propagate into worker threads automatically).
  • solwyn.current_run() — Read the active (agent_run_id, agent_run_name) for correlating your own logs.
  • Wire fieldsagent_run_id and agent_run_name added 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) and cache_creation_1h_tokens (1-hour TTL, 2× rate), with an aggregate fallback for older responses.
  • OpenAI service tier — The response service_tier is captured and reported on the metadata event when present.

0.1.3 — 2026-05-01

Changed (BREAKING)

  • Single-credential SDK contract. project_id removed from Solwyn() / AsyncSolwyn() constructors and the SOLWYN_PROJECT_ID environment variable is no longer read. The project is resolved server-side from the API key. Migration: delete the project_id= kwarg and unset SOLWYN_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 the fallback= chain.)

0.1.1 — 2026-04-15

Changed

  • Tag-derived versioning — Migrated to hatch-vcs so 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 constructionSolwyn(OpenAI()) and AsyncSolwyn(AsyncOpenAI()) work with no kwargs when SOLWYN_API_KEY is set in the environment.
  • Solwyn and AsyncSolwyn client 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 TokenDetails format.
  • Budget enforcement — Pre-call budget checks via Solwyn Cloud API with local fallback. Supports alert_only (default) and hard_deny modes.
  • 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 hierarchyBudgetExceededError, ProviderUnavailableError, ConfigurationError with structured attributes.
  • OpenAI support — Chat Completions and Responses API response shapes. Optional tiktoken extra 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_metadata handling.
  • Fail-open default — LLM calls proceed when Solwyn Cloud is unreachable. Local tracking until connectivity is restored.
  • Context manager supportwith Solwyn(...) and async with AsyncSolwyn(...) for automatic resource cleanup.

On this page