Wurun

Async OpenAI API wrapper optimized for Jupyter notebooks

Features

HTTP/2 Connection Pooling

Shared client for efficient API calls with persistent connections

Robust Retry Logic

Exponential backoff for rate limits, timeouts and 5xx — and fail-fast on errors retrying cannot fix

Batch Processing

Concurrent API calls with semaphore control and order preservation

Jupyter Optimized

Clean notebook output and error handling for interactive development

DataFrame Support

Process pandas DataFrames with built-in message column handling

Zero Configuration

Simple setup that works with OpenAI and Azure OpenAI out of the box

Installation

Production Use

pip install wurun

With DataFrame Support

pip install wurun[dataframe]

Development

pip install -e ".[dev,dataframe]"

Requirements

Python 3.10 or newer. Works with the OpenAI API, Azure OpenAI, and any OpenAI-compatible endpoint.

Quick Start

Basic Usage

from wurun import Wurun

# Setup once per kernel
await Wurun.setup(
    endpoint="https://api.openai.com/v1",
    api_key="your-api-key",
    deployment_name="gpt-3.5-turbo"
)

# Single question
messages = [{"role": "user", "content": "Explain asyncio"}]
answer = await Wurun.ask(messages)
print(answer)

# Control the response
answer = await Wurun.ask(
    messages,
    max_tokens=512,      # default: 1024
    temperature=0.7,     # default: 0
)

# Cleanup
await Wurun.close()

Batch Processing

# Ordered results
questions = [
    [{"role": "user", "content": "What is Python?"}],
    [{"role": "user", "content": "What is JavaScript?"}]
]
answers = await Wurun.run_gather(questions, concurrency=2)

# Or stream them as they finish: (index, answer)
for idx, answer in await Wurun.run_as_completed(questions, concurrency=2):
    print(idx, answer)

# DataFrame processing (order is preserved)
import pandas as pd
df = pd.DataFrame({'messages': questions})
df['answer'] = await Wurun.run_dataframe(
    df, 'messages', concurrency=2, max_tokens=512
)

Configuration

await Wurun.setup(
    endpoint="https://api.openai.com/v1",
    api_key="your-key",
    deployment_name="gpt-3.5-turbo",
    timeout=60.0,        # per-request cap, seconds
    max_connections=32,
    max_keepalive=16,
    http2=True,
    max_retries=2,       # SDK-level retries
)

timeout is the per-request cap. Calls inherit it unless they pass their own timeout.

setup() is safe to call again in the same kernel. The connection pool is reused when the pool settings are unchanged, and rebuilt when timeout, max_connections, max_keepalive or http2 differ — so re-running your setup cell with a new value actually takes effect.

Retry budget

max_retries is applied by the OpenAI SDK underneath ask(attempts=...). The worst case is attempts × (max_retries + 1) HTTP requests — 15 with the defaults. Lower either one for a tighter bound.

Error Handling

ask() never raises on API failures — it returns an "[ERROR] ..." string so one bad row cannot abort a whole batch. Use return_meta=True to detect failures reliably instead of matching on the string.

answer, meta = await Wurun.ask(messages, return_meta=True)

if meta["error"]:
    print(f"failed: {meta['error_type']}")
else:
    print(f"{meta['latency']:.2f}s, {meta['retries']} retries")

Metadata fields

  • latency - seconds elapsed, including retry backoff
  • retries - retries used (0 on first-attempt success)
  • error - True if the answer is an error string
  • error_type - exception name, or None on success

What gets retried

Only transient failures: rate limits, connection and timeout errors, HTTP 408/429, and any 5xx. Non-retryable statuses such as 400, 401, 403 and 404 return immediately rather than burning the retry budget on a request that cannot succeed.

API Reference

Every call accepts max_tokens, temperature and timeout; every batch call also accepts concurrency and return_meta.

Setup & Teardown

  • Wurun.setup() - Initialize client
  • Wurun.close() - Clean up resources

Single Calls

  • Wurun.ask() - Single API call with retry
  • return_meta=True - Return (answer, meta) with latency, retries and error info

Batch Processing

  • Wurun.run_gather() - Preserve input order
  • Wurun.run_as_completed() - Yield (index, answer) in completion order
  • Wurun.run_dataframe() - Process DataFrame columns

Notebook Helpers

  • Wurun.print_qna_ordered() - Pretty print Q&A
  • Wurun.print_as_ready() - Print as completed