Skip to main content

OpenAI-compatible client for the OpenAPI Router gateway, with routing, attribution and usage.

Project description

openapi-ai-sdk (Python)

pip install openapi-ai-sdk      # installs the openapi_ai module
from openapi_ai import OpenAPI, provider, metadata, routing, session

client = OpenAPI(api_key="gw_…", metadata={"env": "prod"})

with session("conversation-42"):
    response = client.chat.completions.create(
        model="openai/gpt-4.1-mini",
        messages=[{"role": "user", "content": "hello"}],
        extra_body=routing(
            provider(order=["groq"], sort="throughput"),
            metadata(tenant="acme"),
        ),
    )

print(response.usage.cost)                            # USD, on every response
client.usage.generations(metadata={"tenant": "acme"})  # what that tenant spent

OpenAPI is an openai.OpenAI — a subclass that overrides nothing, so migrating is one import and one name. Streaming, tool calls, retries and typed responses are the OpenAI SDK's own, and anything built on that SDK — LangChain, LlamaIndex, Instructor — takes this client unchanged. What the platform adds appears as namespaces on the same client: capabilities, invocations, callbacks, usage. There is an AsyncOpenAPI twin over openai.AsyncOpenAI, and router(...) remains as a factory spelling of the same client.

What is ours

provider(...) which provider serves the request: order, only, ignore, sort, data_collection
models(...) fallback models, tried in order
transforms(...) middle-out prompt compression
metadata(...) labels recorded with the request and filterable afterwards
routing(...) merges the above into one extra_body
session(...) attributes every request in the block to one conversation
generation, generations, credits, key_info what it cost, and what a set of calls cost
client.capabilities, client.invocations call capability contracts, sync or async — see below
client.callbacks.verify(...) constant-time verification of signed result webhooks
client.usage generation, generations, credits, key_info, namespaced

One deployment, https://openapi.ai/v1, so there is nothing to configure. Point base_url somewhere else for a local stack. Where inference physically runs is a routing choice per request — provider(jurisdictions=["EU"]) — not a hostname.

Recording what you spent

from openapi_ai import router, SqliteStore, summary

store = SqliteStore("~/.local/state/openapi-ai/calls.db")
client = router(api_key="gw_…", store=store)

client.chat.completions.create(..., extra_body=metadata(tenant="acme"))
summary(store, metadata={"tenant": "acme"})
# {'calls': 12, 'billable': 11, 'cached': 1, 'errors': 0, 'cost': 0.0241, ...}

Content is not stored by default. Ids, model, routing, tokens, cost, latency and your labels are; prompts and completions need store_io=True. This mirrors the server, where I/O capture is opt-in per key and off by default — a client that wrote transcripts to disk by default would invert that decision onto a machine nobody reviewed. A request sent with data_collection="deny" never has its content stored at all, whatever the flag says.

Backends: SqliteStore (recommended), JsonFileStore (grep-able, single process), PostgresStore (shared; needs the postgres extra, and makes whoever runs it a data controller for what it holds).

Caching

store = SqliteStore("calls.db", store_io=True)   # a cache needs a body to replay
client = router(api_key="gw_…", store=store, cache=True)

Non-streaming only, mirroring the gateway's own refusal to cache streams. Note what a client-side hit skips: it never reaches the gateway, so credits, free-tier counters, budgets, guardrails and rate limits see nothing. That is why it is opt-in.

The cache key covers everything that changes the answer — including provider preferences, model suffixes like :online, and data_collection — and excludes what does not, so labelling a request does not give it its own cache entry. It also mixes in a fingerprint of your API key, so a shared store cannot serve one organization's response to another.

Capabilities

Capability contracts invert the usual integration: your organization defines a named, versioned interface — an input schema and an output schema — providers register endpoints that fulfill it, and the platform validates both directions and retries across fulfillments. You integrate against the contract, never a provider API.

summarize = client.capabilities.get("doc-summary", version=3)

# Synchronous: the handle is callable; blocks until a conforming result.
result = summarize(input={"text": "..."})

# Asynchronous: a live handle now, the result when the work finishes.
job = summarize.submit(input={"text": "..."})
print(job.wait().output["summary"])

