Skip to main content

autobatcher (Python)

Drop-in replacement for AsyncOpenAI that uses Doubleword flex background inference and polling by default, with explicit 24-hour batch inference. It is designed for the Doubleword Inference API.

Why?

Async and batch inference reduce cost, but usually require applications to manage background response IDs, polling, JSONL uploads, and result matching. autobatcher handles those lifecycles behind the familiar OpenAI interface.

# Before: regular async calls (full price)
from openai import AsyncOpenAI
client = AsyncOpenAI()

# After: flex calls with background polling
from autobatcher import AsyncOpenAI
client = AsyncOpenAI(base_url="https://api.doubleword.ai/v1")

# Same interface, same code
response = await client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello!"}]
)

How it works

  1. Chat-completion and Responses calls are submitted immediately to the Responses API with service_tier="flex" and background=True.
  2. The returned response ID is polled with separate retrieval requests until completion; no inference connection remains open.
  3. Chat results are converted back to ChatCompletion; Responses results stay native Response objects.
  4. Embeddings always use 24-hour batches. Passing completion_window="24h" puts text generation on the same batch lifecycle.

Installation

pip install autobatcher

Usage

Chat completions

import asyncio
from autobatcher import BatchOpenAI

async def main():
    client = BatchOpenAI(
        api_key="sk-...",  # or set OPENAI_API_KEY env var
    )

    response = await client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": "What is 2+2?"}],
    )
    print(response.choices[0].message.content)

    await client.close()

asyncio.run(main())

Embeddings

async def embed(client: BatchOpenAI):
    response = await client.embeddings.create(
        model="text-embedding-3-small",
        input="Hello, world!",
    )
    print(response.data[0].embedding[:5])

Responses API

async def respond(client: BatchOpenAI):
    response = await client.responses.create(
        model="gpt-4o",
        input="Explain quantum computing in one sentence.",
    )
    print(response.output[0].content[0].text)

Parallel requests

Concurrent text requests are submitted independently and queued by Doubleword:

async def process_many(prompts: list[str]) -> list[str]:
    client = BatchOpenAI()

    async def get_response(prompt: str) -> str:
        response = await client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
        )
        return response.choices[0].message.content

    # All requests use background flex inference and poll independently
    results = await asyncio.gather(*[get_response(p) for p in prompts])

    await client.close()
    return results

Mixed endpoint routing

Chat uses flex polling while embeddings continue through a 24-hour batch:

async def mixed(client: BatchOpenAI):
    chat, embedding = await asyncio.gather(
        client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": "Hello!"}],
        ),
        client.embeddings.create(
            model="text-embedding-3-small",
            input="Hello!",
        ),
    )

Context manager

async with BatchOpenAI() as client:
    response = await client.chat.completions.create(...)

Serve mode

autobatcher serve runs a local OpenAI-compatible HTTP proxy. This is useful when you want to transparently route traffic from tools that already support an OpenAI-style base_url, such as evaluation frameworks, SDK consumers, or local benchmark runners.

autobatcher serve \
  --base-url https://api.doubleword.ai/v1 \
  --api-key "$DOUBLEWORD_API_KEY" \
  --host 127.0.0.1 \
  --port 8080 \
  --poll-interval 10

Then point your OpenAI-compatible client at the proxy:

export OPENAI_BASE_URL=http://127.0.0.1:8080/v1
export OPENAI_API_KEY=dummy

Use your real Doubleword credential for the proxy's upstream --api-key. The downstream client still uses a dummy OPENAI_API_KEY because it is only talking to the local OpenAI-compatible proxy.

Supported proxy routes:

Route Default upstream behavior
/v1/chat/completions flex Responses polling, converted back to chat
/v1/embeddings 24-hour batch
/v1/responses flex Responses polling
/health local healthcheck

Pass --mode batch to use 24-hour batches for text generation as well.

Batch lifecycle events

In serve mode, autobatcher emits structured JSON lines to stdout for batch lifecycle events. These are intended for log collection systems such as Kubernetes logs, Loki, or Cloud Logging.

Example event:

{
  "batch_id": "batch_123",
  "completion_window": "24h",
  "endpoint": "/v1/chat/completions",
  "event": "batch_submitted",
  "input_file_id": "file_123",
  "metadata": {
    "benchmark_id": "bench-2026-04-14",
    "github_run_id": "24393857047"
  },
  "models": ["Qwen/Qwen3.5-397B-A17B-FP8"],
  "request_count": 872,
  "source": "autobatcher",
  "ts": 1776163751.821
}

Emitted events currently include:

  • batch_submitted
  • batch_progress
  • batch_completed
  • batch_terminal
  • batch_cancel_requested
  • batch_cancelled_upstream
  • batch_cancel_failed
  • client_closing

Batch metadata

You can stamp correlation metadata onto every upstream batch:

