AsyncSolwyn
Reference for the AsyncSolwyn asynchronous client wrapper
import asyncio
import os
from openai import AsyncOpenAI
from solwyn import AsyncSolwyn
async def main():
async with AsyncSolwyn(
AsyncOpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
) as client:
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
asyncio.run(main())Asynchronous client wrapper. Same API and behavior as Solwyn, but all I/O operations are async.
Constructor
AsyncSolwyn(
client,
*,
api_key: str | None = None,
model: str | None = None,
provider: str | None = None,
fallback: list[tuple] | None = None,
default_params: dict | None = None,
tags: Mapping[str, str] | None = None,
on_unmetered: str | None = None, # "warn" | "raise" | "allow"
acknowledge_untracked: Collection[str] | None = None,
selection_policy: SelectionPolicy | None = None,
control_plane_transport: ControlPlaneTransport | None = None,
**config_kwargs,
)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
client | openai.AsyncOpenAI | anthropic.AsyncAnthropic | genai.Client | aioboto3 bedrock-runtime client | Yes | The async LLM provider client to wrap. Provider is auto-detected — including OpenAI-compatible endpoints via the client's base_url. Becomes the primary entry in the failover chain. A sync client pinned with provider= raises ConfigurationError(field="client") naming Solwyn as the wrapper to use; without a pin the mismatch is not caught at construction and surfaces at call time. |
api_key | str | None | Yes (kwarg or env) | Solwyn API key. Must match sk_proj_<64 lowercase hex chars>. Falls back to SOLWYN_API_KEY if omitted. |
model | str | None | No | Model for the primary entry. Optional for single-provider use; set it when configuring a fallback chain. |
provider | str | None | No | An explicit provider identity for the primary client. SDK v0.6.0+: bypasses auto-detection and selects the named adapter (for a gateway base_url or a non-default local port); it never translates dialects or rewrites the endpoint. Unknown name: ConfigurationError(field="provider"); client-family or sync/async mismatch: ConfigurationError(field="client"). |
fallback | list[tuple] | None | No | Failover chain entries: (client, model), (client, model, default_params), or (client, model, default_params, provider) tuples, in attempt order. Use the async client classes here. See Provider Failover. |
default_params | dict | None | No | Global fill-absent request params applied to every entry (per-entry default_params wins). |
tags | Mapping[str, str] | None | No | SDK v0.5.0+. Default spend tags for every intercepted call; falls back to SOLWYN_TAGS. See Tags. |
on_unmetered | "warn" | "raise" | "allow" | No | SDK v0.6.0+. Posture for untracked provider capabilities; default "warn". See Coverage controls. |
acknowledge_untracked | Collection[str] | None | No | SDK v0.6.0+. Exact capability tokens exempt from on_unmetered; validated at construction. |
selection_policy | SelectionPolicy | None | No | Candidate ordering policy. Defaults to HealthBasedPolicy; LatencyPolicy and CostPolicy are also available. Since 0.6.0 CostPolicy is driven by per-call server price hints. |
control_plane_transport | ControlPlaneTransport | None | No | SDK v0.6.0+. A transport for all Solwyn Cloud traffic. The async client needs both handle_request and handle_async_request, because interpreter-exit drains are blocking. You own it; the SDK never closes it. |
**config_kwargs | No | Additional configuration options. See SolwynConfig. |
Everything after client is keyword-only. If SOLWYN_API_KEY is present in the environment, you can omit api_key:
# With SOLWYN_API_KEY set in the environment:
client = AsyncSolwyn(AsyncOpenAI())Raises
The same set as Solwyn: ConfigurationError at construction (invalid API key, malformed fallback, unknown config field, provider= / client-family mismatch, invalid tags, invalid acknowledgment token, non-finite failover timeout) and at call time (the Bedrock invoke_model family, and Responses background / streaming parse / metering-critical extra_body); UntranslatableRequestError and UntranslatableModelError; UntrackedSpendSurfaceError under on_unmetered="raise"; UnsupportedSurfaceError; and RunStoppedError inside a stopped run.
Example
import os
from anthropic import AsyncAnthropic
from openai import AsyncOpenAI
from solwyn import AsyncSolwyn
client = AsyncSolwyn(
AsyncOpenAI(),
model="gpt-4o",
api_key=os.environ["SOLWYN_API_KEY"],
budget_mode="hard_deny",
fallback=[(AsyncAnthropic(), "claude-sonnet-4-5", {"max_tokens": 1024})],
)Methods
close()
await client.close() -> NoneShuts down the async metadata reporter and closes HTTP connections. Always call await client.close() when you are done, or use async with.
It then forwards to the wrapped provider client's own close — aclose() if the client has one, otherwise close() — and awaits the result when it is awaitable.
Changed in 0.4.0. Two things:
close()is bounded by one wall-clock deadline (reporter_shutdown_deadline, default5.0seconds), set on the constructor rather than passed here. Undelivered work at the deadline is counted and dropped — see Undeliverable spend. Held budget leases are surrendered on the way out.- The reporter's flush loop now auto-starts on the first enqueue, so an
AsyncSolwynbuilt withoutasync withno longer queues events and settlements silently untilclose().async withis still the recommended pattern. See Async reporters start on first use.
close() is the only method AsyncSolwyn adds. The run APIs — solwyn.run(...), solwyn.start_run(...), solwyn.create_run(...) and its RunHandle, solwyn.current_run_context() — are module-level functions on the solwyn package and work the same under async with; their signatures are in the API summary on Agent runs.
Async context manager
AsyncSolwyn supports async with. The __aenter__ method starts the background metadata reporter, and __aexit__ flushes pending events and closes connections:
import os
from openai import AsyncOpenAI
from solwyn import AsyncSolwyn
async with AsyncSolwyn(
AsyncOpenAI(),
api_key=os.environ["SOLWYN_API_KEY"],
) as client:
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
# close() is called automatically on exitCall surfaces
All calls use await:
OpenAI
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
# SDK v0.6.0+: the Responses API is metered too
response = await client.responses.create(model="gpt-4o", input="Hello!")
async with client.responses.stream(model="gpt-4o", input="Hello!") as stream:
async for event in stream:
...responses.create, responses.parse, and the responses.stream() helper are metered on native OpenAI and Azure OpenAI, with the same refusals as the sync client. The async stream() helper is returned without awaiting and defers its budget check to the first __aenter__; a helper that is never entered performs no check. See The Responses API.
Anthropic
response = await client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)Google Gemini
response = await client.models.generate_content(
model="gemini-2.0-flash",
contents="Hello!",
)Amazon Bedrock
Async Bedrock uses aioboto3 — pass the client to AsyncSolwyn() inside the session context manager:
import os
import aioboto3
from solwyn import AsyncSolwyn
session = aioboto3.Session()
async with session.client("bedrock-runtime", region_name="us-east-1") as bedrock:
async with AsyncSolwyn(
bedrock,
api_key=os.environ["SOLWYN_API_KEY"],
) as client:
response = await client.converse(
modelId="us.anthropic.claude-3-5-sonnet-20241022-v2:0",
messages=[{"role": "user", "content": [{"text": "Hello!"}]}],
inferenceConfig={"maxTokens": 1024},
)await client.converse_stream(...) returns the boto3 contract dict; iterate result["stream"] with async for and close with await result["stream"].close() or async with result["stream"]: on early abandonment. invoke_model / invoke_model_with_response_stream raise ConfigurationError exactly as on the sync client. See Amazon Bedrock.
Media surfaces
The async client serves every media surface the sync client does — the async proxies mirror the sync set one for one, with the same postures. All are awaited:
embedding = await client.embeddings.create(model="text-embedding-3-small", input="...")
image = await client.images.generate(model="gpt-image-1", prompt="...", n=1)
edited = await client.images.edit(model="gpt-image-1", image=..., prompt="...")
transcript = await client.audio.transcriptions.create(model="whisper-1", file=..., response_format="json")
speech = await client.audio.speech.create(model="tts-1", voice="alloy", input="...")
video_job = await client.videos.create(model="sora-2", prompt="...") # OpenAI onlyOn a Google client, await client.models.embed_content(...), await client.models.generate_images(...) (Imagen), and await client.models.generate_videos(...) (Veo) are intercepted the same way. Each surface is budget-checked before dispatch and recorded with its modality, identically to the sync client; audio.translations is untracked and follows on_unmetered, and a media surface the wrapped client's adapter does not serve fails loud with UnsupportedSurfaceError. See Surface coverage for every surface's posture and the provider × modality matrix for what each provider is priced for.
Attribute passthrough and type transparency
Any attribute not intercepted by AsyncSolwyn is resolved against the underlying async provider client through the coverage guard: untracked leaves follow on_unmetered, namespaces come back guarded, and structural attributes are silent. Writes and deletes forward to the provider client.
SDK v0.6.0+: isinstance(client, AsyncOpenAI) is True and client.__class__ is AsyncOpenAI, while type(client) stays AsyncSolwyn. copy.copy / copy.deepcopy return the same wrapper and pickle raises TypeError. Details are identical to the sync client — see Type transparency.
Differences from Solwyn
| Aspect | Solwyn | AsyncSolwyn |
|---|---|---|
| Context manager | with | async with |
| Close | client.close() | await client.close() |
| LLM calls | client.chat.completions.create() | await client.chat.completions.create() |
| Budget checks | Synchronous HTTP | Asynchronous HTTP |
| Reporter | Background thread queue | asyncio.create_task |
| Provider clients | OpenAI, Anthropic, genai.Client, boto3 bedrock-runtime | AsyncOpenAI, AsyncAnthropic, genai.Client, aioboto3 bedrock-runtime |
The interception behavior (budget check, circuit breaker, metadata reporting) is identical. Only the I/O layer differs.