SOLWYN

Overview

Solwyn Python SDK — hard spending caps, automatic failover, and per-agent cost attribution for AI applications

import os
from openai import OpenAI
from solwyn import Solwyn

client = Solwyn(
    OpenAI(),
    api_key=os.environ["SOLWYN_API_KEY"],
)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello, world!"}],
)

print(response.choices[0].message.content)
client.close()

Solwyn is a drop-in wrapper for your existing LLM provider client. It adds hard spending caps, automatic provider failover, and per-agent cost attribution to your AI applications -- without ever seeing your prompts or responses.

The SDK is a context engine: after each LLM call it reads raw token counts from the provider's response and forwards them to Solwyn Cloud, the intelligence engine that owns pricing and budget state. LLM calls go directly to the provider — Solwyn is never in the request path. See How it works for the full architecture and Privacy for the wire contract.

New in 0.9.0

  • Optional OpenTelemetry. Metadata events carry the active trace ID, span ID, and sampled flag (on by default, trace_correlation=False to disable). otel_spans=True emits content-free solwyn.call and solwyn.run spans through your tracer provider, and anthropic_conversation_id=True tags Anthropic's native spans with the active run ID. → OpenTelemetry
  • Anthropic beta Messages and compaction are metered. beta.messages.create is budget-checked and settled like messages.create, and server-side compaction usage — reported only in usage.iterations — is now counted in the settled totals. → Beta Messages
  • OpenAI Decisions is metered. Native decisions.create (openai>=3.26) is budget-checked and settled with text pricing. → Decisions
  • Runs survive fresh-context generators. Runs opened in sync generators that Starlette/FastAPI StreamingResponse steps on worker threads keep their attribution and release their leases. → Sync generators stepped in fresh contexts
  • Newer provider SDKs. Tested against anthropic 2.x and together 3.x; the surface ledger classifies openai through 3.37, anthropic through 2.3, together 2.41 and 3.0, and google-genai 2.25's voices. Two leaves are promoted to metered, so pinned coverage fingerprints move. → Coverage controls

Version 0.8.0 removed the local outage cost estimate, made fail_open=False fail closed, and kept the token bound on calls that do not finish cleanly; 0.7.0 handed leases back the moment a run stopped using them; 0.6.0 added dashboard run stops, the FakeControlPlane test double, coverage controls, framework integrations, native Responses metering, and cost-aware routing. → Changelog

Key guarantees

  • No prompt capture -- Solwyn never reads, logs, or transmits your prompts or responses. Only bounded metadata leaves your process — token counts, latencies, model and provider labels, OpenTelemetry trace and span IDs when a span is active, and the run names and tags you set yourself. See Privacy.
  • Fail-open by default -- If Solwyn Cloud is unreachable, your LLM calls proceed, and every call admitted that way is tallied and reported once Solwyn is reachable again. No downtime from the control plane. Set fail_open=False to fail closed instead.
  • Provider-agnostic -- Wrap OpenAI, Anthropic, Google Gemini, or Amazon Bedrock clients — or any OpenAI-compatible endpoint via base_url — with the same API surface.
  • Server-side pricing -- The API owns all pricing tables. Pricing updates require only an API deploy, never an SDK release. Nothing in the SDK multiplies tokens by a price — not even during an outage (SDK v0.8.0+).

Supported providers

ProviderClientModels
OpenAIopenai.OpenAI / openai.AsyncOpenAIgpt-*, o3*, o4*
Anthropicanthropic.Anthropic / anthropic.AsyncAnthropicclaude-*
Google Geminigoogle.genai.Clientgemini-*
Amazon Bedrockboto3.client("bedrock-runtime") (sync) / aioboto3 (async)vendor.model ids, geo inference profiles (us., eu., ...), Bedrock ARNs — every chat model on the Converse API
OpenAI-compatible (xAI, DeepSeek, Mistral, Qwen, Groq, Together, Fireworks, Perplexity, Azure OpenAI, OpenRouter, Ollama, vLLM, LM Studio, ...)openai.OpenAI / openai.AsyncOpenAI with base_url setthe endpoint's native model ids

The SDK auto-detects your provider from the client you pass in — client type, base_url host, Azure client class, or conventional local port. An optional provider= pin bypasses detection and names the adapter yourself — for endpoints detection cannot name (vLLM on a non-default port), or to keep native OpenAI metering (including the Responses API) behind a corporate gateway base_url; see OpenAI-compatible providers.

What the SDK tracks

After every LLM call, Solwyn extracts a normalized token breakdown from the provider's response:

