Type-safe, zero-SDK adapter layer that normalizes any LLM provider into one consistent interface.
Project description
unifai
A minimalist, model-agnostic adapter layer for LLMs. No massive SDKs, strict type-safe normalization, zero-overhead abstraction — just pydantic, httpx, and boto3. Unlike LiteLLM, unifai calls provider APIs directly rather than wrapping heavyweight SDKs, giving you a predictable canonical schema with nothing hidden underneath.
Install
pip install -e .
# With dev dependencies
pip install -e ".[dev]"
Quick Start
import asyncio
from unifai import ModelGate, ModelGateConfig
async def main():
client = ModelGate(ModelGateConfig(
openai_api_key="sk-...",
anthropic_api_key="sk-ant-...",
))
# Non-streaming
response = await client.chat(
model="anthropic/claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "What is 2+2?"}],
)
print(response.text) # "4"
print(response.tool_calls) # [] — same shape for ALL providers
# Streaming (yields ContentBlock chunks, then a final Usage)
async for chunk in client.stream(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Tell me a story"}],
):
if chunk.type == "text":
print(chunk.text, end="", flush=True)
asyncio.run(main())
Supported Providers
| Provider | Model string format | Adapter | Status |
|---|---|---|---|
| OpenAI | openai/<model-id> |
OpenAIAdapter |
✅ Full |
| Anthropic | anthropic/<model-id> |
AnthropicAdapter |
✅ Full |
| AWS Bedrock | bedrock/<model-id> |
BedrockAdapter |
✅ Full |
| Groq | groq/<model-id> |
GenericOpenAIAdapter |
✅ Full |
| Ollama | ollama/<model-id> |
GenericOpenAIAdapter |
✅ Full |
| Gemini | gemini/<model-id> |
GeminiAdapter |
✅ Full |
| Vertex AI | vertex/<model-id> |
VertexAdapter |
⚠️ Not Tested |
Any OpenAI-compatible API can be added by pointing GenericOpenAIAdapter at a new base_url — no new adapter code required.
Tool Use
Tools produce the exact same ContentBlock shape regardless of provider:
from unifai import Tool, ToolParameter
weather_tool = Tool(
name="get_weather",
description="Get current weather for a location",
parameters={
"location": ToolParameter(type="string", description="City name"),
},
required=["location"],
)
response = await client.chat(
model="anthropic/claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Weather in NYC?"}],
tools=[weather_tool],
)
for tc in response.tool_calls:
print(tc.tool_name) # "get_weather"
print(tc.tool_input) # {"location": "NYC"} — always a dict, never a string
Error Handling
Every adapter catches raw httpx.HTTPStatusError and re-raises as a typed ModelGateError — provider-specific error formats never leak to your code:
ModelGateError
├── AuthenticationError # 401 — invalid or missing API key
├── RateLimitError # 429 — provider rate limit exceeded
├── InvalidRequestError # 400 — malformed input
├── ProviderError # 5xx — unexpected provider failure
│ ├── BedrockError
│ └── VertexError
└── StreamingError # error mid-stream
from unifai import RateLimitError, AuthenticationError
try:
response = await client.chat(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
)
except RateLimitError:
# retry with backoff
except AuthenticationError:
# bad key
Architecture
src/unifai/
├── __init__.py # Public API surface
├── types.py # Pydantic v2 canonical schemas
├── errors.py # ModelGateError hierarchy
├── client.py # ModelGate entry point + provider routing
└── providers/
├── base.py # BaseProvider ABC
├── openai.py # OpenAI adapter
├── anthropic.py # Anthropic adapter
├── bedrock.py # AWS Bedrock Converse API
├── gemini.py # Gemini (stub)
├── vertex.py # Vertex AI (stub)
└── generic_openai.py # OpenAI-compatible fallback
Testing
pytest tests/ -v
Dependencies
pydantic— type-safe modelshttpx— async HTTP (no provider SDKs)boto3— AWS credential signing only
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file modelgate-0.1.1.tar.gz.
File metadata
- Download URL: modelgate-0.1.1.tar.gz
- Upload date:
- Size: 35.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9a828bc291ed7e7ca5e8d60b62f9457bc686ed53df0875e6284a06e5d72b251
|
|
| MD5 |
47074d925224ea5dfa9039ab89c4dd92
|
|
| BLAKE2b-256 |
c794560d9094ddea186f26ede4e5ccc4536c39a962c7c3d5c8b1e3366d4e3190
|
Provenance
The following attestation bundles were made for modelgate-0.1.1.tar.gz:
Publisher:
publish.yml on PavanPapiReddy22/modelgate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
modelgate-0.1.1.tar.gz -
Subject digest:
a9a828bc291ed7e7ca5e8d60b62f9457bc686ed53df0875e6284a06e5d72b251 - Sigstore transparency entry: 1189473581
- Sigstore integration time:
-
Permalink:
PavanPapiReddy22/modelgate@53aad4aac40627e4e65b86fe7a9cbbde362c4e0c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/PavanPapiReddy22
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@53aad4aac40627e4e65b86fe7a9cbbde362c4e0c -
Trigger Event:
release
-
Statement type:
File details
Details for the file modelgate-0.1.1-py3-none-any.whl.
File metadata
- Download URL: modelgate-0.1.1-py3-none-any.whl
- Upload date:
- Size: 27.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d0ec6ac02eedd9de60bf974401f11e9ba91c9f503311b7ec857f826f0d23620
|
|
| MD5 |
f18bcaa38a9ce687e94cf89d7ea6e124
|
|
| BLAKE2b-256 |
f19688c2b1626cd17c2740b84242c3ec83426101f9c4c3e0f0313b8860364ae2
|
Provenance
The following attestation bundles were made for modelgate-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on PavanPapiReddy22/modelgate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
modelgate-0.1.1-py3-none-any.whl -
Subject digest:
8d0ec6ac02eedd9de60bf974401f11e9ba91c9f503311b7ec857f826f0d23620 - Sigstore transparency entry: 1189473587
- Sigstore integration time:
-
Permalink:
PavanPapiReddy22/modelgate@53aad4aac40627e4e65b86fe7a9cbbde362c4e0c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/PavanPapiReddy22
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@53aad4aac40627e4e65b86fe7a9cbbde362c4e0c -
Trigger Event:
release
-
Statement type: