One contract,
any LLM
backend
The Adapter protocol — the stable, zero-dependency contract any LLM backend implements so ogentic-router, and the rest of the suite, can call it uniformly. Implement one async method; hand back the provider's response, unchanged.
Every backend, one call path.
OpenAI, Anthropic, Ollama, or a backend you wrote yourself — the caller makes the same call. Each adapter returns its provider's native response, unchanged. The contract standardizes the call, not the payload.
A new backend is one async method away — implement chat(), pass the conformance suite, and the whole suite can reach it.
{"choices":[{"message":{"role":"assistant","content":"…"}}],"usage":{"total_tokens":38}}Five things a contract has to be.
A contract is only worth building on if it's tiny, stable, and honest about the payload. These are the properties that let the whole suite — and your own code — depend on it without regret.
Zero runtime dependencies
The contract layer pulls in nothing else — import it without dragging in shield, router, or audit. It's the base every other package can depend on without a dependency cycle. Optional integrations live behind extras.
One method to implement
A backend is a single async method — chat(messages, …). Implement it and the whole suite can call your provider. No base class to inherit, no framework to adopt — just satisfy the protocol.
Provider-native passthrough
Adapters hand back the provider's response unchanged. The contract standardizes the call, not the payload — so you never lose a field to a lowest-common-denominator wrapper.
Stable by semver
Once a symbol is exported from the package, it follows semver — breaking changes ship a major bump and a deprecation notice first. Build on it without chasing churn.
The seam the suite is built on
Router already calls its backends through this contract; your own code can too. It's the seam that keeps the whole pipeline decoupled from any single vendor's SDK — swap the provider, keep the callers.
It's a contract, not a gateway: there's no hosted service and no runtime in the path — just the interface and the types every backend agrees to.
One method. That's the contract.
No base class, no framework, no runtime. The Adapter is a typed Protocol with a single async method — anything that implements it is a backend the whole suite can call.
class Adapter(Protocol):
async def chat(
self,
messages: list[Message],
*,
model: str | None = None,
) -> ChatResponse: ...- Interface
- one async chat(messages, …)
- Returns
- provider-native response, unchanged
- Deps
- zero runtime deps on ogentic-*
- Stability
- semver on the public API
- Extras
- opt-in integrations ([router], …)
- Typing
- Pydantic v2 models · fully typed
Because it depends on nothing, every other package can depend on it — the base of the suite that never causes a dependency cycle.
One seam, many payoffs.
Everything a stable provider abstraction buys you — provider swaps, custom backends, conformance, and freedom from lock-in — falls out of the same one-method contract.
Change the backend, not the callers
Move from one provider to another — or run several — without touching a line of caller code. Same contract, a different adapter behind it.
Wrap anything that generates
Bring an in-house model, an internal gateway, or a niche provider into the suite by implementing one method against the protocol.
Prove it behaves
A shared conformance suite checks your adapter against the same expectations the reference adapters meet — so a new backend is a known quantity before it ships.
Independent of any vendor
Keep the suite — and your application — free of any single provider's SDK in the hot path. The contract is the seam that makes the swap cheap.
Implement, conform, register, call.
How a new backend joins the suite. The whole ceremony is one method and a test run — after that, anything that routes by backend id can reach it.
implementSatisfy the protocol
Write one async method — chat(messages, …) — that calls your provider and returns its native response. No base class, no framework.
conformRun the conformance suite
The shared conformance tests verify your adapter matches the contract — the same bar the reference adapters clear.
registerGive it a backend id
Register the adapter under an id in config. Anything that routes by backend id — like ogentic-router — can now reach it.
callCall it through the contract
The suite (or your own code) calls the adapter through the protocol — provider-agnostic, one code path for every backend.
evolveStay compatible
The public API only changes by semver — your adapter keeps working across minor and patch versions without edits.
implement → conform → register → call → evolve// The only thing you depend on is the contract — no provider stack pulled into your process, no framework to adopt.
Reference adapters, one interface.
The suite ships adapters for the common providers, all satisfying the same protocol. Yours joins them the moment it implements the method — no privileged position for the built-ins.
OpenAIAdapter
Implements the Adapter protocol; returns the provider's response unchanged.
AnthropicAdapter
Implements the Adapter protocol; returns the provider's response unchanged.
OllamaAdapter
Implements the Adapter protocol; returns the provider's response unchanged.
MyBackend
Implement one async method and it conforms — a first-class backend, same as the rest.
A contract, not a wrapper.
Most provider abstractions are fat wrappers or hosted gateways — they normalize responses (and lose fields), pull in a stack, and tie you to the vendor. The Adapter is the opposite: a thin protocol you implement, that gets out of the way.
- Shape
- A fat wrapper / gateway
- Dependencies
- Pulls in a provider stack
- Response
- Normalized (often lossy)
- Lock-in
- Tied to the vendor
- Stability
- Follows the vendor's SDK
- Shape
- A thin protocol
- Dependencies
- Zero runtime deps
- Response
- Provider-native, unchanged
- Lock-in
- Swap adapters freely
- Stability
- Semver on the contract
The contract is the seam. Because it's tiny and depends on nothing, it's what ogentic-router ↗ calls backends through — and what keeps your own code free of any single provider's SDK in the hot path.
The whole contract is one method.
Python 3.11+, Pydantic v2, fully typed, Apache-2.0. The examples below are illustrative — the public API is being finalized for the Wave 2 release.
from typing import Protocol
from ogentic_adapter_sdk import Message, ChatResponse
class Adapter(Protocol):
async def chat(
self,
messages: list[Message],
*,
model: str | None = None,
) -> ChatResponse: ...
# That's the whole contract. Anything with this method is an adapter.from ogentic_adapter_sdk import Message, ChatResponse
class MyBackend:
async def chat(self, messages, *, model=None) -> ChatResponse:
raw = await my_provider.generate(messages, model=model)
return ChatResponse.from_provider(raw) # native passthrough
# No base class, no registration ceremony — it just conforms.pip install ogentic-adapter-sdk # Wave 2 — coming soon # zero runtime deps; opt into integrations with extras: pip install "ogentic-adapter-sdk[router]"
The base the whole suite plugs into.
Composable Apache-2.0 primitives for regulated AI. Shield, Redact, Router, and Audit each do one job — and Router reaches every model through this one contract. The adapter-sdk is the seam underneath, so nothing is tied to a single vendor.
The contract that ogentic-router ↗ calls its backends through — alongside ogentic-shield ↗ and ogentic-redact ↗, part of the OgenticAI ↗ OSS suite.
The contract lands in Wave 2.
The zero-dependency backend contract for the OgenticAI suite — semver-stable, Apache-2.0. Router already routes through it; the standalone package ships in Wave 2. Follow the repo.