Binding name and version once is the point: published contracts are frozen, callers integrate against exactly one schema pair, and neither the gateway nor the SDK invents a floating "latest". Three unhappy endings, kept apart on purpose:

  • a refusal (nonconforming input, the in-flight cap, exhausted fulfillments) raises CapabilityInvocationError at the call site, carrying the typed code, the JSON-pointer paths, and the attempt trail;
  • a failed invocation is a terminal outcome the platform already retried — job.failed and job.error read it as data, while job.output raises InvocationFailed so the happy path cannot quietly read a None;
  • a timeout in wait() raises InvocationTimeout and changes nothing: the job keeps running server-side.

Long-running work can skip polling: pass callback_url= to submit, then verify what arrives — client.callbacks.verify(raw_body, signature_header, secret="whsec_…") does the constant-time, tolerance-checked HMAC and returns the parsed document. AsyncOpenAPI serves the same namespaces with await.

Capabilities as model tools

tools = [client.capabilities.get("doc-summary", version=3).as_tool()]
# … model answers with tool_calls …
messages.append(client.capabilities.execute_tool_call(call))

A contract is already a tool definition; execute_tool_call runs the call through the platform and answers with the tool message. Model-caused failures (hallucinated arguments, nonconforming input) become tool-message content, never exceptions — an agent loop that crashes on a bad model turn is a loop nobody can run.

Typed contracts, generated

A published contract is a frozen pair of JSON Schemas — a type registry the platform already runs. Pull one into your codebase:

openapi-ai pull doc-summary@3

writes doc_summary_v3.py: TypedDicts for both sides plus invoke/submit helpers typed end to end, so the type checker holds your calls to the same schema pair the provider is held to at runtime. Commit the file; regenerate only to bind a different version — the contract cannot change under you, because published versions are frozen. The generator handles the common schema subset and degrades to Any (never a crash) for constructs the platform validates but a TypedDict cannot express. The key comes from --api-key or OPENAPI_AI_API_KEY.

Fulfilling capabilities (providers)

The other side of the contract, for provider teams: your endpoint is your own server, and the SDK provides the pieces every endpoint needs — the constant-time bearer check, the parsed job, completion with the platform's answers surfaced as typed outcomes, and the pull-mode shapes spelled once.

from openapi_ai import DispatchedJob, verify_dispatch, push_result, push_failure

verify_dispatch(request.headers.get("Authorization"), secret=DISPATCH_SECRET)
job = DispatchedJob.parse(await request.body())     # input already conforms
push_result(job.completion_url, {"summary": "…"})   # or push_failure(...)

A nonconforming result raises CompletionRejected with the JSON-pointer paths — and the token is burned; rejected.gone marks a consumed or expired token. An honest push_failure is authoritative by default: the platform does not shop the same input to another provider.

Development

python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
PYTHONPATH=. .venv/bin/python -m pytest tests/

tests/test_matches_the_gateway.py reads the gateway source and the committed spec, so a value this package validates locally cannot silently drift from what the server actually accepts.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openapi_ai_sdk-0.1.0.tar.gz (55.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openapi_ai_sdk-0.1.0-py3-none-any.whl (45.0 kB view details)

Uploaded Python 3

File details

Details for the file openapi_ai_sdk-0.1.0.tar.gz.

File metadata

  • Download URL: openapi_ai_sdk-0.1.0.tar.gz
  • Upload date:
  • Size: 55.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for openapi_ai_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 06851e1ec5b4840c049af51d24684863d85b5760c0ba3ba7e86c094738df4a9c
MD5 6f34d643f4c6ff9b426e963433adf11a
BLAKE2b-256 80c800ce77c1e33e938f69dc3dde1cc8684b04fa6bd8d00e8a8068e7cb65960d

See more details on using hashes here.

Provenance

The following attestation bundles were made for openapi_ai_sdk-0.1.0.tar.gz:

Publisher: pypi.yml on sireto/openapi-ai

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file openapi_ai_sdk-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: openapi_ai_sdk-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 45.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for openapi_ai_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 013149a340c368836873f2351197fa3c486e0a8aa5de957ac7a4fad9e8fd73bb
MD5 fb194a3bf1b0be5446abd90dc12ffab1
BLAKE2b-256 4f6bd4392c4abaccb1a42c455f0c03b299cc49a0d04487b0ff9bc3b43bd3684c

See more details on using hashes here.

Provenance

The following attestation bundles were made for openapi_ai_sdk-0.1.0-py3-none-any.whl:

Publisher: pypi.yml on sireto/openapi-ai

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page