FieldDescriptionProviders
input_tokensTotal input tokens (normalized)All
output_tokensTotal output tokens (normalized)All
cached_input_tokensTokens served from prompt cacheOpenAI, Anthropic, Google, Bedrock
cache_creation_5m_tokensTokens written to prompt cache (5-minute TTL on Anthropic and Bedrock; OpenAI's cache writes map into this bucket to preserve the wire contract — the name is not a TTL claim for OpenAI, see OpenAI)OpenAI, Anthropic, Bedrock
cache_creation_1h_tokensTokens written to prompt cache (1-hour TTL)Anthropic, Bedrock
reasoning_tokensChain-of-thought / thinking tokensOpenAI, Google
audio_input_tokensAudio input tokensOpenAI, Google
audio_output_tokensAudio output tokensOpenAI, Google
image_input_tokensImage input tokens (token-billed image models)OpenAI, Google
image_output_tokensImage output tokens (token-billed image models)OpenAI, Google
accepted_prediction_tokensTokens from accepted predicted outputsOpenAI
rejected_prediction_tokensTokens from rejected predicted outputsOpenAI
tool_use_input_tokensTokens for tool/function definitionsGoogle
compaction_input_tokens, compaction_cached_input_tokens, compaction_cache_creation_5m_tokens, compaction_cache_creation_1h_tokens, compaction_output_tokensServer-side compaction share of the matching totals, each omitted when zero (SDK v0.9.0+)Anthropic
is_estimatedBoolean flag — set when the provider returned no usable usage data and the counts are SDK-side length-based estimates. Serialized only when true.OpenAI-compatible; Google (embeddings)

OpenAI-compatible providers reuse the full OpenAI extractor, so every field marked OpenAI applies to them as well when the endpoint reports it. These raw counts are sent to Solwyn Cloud, which uses its own pricing tables to compute exact dollar costs. The SDK never computes or estimates costs itself — the only estimation is the explicitly flagged is_estimated fallback above, which still reports token counts, never dollars.

Beyond chat, embedding calls are tracked and priced too: embeddings.create on OpenAI and every OpenAI-compatible endpoint, and Google's models.embed_content, are budget-checked before the request and reported as a cost event with modality embedding, which the Cloud API prices at the model's per-token input rate. OpenAI-compatible endpoints report usage.prompt_tokens and fall back to a flagged length estimate only when it is absent; Google's embedding responses expose no usage metadata, so Google embedding counts are always length-estimated (is_estimated = true). As always, only token counts — never text, never the embedding vectors — leave your process.

Image generation is tracked and priced too. images.generate (plus images.edit on native OpenAI) and Google's models.generate_images are budget-checked before the request — the pre-flight estimate is exact, derived from the image count, size, and quality you request, so an over-budget call is denied before the provider is reached — and reported as a cost event with modality image. OpenAI's gpt-image models return image-token usage, so the SDK reports image_input_tokens / image_output_tokens and the Cloud API bills text and image tokens at their separate rates; OpenAI-compatible image endpoints (including Together) return no usage, so quantities are request-derived (image count × the model's per-image rate); Google's Imagen bills per image from config.number_of_images. Only token counts, image counts, and the size/quality selectors you pass leave your process — never the prompt, never image bytes, never the generated image.

Audio is tracked and priced too. OpenAI's audio.transcriptions.create and audio.speech.create — on the native client and on every OpenAI-compatible endpoint, including Groq — are budget-checked before the request and recorded as a cost event with modality audio. Token-billed transcription models (the gpt-4o-transcribe family) price from their usage token buckets, with audio input tokens reported as audio_input_tokens; whisper-1-style models price from the whole-second audio duration the provider reports, which is present whenever you request a JSON response_format — a transcription made with a non-JSON response_format (text/srt/vtt) returns no usage, so it is recorded but left unpriced and the SDK warns once suggesting a JSON response_format. Text-to-speech models tts-1 / tts-1-hd price exactly from the input character count, measured inside the privacy firewall so the pre-flight check is exact and the text never leaves your process; token-billed TTS models (gpt-4o-mini-tts) report no usage of any kind and are deliberately left untracked (warn-once), never billed a fake $0. Google's Gemini text-to-speech and native-audio models price their audio token buckets at audio rates through generate_content. Only token counts, whole-second durations, and character counts leave your process — never audio bytes, never the input text, never the transcript.

Video generation is tracked and priced too. OpenAI's videos.create (Sora) and Google's models.generate_videos (Veo) are budget-checked before the request — the pre-flight estimate is exact, the requested duration (seconds / config.duration_seconds) at the requested resolution times that resolution variant's per-second rate, so an over-budget video is denied before the provider is reached — and recorded as a cost event with modality video. Both providers return an asynchronous job or long-running operation, which passes through untouched for you to poll as usual. Because that job carries no usage and neither provider charges for failed or blocked generations, video billing settles at request time from the requested duration and resolution and is always flagged is_estimated — a deliberate, conservative over-count. OpenAI's documented defaults (4 seconds, 720x1280) apply when omitted; Google publishes no default duration, so a Veo call with no config.duration_seconds is recorded but left unpriced, never a guessed duration. Only durations and resolution selectors leave your process — never the prompt, never a reference image, never the generated video.

OpenAI Decisions and Anthropic beta Messages are tracked and priced too (SDK v0.9.0+). Native OpenAI decisions.create and Anthropic beta.messages.create are budget-checked and settled with text pricing. See Decisions and Beta Messages.

The OpenAI Responses API is tracked and priced too (SDK v0.6.0+). responses.create, responses.parse, and the responses.stream() helper on native OpenAI and Azure OpenAI clients are budget-checked and metered like Chat Completions, with usage read from the response or the stream's terminal event. See The Responses API.

With video and Responses shipped, the full modality surface — text, embeddings, images, audio, and video — is tracked and priced across the providers that offer each. Every other provider capability your code reaches is classified: a reviewed-but-unmetered surface (files, batches, moderations, audio.translations, the token-billed gpt-4o-mini-tts, ...) warns once per process by default and can be refused or allowed with on_unmetered; see Coverage controls. On Amazon Bedrock, invoke_model / start_async_invoke fail loud rather than pass through untracked.

Next steps

On this page