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.
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, and the run names and tags you set yourself. See Privacy.
- Fail-open by default -- If Solwyn Cloud is unreachable, your LLM calls proceed normally with local tracking. No downtime from the control plane.
- 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.
Supported providers
| Provider | Client | Models |
|---|---|---|
| OpenAI | openai.OpenAI / openai.AsyncOpenAI | gpt-*, o3*, o4* |
| Anthropic | anthropic.Anthropic / anthropic.AsyncAnthropic | claude-* |
| Google Gemini | google.genai.Client | gemini-* |
| Amazon Bedrock | boto3.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 set | the 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= override names endpoints detection cannot (e.g. vLLM on a non-default port); see OpenAI-compatible providers.
What the SDK tracks
After every LLM call, Solwyn extracts a normalized token breakdown from the provider's response:
| Field | Description | Providers |
|---|---|---|
input_tokens | Total input tokens (normalized) | All |
output_tokens | Total output tokens (normalized) | All |
cached_input_tokens | Tokens served from prompt cache | OpenAI, Anthropic, Google, Bedrock |
cache_creation_5m_tokens | Tokens 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_tokens | Tokens written to prompt cache (1-hour TTL) | Anthropic, Bedrock |
reasoning_tokens | Chain-of-thought / thinking tokens | OpenAI, Google |
audio_input_tokens | Audio input tokens | OpenAI, Google |
audio_output_tokens | Audio output tokens | OpenAI, Google |
image_input_tokens | Image input tokens (token-billed image models) | OpenAI, Google |
image_output_tokens | Image output tokens (token-billed image models) | OpenAI, Google |
accepted_prediction_tokens | Tokens from accepted predicted outputs | OpenAI |
rejected_prediction_tokens | Tokens from rejected predicted outputs | OpenAI |
tool_use_input_tokens | Tokens for tool/function definitions | |
is_estimated | Boolean 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.
With video shipped, the full modality surface — embeddings, images, audio, and video — is tracked and priced across the providers that offer each. The only recognized-but-untracked surfaces that remain warn once and pass through: OpenAI's audio.translations and the token-billed gpt-4o-mini-tts. On Amazon Bedrock, invoke_model / start_async_invoke fail loud rather than pass through untracked.
Next steps
- Quickstart -- Get running in 5 minutes
- Providers -- Provider-specific usage details
- How it works -- Wrapper-not-proxy, the context-engine / intelligence-engine split
- Budget Enforcement -- Set up spending caps
- Provider Failover -- Cross-provider failover chains and selection policies
- Agent Runs -- Group cost and latency by unit of work
- Privacy -- What we transmit, what we never see
- Reference -- Full constructor and method signatures