autobatcher serve \
  --base-url https://api.doubleword.ai/v1 \
  --api-key "$DOUBLEWORD_API_KEY" \
  --batch-metadata benchmark_id=bench-2026-04-14 \
  --batch-metadata github_run_id=24393857047 \
  --batch-metadata k8s_job=perf-1234

This metadata is passed through to the upstream batches.create(...) call and is also included in the emitted lifecycle events.

Shutdown behavior

By default, serve mode best-effort cancels any still-active upstream batches when the proxy shuts down. This is useful for short-lived pods or CI jobs where the proxy lifetime should own the batch lifetime.

If you want upstream batches to continue running after the proxy exits, use:

autobatcher serve --keep-active-batches-on-close

Configuration

Parameter Default Description
api_key None OpenAI API key (falls back to OPENAI_API_KEY env var)
base_url None API base URL (for proxies or compatible APIs)
batch_size 1000 Submit batch when this many requests are queued
batch_window_seconds 10.0 Submit batch after this many seconds
poll_interval_seconds 5.0 How often to poll flex responses or batch completion
max_concurrent_requests 20 Maximum simultaneous flex HTTP operations per client
max_poll_retries 5 Consecutive failed poll retries after SDK retries
completion_window None Set to "24h" for text-generation batches
batch_metadata None Optional metadata attached to each upstream batch
cancel_active_batches_on_close False Best-effort cancel active upstream batches when closing the client

Completion window

An omitted value (and legacy "1h") selects Doubleword flex polling for text generation. Exactly "24h" selects the Batch API. Embeddings always use a 24-hour batch, irrespective of this setting.

Flex polling and recovery

Flex HTTP operations are limited to 20 per client by default. A waiting response holds no slot between polls, so other agents can still submit work. This limit covers flex submission, retrieval, and cancellation; it does not limit queued server jobs, Batch API operations, or calls made directly to inherited resources.

Flex polling adds 0–20% jitter to the configured interval. Connection errors and HTTP 404, 408, 409, 429, and 5xx responses retry the same response ID, with exponential backoff capped at 60 seconds before jitter (or a longer numeric Retry-After). Up to five consecutive retries are allowed after the OpenAI SDK's own per-call retries; a successful poll resets the counter. Submission is never repeated by this polling retry loop. Transport timeouts apply per HTTP call, not to the entire inference job.

Incomplete Responses are returned with their partial output and usage. Chat completions map max_output_tokens to finish_reason="length" and content_filter to finish_reason="content_filter".

from autobatcher import FlexPollingError

try:
    response = await client.responses.create(model=model, input=prompt)
except FlexPollingError as exc:
    # Retrieve this accepted job; do not resubmit the prompt.
    response = await client.responses.retrieve(exc.response_id)

Manual retrieval returns the current status, which may still be queued or in_progress. Continue retrieving the same ID until terminal. The original polling error is available through __cause__.

Supported endpoints

Endpoint Method Return type
client.chat.completions.create() Chat completions ChatCompletion
client.embeddings.create() Embeddings CreateEmbeddingResponse
client.responses.create() Responses API Response

Limitations

  • Not suitable for real-time or interactive use cases. Flex work has minutes-scale latency and 24-hour batches may take longer.
  • Streaming is not supported. Python with_streaming_response.create() is explicitly rejected to prevent bypassing flex/batch routing. Requests that would normally stream are forced to non-streaming; the serve proxy can re-wrap results as SSE for consuming clients.
  • Per-request headers, query parameters, and timeouts are forwarded for flex calls. Batch calls reject transport options because Batch API JSONL cannot represent them.
  • Flex polling is Doubleword-only. OpenAI users must select "24h" for batch inference or use the upstream OpenAI client for realtime inference.
  • No automatic escalation to realtime if flex or batch inference is delayed.

License

MIT

Metadata

Release files for autobatcher 0.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for autobatcher 0.11.0
File Size Uploaded
autobatcher-0.11.0.tar.gz 43.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for autobatcher 0.11.0
File Interpreter ABI Platform
autobatcher-0.11.0-py3-none-any.whl Python 3 none any Details

Total release size: 69.4 kB

Release files / autobatcher-0.11.0.tar.gz

Download URL autobatcher-0.11.0.tar.gz
Size 43.9 kB
Tags Source
SHA-256 checksum
How to use checksums
681f96ba945c3b4d4ca4791b99992a7bea6fd4ef31e4739e37843ad904c468af
BLAKE2b-256 checksum
How to use checksums
7cf9ab4c3aacd848d4627babefbf0c759dade6c701dafb20c37cefbed33ef6d8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / autobatcher-0.11.0-py3-none-any.whl

Download URL autobatcher-0.11.0-py3-none-any.whl
Size 25.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a3933a48d8f015da1421a4d1533032b014574e24aa4299b0cc85886e7e7be6c9
BLAKE2b-256 checksum
How to use checksums
02b1f937821abd05d768e5d6237b8b1f2870c2b942acf1d900790775badcaa36
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.11.0 This release

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page