AI Agent Control Plane SDK — hard spending caps, automatic failover, per-agent cost attribution
Project description
Solwyn Python SDK
Budget enforcement, circuit breaking, and usage tracking for OpenAI, Anthropic, and Google LLM clients.
Solwyn wraps your existing LLM client. Calls go directly to the provider — the SDK only reports metadata (token counts, latency, model name) to the Solwyn API. Prompts and responses never leave your application.
Installation
pip install solwyn
For improved token estimation with OpenAI models:
pip install solwyn[openai]
Quick Start
from openai import OpenAI
from solwyn import Solwyn
client = Solwyn(
OpenAI(),
api_key="sk_solwyn_...",
project_id="proj_abc12345",
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
client.close()
Or use as a context manager:
with Solwyn(OpenAI(), api_key="sk_solwyn_...", project_id="proj_abc12345") as client:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
Providers
OpenAI
from openai import OpenAI
from solwyn import Solwyn
client = Solwyn(OpenAI(), api_key="sk_solwyn_...", project_id="proj_abc12345")
response = client.chat.completions.create(model="gpt-4o", messages=[...])
Anthropic
from anthropic import Anthropic
from solwyn import Solwyn
client = Solwyn(Anthropic(), api_key="sk_solwyn_...", project_id="proj_abc12345")
response = client.messages.create(model="claude-sonnet-4-20250514", max_tokens=1024, messages=[...])
Google Gemini
from google import genai
from solwyn import Solwyn
client = Solwyn(genai.Client(api_key="..."), api_key="sk_solwyn_...", project_id="proj_abc12345")
response = client.models.generate_content(model="gemini-2.0-flash", contents="Hello!")
Async
from openai import AsyncOpenAI
from solwyn import AsyncSolwyn
async with AsyncSolwyn(
AsyncOpenAI(),
api_key="sk_solwyn_...",
project_id="proj_abc12345",
) as client:
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
Streaming
Pass stream=True as you normally would. Solwyn wraps the stream transparently and reports usage when it completes:
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
Budget Enforcement
Set budget_mode to control spending:
client = Solwyn(
OpenAI(),
api_key="sk_solwyn_...",
project_id="proj_abc12345",
budget_mode="hard_deny",
)
| Mode | Behavior |
|---|---|
alert_only |
Log a warning when budget is exceeded (default) |
hard_deny |
Raise BudgetExceededError and block the call |
from solwyn import BudgetExceededError
try:
response = client.chat.completions.create(model="gpt-4o", messages=[...])
except BudgetExceededError as e:
print(f"Budget limit: ${e.budget_limit}, usage: ${e.current_usage}")
Configuration
| Parameter | Env Var | Default | Description |
|---|---|---|---|
api_key |
SOLWYN_API_KEY |
required | Solwyn API key |
project_id |
SOLWYN_PROJECT_ID |
required | Project identifier |
api_url |
SOLWYN_API_URL |
https://api.solwyn.ai |
Solwyn API endpoint |
fail_open |
SOLWYN_FAIL_OPEN |
True |
Allow LLM calls when Solwyn API is unreachable |
budget_mode |
SOLWYN_BUDGET_MODE |
alert_only |
Budget enforcement mode |
fallback_provider |
SOLWYN_FALLBACK_PROVIDER |
None |
Failover provider when primary circuit breaks |
Use env vars to avoid passing credentials in code:
export SOLWYN_API_KEY="sk_solwyn_..."
export SOLWYN_PROJECT_ID="proj_abc12345"
client = Solwyn(OpenAI()) # picks up from environment
Error Handling
All SDK errors inherit from SolwynError:
| Exception | Raised when |
|---|---|
BudgetExceededError |
Budget exceeded in hard_deny mode |
ProviderUnavailableError |
Circuit breaker is open and no fallback exists |
ConfigurationError |
Invalid API key or project ID format |
Provider errors (e.g., openai.RateLimitError) pass through unmodified.
Data Transparency
The SDK sends a MetadataEvent after each LLM call. This is everything it transmits:
| Field | Type | Description |
|---|---|---|
project_id |
str |
Project identifier |
model |
str |
Model name (e.g., gpt-4o) |
provider |
str |
openai, anthropic, or google |
input_tokens |
int |
Input token count |
output_tokens |
int |
Output token count |
token_details |
object |
Breakdown: cached, reasoning, audio tokens |
latency_ms |
float |
Call duration in milliseconds |
status |
str |
success, error, or budget_denied |
is_failover |
bool |
Whether a fallback provider was used |
sdk_instance_id |
str |
Per-process UUID for deduplication |
timestamp |
datetime |
When the call completed (UTC) |
The SDK never captures, logs, or transmits prompts or responses. This is enforced by structural tests and the privacy module.
Requirements
Python 3.11+
Contributing
make install # install in dev mode
make install-hooks # install pre-commit hook
make check # lint + format + typecheck
make test # run unit tests
Links
- Documentation
- Solwyn Cloud — Dashboard, alerts, and analytics
- MPI.sh — LLM API pricing comparison
License
Apache 2.0 — see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file solwyn-0.1.0.tar.gz.
File metadata
- Download URL: solwyn-0.1.0.tar.gz
- Upload date:
- Size: 118.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa1c1501091ed293f9b64b7e84c9fba520542787c67e80eec11d35ce4258e05b
|
|
| MD5 |
6323f23c7ab206f8774557743bae17c3
|
|
| BLAKE2b-256 |
cedaef2fb563018a277069ecdc3f1be5d3c34c326c86e0774dd7eb85b1ac030c
|
Provenance
The following attestation bundles were made for solwyn-0.1.0.tar.gz:
Publisher:
publish.yml on solwyn-ai/solwyn-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
solwyn-0.1.0.tar.gz -
Subject digest:
fa1c1501091ed293f9b64b7e84c9fba520542787c67e80eec11d35ce4258e05b - Sigstore transparency entry: 1269790249
- Sigstore integration time:
-
Permalink:
solwyn-ai/solwyn-python@450221192f25ed181c5c399f54504fa9bf4a9913 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/solwyn-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@450221192f25ed181c5c399f54504fa9bf4a9913 -
Trigger Event:
push
-
Statement type:
File details
Details for the file solwyn-0.1.0-py3-none-any.whl.
File metadata
- Download URL: solwyn-0.1.0-py3-none-any.whl
- Upload date:
- Size: 42.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b323d65dcc111178d0ca16b79c945978390ec46b19701c6fd81e54fefb95c5d2
|
|
| MD5 |
d6c9552cdc60cfd6c72f9a4c8bc0b5f9
|
|
| BLAKE2b-256 |
cc997a14103b90cb97fea7a56371e396513c4ef46acc1d43d4afd380df8b7b0f
|
Provenance
The following attestation bundles were made for solwyn-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on solwyn-ai/solwyn-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
solwyn-0.1.0-py3-none-any.whl -
Subject digest:
b323d65dcc111178d0ca16b79c945978390ec46b19701c6fd81e54fefb95c5d2 - Sigstore transparency entry: 1269790324
- Sigstore integration time:
-
Permalink:
solwyn-ai/solwyn-python@450221192f25ed181c5c399f54504fa9bf4a9913 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/solwyn-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@450221192f25ed181c5c399f54504fa9bf4a9913 -
Trigger Event:
push
-
Statement type: