SOLWYN
Guides

Async Usage

Use AsyncSolwyn for async applications — context managers, event loops, and provider examples

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())

AsyncSolwyn provides the same API as Solwyn but with async/await support. Use it when your application runs in an async event loop (FastAPI, aiohttp, etc.).

Context manager

Always use async with to ensure resources are properly cleaned up:

async with AsyncSolwyn(
    AsyncOpenAI(),
    api_key=os.environ["SOLWYN_API_KEY"],
) as client:
    # client is ready -- reporter is started
    response = await client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": "Hello!"}],
    )
# client.close() is called automatically

The async with block calls __aenter__() which starts the background metadata reporter, and __aexit__() which flushes pending reports and closes HTTP connections.

If you cannot use a context manager, call await client.close() explicitly:

client = AsyncSolwyn(
    AsyncOpenAI(),
    api_key=os.environ["SOLWYN_API_KEY"],
)

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

Changed in 0.4.0. This pattern used to be quietly lossy: without __aenter__, the reporter never started its flush loop, so events and settlements sat in the queue until close() — and server-side spend tracking drifted for the life of the client. The flush loop now starts on the first enqueue, so the client above reports normally. await client.close() is still required to flush what remains and surrender any held budget leases. See Spend delivery.

Per-provider async

The async surface mirrors the sync wrapper for every provider: pass the provider's async client to AsyncSolwyn() and call it the same way you would synchronously. Each provider page carries its own async example and any provider-specific notes:

Event loop notes

  • AsyncSolwyn must be used within a running async event loop.
  • The background metadata reporter uses asyncio.create_task() to send batches without blocking your LLM calls.
  • Changed in 0.4.0: that flush loop now starts on the first enqueue, not only on an explicit start(). Previously, an AsyncSolwyn constructed without async with queued events and settlements silently until close(), which drifted server-side spend tracking. If there is no running loop when an event is enqueued, the item stays queued and the SDK logs reporter.enqueue_without_event_loop once per reporter. Enqueueing never raises. See Spend delivery.
  • Do not mix Solwyn (sync) and AsyncSolwyn (async) in the same event loop. Use Solwyn for synchronous code and AsyncSolwyn for async code.
  • asyncio.run(main()) creates and manages the event loop. If you are using a framework like FastAPI, the framework manages the event loop for you -- just use async with in your lifespan or dependency injection.

Concurrent calls

AsyncSolwyn supports concurrent LLM calls with asyncio.gather:

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:
        tasks = [
            client.chat.completions.create(
                model="gpt-4o",
                messages=[{"role": "user", "content": f"Count to {i}"}],
            )
            for i in range(1, 4)
        ]
        responses = await asyncio.gather(*tasks)

        for r in responses:
            print(r.choices[0].message.content)

asyncio.run(main())

Each concurrent call independently checks budget, reports metadata, and updates the circuit breaker.

On this page