otari 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- otari-0.0.1/.github/workflows/publish.yml +57 -0
- otari-0.0.1/.gitignore +33 -0
- otari-0.0.1/PKG-INFO +304 -0
- otari-0.0.1/README.md +275 -0
- otari-0.0.1/pyproject.toml +65 -0
- otari-0.0.1/src/otari/__init__.py +85 -0
- otari-0.0.1/src/otari/client.py +645 -0
- otari-0.0.1/src/otari/errors.py +159 -0
- otari-0.0.1/src/otari/py.typed +0 -0
- otari-0.0.1/src/otari/types.py +106 -0
- otari-0.0.1/tests/__init__.py +0 -0
- otari-0.0.1/tests/unit/__init__.py +0 -0
- otari-0.0.1/tests/unit/test_client.py +712 -0
- otari-0.0.1/tests/unit/test_errors.py +177 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: astral-sh/setup-uv@v6
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: uv sync --extra dev
|
|
25
|
+
|
|
26
|
+
- name: Lint
|
|
27
|
+
run: uv run ruff check .
|
|
28
|
+
|
|
29
|
+
- name: Type check
|
|
30
|
+
run: uv run mypy src/
|
|
31
|
+
|
|
32
|
+
- name: Test
|
|
33
|
+
run: uv run pytest
|
|
34
|
+
|
|
35
|
+
publish:
|
|
36
|
+
needs: test
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment: pypi
|
|
39
|
+
permissions:
|
|
40
|
+
contents: read
|
|
41
|
+
id-token: write
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v4
|
|
44
|
+
|
|
45
|
+
- uses: astral-sh/setup-uv@v6
|
|
46
|
+
|
|
47
|
+
- name: Set version from release tag
|
|
48
|
+
run: |
|
|
49
|
+
VERSION="${GITHUB_REF_NAME#v}"
|
|
50
|
+
echo "Publishing version: $VERSION"
|
|
51
|
+
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
|
|
52
|
+
|
|
53
|
+
- name: Build package
|
|
54
|
+
run: uv build
|
|
55
|
+
|
|
56
|
+
- name: Publish to PyPI
|
|
57
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
otari-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.egg-info/
|
|
7
|
+
*.egg
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# IDE
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
*~
|
|
23
|
+
|
|
24
|
+
# Testing / Linting
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
htmlcov/
|
|
28
|
+
.mypy_cache/
|
|
29
|
+
.ruff_cache/
|
|
30
|
+
|
|
31
|
+
# OS
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
otari-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: otari
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python client for the otari gateway
|
|
5
|
+
Project-URL: Homepage, https://github.com/mozilla-ai/otari-sdk-python
|
|
6
|
+
Project-URL: Documentation, https://mozilla-ai.github.io/otari/
|
|
7
|
+
Project-URL: Repository, https://github.com/mozilla-ai/otari-sdk-python
|
|
8
|
+
Project-URL: Issues, https://github.com/mozilla-ai/otari-sdk-python/issues
|
|
9
|
+
Author-email: Mozilla AI <ai-engineering@mozilla.com>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: httpx>=0.25.0
|
|
22
|
+
Requires-Dist: openai>=1.99.3
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<picture>
|
|
32
|
+
<img src="https://raw.githubusercontent.com/mozilla-ai/otari/refs/heads/main/docs/public/images/otari-logo-mark.png" width="20%" alt="Project logo"/>
|
|
33
|
+
</picture>
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
<div align="center">
|
|
37
|
+
|
|
38
|
+
# otari (Python)
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+
[](https://pypi.org/project/otari/)
|
|
42
|
+
<a href="https://discord.gg/4gf3zXrQUc">
|
|
43
|
+
<img src="https://img.shields.io/static/v1?label=Chat%20on&message=Discord&color=blue&logo=Discord&style=flat-square" alt="Discord">
|
|
44
|
+
</a>
|
|
45
|
+
|
|
46
|
+
**Python client for [otari-gateway](https://github.com/mozilla-ai/otari).**
|
|
47
|
+
Communicate with any LLM provider through the gateway using a single, typed interface.
|
|
48
|
+
|
|
49
|
+
[TypeScript SDK](https://github.com/mozilla-ai/otari-sdk-ts) | [Documentation](https://mozilla-ai.github.io/otari/) | [Platform (Beta)](https://otari.ai/)
|
|
50
|
+
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
## Quickstart
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from otari import OtariClient
|
|
57
|
+
|
|
58
|
+
client = OtariClient(
|
|
59
|
+
api_base="http://localhost:8000",
|
|
60
|
+
platform_token="your-token-here",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
response = await client.completion(
|
|
64
|
+
model="openai:gpt-4o-mini",
|
|
65
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
print(response.choices[0].message.content)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**That's it!** Change the model string to switch between LLM providers through the gateway.
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
### Requirements
|
|
76
|
+
|
|
77
|
+
- Python 3.11 or newer
|
|
78
|
+
- A running [otari-gateway](https://mozilla-ai.github.io/otari/gateway/overview/) instance
|
|
79
|
+
|
|
80
|
+
### Install
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install otari
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Setting Up Credentials
|
|
87
|
+
|
|
88
|
+
Set environment variables for your gateway:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
export GATEWAY_API_BASE="http://localhost:8000"
|
|
92
|
+
export GATEWAY_PLATFORM_TOKEN="your-token-here"
|
|
93
|
+
# or for non-platform mode:
|
|
94
|
+
export GATEWAY_API_KEY="your-key-here"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Alternatively, pass credentials directly when creating the client (see [Usage](#usage) examples).
|
|
98
|
+
|
|
99
|
+
## otari-gateway
|
|
100
|
+
|
|
101
|
+
This Python SDK is a client for [otari-gateway](https://github.com/mozilla-ai/otari), an **optional** FastAPI-based proxy server that adds enterprise-grade features on top of the core library:
|
|
102
|
+
|
|
103
|
+
- **Budget Management** - Enforce spending limits with automatic daily, weekly, or monthly resets
|
|
104
|
+
- **API Key Management** - Issue, revoke, and monitor virtual API keys without exposing provider credentials
|
|
105
|
+
- **Usage Analytics** - Track every request with full token counts, costs, and metadata
|
|
106
|
+
- **Multi-tenant Support** - Manage access and budgets across users and teams
|
|
107
|
+
|
|
108
|
+
The gateway sits between your applications and LLM providers, exposing an OpenAI-compatible API that works with any supported provider.
|
|
109
|
+
|
|
110
|
+
### Quick Start
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
docker run \
|
|
114
|
+
-e GATEWAY_MASTER_KEY="your-secure-master-key" \
|
|
115
|
+
-e OPENAI_API_KEY="your-api-key" \
|
|
116
|
+
-p 8000:8000 \
|
|
117
|
+
ghcr.io/mozilla-ai/otari/gateway:latest
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
> **Note:** You can use a specific release version instead of `latest` (e.g., `1.2.0`). See [available versions](https://github.com/orgs/mozilla-ai/packages/container/package/otari%2Fgateway).
|
|
121
|
+
|
|
122
|
+
### Managed Platform (Beta)
|
|
123
|
+
|
|
124
|
+
Prefer a hosted experience? The [otari platform](https://otari.ai/) provides a managed control plane for keys, usage tracking, and cost visibility across providers, while still building on the same `otari` interfaces.
|
|
125
|
+
|
|
126
|
+
## Usage
|
|
127
|
+
|
|
128
|
+
### Authentication Modes
|
|
129
|
+
|
|
130
|
+
The client supports two authentication modes, matching the TypeScript SDK:
|
|
131
|
+
|
|
132
|
+
#### Platform Mode (Recommended)
|
|
133
|
+
|
|
134
|
+
Uses a Bearer token in the standard Authorization header:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
client = OtariClient(
|
|
138
|
+
api_base="http://localhost:8000",
|
|
139
|
+
platform_token="tk_your_platform_token",
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Non-Platform Mode
|
|
144
|
+
|
|
145
|
+
Sends the API key via a custom `Otari-Key` header:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
client = OtariClient(
|
|
149
|
+
api_base="http://localhost:8000",
|
|
150
|
+
api_key="your-api-key",
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### Auto-Detection from Environment Variables
|
|
155
|
+
|
|
156
|
+
When no explicit credentials are provided, the client reads from environment variables:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# Uses GATEWAY_API_BASE, GATEWAY_PLATFORM_TOKEN, or GATEWAY_API_KEY
|
|
160
|
+
client = OtariClient()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Chat Completions
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
response = await client.completion(
|
|
167
|
+
model="openai:gpt-4o-mini",
|
|
168
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
print(response.choices[0].message.content)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Streaming
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
stream = await client.completion(
|
|
178
|
+
model="openai:gpt-4o-mini",
|
|
179
|
+
messages=[{"role": "user", "content": "Tell me a story."}],
|
|
180
|
+
stream=True,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
async for chunk in stream:
|
|
184
|
+
content = chunk.choices[0].delta.content
|
|
185
|
+
if content:
|
|
186
|
+
print(content, end="", flush=True)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Responses API
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
response = await client.response(
|
|
193
|
+
model="openai:gpt-4o-mini",
|
|
194
|
+
input="Summarize this in one sentence.",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
print(response.output_text)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Embeddings
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
result = await client.embedding(
|
|
204
|
+
model="openai:text-embedding-3-small",
|
|
205
|
+
input="Hello world",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
print(result.data[0].embedding)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Listing Models
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
models = await client.list_models()
|
|
215
|
+
for model in models:
|
|
216
|
+
print(model.id)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Error Handling
|
|
220
|
+
|
|
221
|
+
In platform mode, HTTP errors are mapped to typed exceptions:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from otari import OtariClient, AuthenticationError, RateLimitError
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
response = await client.completion(
|
|
228
|
+
model="openai:gpt-4o-mini",
|
|
229
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
230
|
+
)
|
|
231
|
+
except AuthenticationError as e:
|
|
232
|
+
print(f"Invalid credentials: {e.message}")
|
|
233
|
+
except RateLimitError as e:
|
|
234
|
+
print(f"Rate limited, retry after: {e.retry_after}")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
| HTTP Status | Error Class | Description |
|
|
238
|
+
|------------|-------------|-------------|
|
|
239
|
+
| 400 (capability) | `UnsupportedCapabilityError` | Selected provider does not support the requested capability |
|
|
240
|
+
| 401, 403 | `AuthenticationError` | Invalid or missing credentials |
|
|
241
|
+
| 402 | `InsufficientFundsError` | Budget or credits exhausted |
|
|
242
|
+
| 404 | `ModelNotFoundError` | Model not found or unavailable |
|
|
243
|
+
| 429 | `RateLimitError` | Rate limit exceeded (includes `retry_after`) |
|
|
244
|
+
| 502 | `UpstreamProviderError` | Upstream provider unreachable |
|
|
245
|
+
| 504 | `GatewayTimeoutError` | Gateway timed out waiting for provider |
|
|
246
|
+
|
|
247
|
+
`UnsupportedCapabilityError` surfaces in both platform and non-platform modes; the other mappings are platform-mode only.
|
|
248
|
+
|
|
249
|
+
### Context Manager
|
|
250
|
+
|
|
251
|
+
The client supports async context manager for automatic cleanup:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
async with OtariClient(api_base="http://localhost:8000") as client:
|
|
255
|
+
response = await client.completion(
|
|
256
|
+
model="openai:gpt-4o-mini",
|
|
257
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
258
|
+
)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Why choose `otari`?
|
|
262
|
+
|
|
263
|
+
- **Simple, unified interface** - Single client for all providers through the gateway, switch models with just a string change
|
|
264
|
+
- **Developer friendly** - Full type hints for better IDE support and clear, actionable error messages
|
|
265
|
+
- **Leverages the OpenAI SDK** - Built on the official OpenAI Python SDK for maximum compatibility
|
|
266
|
+
- **Async-first** - Built on `AsyncOpenAI` for modern async Python applications
|
|
267
|
+
- **Stays framework-agnostic** so it can be used across different projects and use cases
|
|
268
|
+
- **Battle-tested** - Powers our own production tools ([any-agent](https://github.com/mozilla-ai/any-agent))
|
|
269
|
+
|
|
270
|
+
## Development
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# Create a virtual environment
|
|
274
|
+
python -m venv .venv
|
|
275
|
+
source .venv/bin/activate
|
|
276
|
+
|
|
277
|
+
# Install with dev dependencies
|
|
278
|
+
pip install -e ".[dev]"
|
|
279
|
+
|
|
280
|
+
# Run unit tests
|
|
281
|
+
pytest tests/
|
|
282
|
+
|
|
283
|
+
# Lint
|
|
284
|
+
ruff check src/ tests/
|
|
285
|
+
|
|
286
|
+
# Type-check
|
|
287
|
+
mypy src/
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Documentation
|
|
291
|
+
|
|
292
|
+
- **[Full Documentation](https://mozilla-ai.github.io/otari/)** - Complete guides and API reference
|
|
293
|
+
- **[Supported Providers](https://mozilla-ai.github.io/otari/providers/)** - List of all supported LLM providers
|
|
294
|
+
- **[Gateway Documentation](https://mozilla-ai.github.io/otari/gateway/overview/)** - Gateway setup and deployment
|
|
295
|
+
- **[TypeScript SDK](https://github.com/mozilla-ai/otari-sdk-ts)** - The TypeScript SDK for Node.js applications
|
|
296
|
+
- **[otari Platform (Beta)](https://otari.ai/)** - Hosted control plane for key management, usage tracking, and cost visibility
|
|
297
|
+
|
|
298
|
+
## Contributing
|
|
299
|
+
|
|
300
|
+
We welcome contributions from developers of all skill levels! Please see the [Contributing Guide](https://github.com/mozilla-ai/otari/blob/main/CONTRIBUTING.md) or open an issue to discuss changes.
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
otari-0.0.1/README.md
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<img src="https://raw.githubusercontent.com/mozilla-ai/otari/refs/heads/main/docs/public/images/otari-logo-mark.png" width="20%" alt="Project logo"/>
|
|
4
|
+
</picture>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
|
|
9
|
+
# otari (Python)
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
[](https://pypi.org/project/otari/)
|
|
13
|
+
<a href="https://discord.gg/4gf3zXrQUc">
|
|
14
|
+
<img src="https://img.shields.io/static/v1?label=Chat%20on&message=Discord&color=blue&logo=Discord&style=flat-square" alt="Discord">
|
|
15
|
+
</a>
|
|
16
|
+
|
|
17
|
+
**Python client for [otari-gateway](https://github.com/mozilla-ai/otari).**
|
|
18
|
+
Communicate with any LLM provider through the gateway using a single, typed interface.
|
|
19
|
+
|
|
20
|
+
[TypeScript SDK](https://github.com/mozilla-ai/otari-sdk-ts) | [Documentation](https://mozilla-ai.github.io/otari/) | [Platform (Beta)](https://otari.ai/)
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from otari import OtariClient
|
|
28
|
+
|
|
29
|
+
client = OtariClient(
|
|
30
|
+
api_base="http://localhost:8000",
|
|
31
|
+
platform_token="your-token-here",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
response = await client.completion(
|
|
35
|
+
model="openai:gpt-4o-mini",
|
|
36
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
print(response.choices[0].message.content)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**That's it!** Change the model string to switch between LLM providers through the gateway.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
### Requirements
|
|
47
|
+
|
|
48
|
+
- Python 3.11 or newer
|
|
49
|
+
- A running [otari-gateway](https://mozilla-ai.github.io/otari/gateway/overview/) instance
|
|
50
|
+
|
|
51
|
+
### Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install otari
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Setting Up Credentials
|
|
58
|
+
|
|
59
|
+
Set environment variables for your gateway:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export GATEWAY_API_BASE="http://localhost:8000"
|
|
63
|
+
export GATEWAY_PLATFORM_TOKEN="your-token-here"
|
|
64
|
+
# or for non-platform mode:
|
|
65
|
+
export GATEWAY_API_KEY="your-key-here"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Alternatively, pass credentials directly when creating the client (see [Usage](#usage) examples).
|
|
69
|
+
|
|
70
|
+
## otari-gateway
|
|
71
|
+
|
|
72
|
+
This Python SDK is a client for [otari-gateway](https://github.com/mozilla-ai/otari), an **optional** FastAPI-based proxy server that adds enterprise-grade features on top of the core library:
|
|
73
|
+
|
|
74
|
+
- **Budget Management** - Enforce spending limits with automatic daily, weekly, or monthly resets
|
|
75
|
+
- **API Key Management** - Issue, revoke, and monitor virtual API keys without exposing provider credentials
|
|
76
|
+
- **Usage Analytics** - Track every request with full token counts, costs, and metadata
|
|
77
|
+
- **Multi-tenant Support** - Manage access and budgets across users and teams
|
|
78
|
+
|
|
79
|
+
The gateway sits between your applications and LLM providers, exposing an OpenAI-compatible API that works with any supported provider.
|
|
80
|
+
|
|
81
|
+
### Quick Start
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
docker run \
|
|
85
|
+
-e GATEWAY_MASTER_KEY="your-secure-master-key" \
|
|
86
|
+
-e OPENAI_API_KEY="your-api-key" \
|
|
87
|
+
-p 8000:8000 \
|
|
88
|
+
ghcr.io/mozilla-ai/otari/gateway:latest
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> **Note:** You can use a specific release version instead of `latest` (e.g., `1.2.0`). See [available versions](https://github.com/orgs/mozilla-ai/packages/container/package/otari%2Fgateway).
|
|
92
|
+
|
|
93
|
+
### Managed Platform (Beta)
|
|
94
|
+
|
|
95
|
+
Prefer a hosted experience? The [otari platform](https://otari.ai/) provides a managed control plane for keys, usage tracking, and cost visibility across providers, while still building on the same `otari` interfaces.
|
|
96
|
+
|
|
97
|
+
## Usage
|
|
98
|
+
|
|
99
|
+
### Authentication Modes
|
|
100
|
+
|
|
101
|
+
The client supports two authentication modes, matching the TypeScript SDK:
|
|
102
|
+
|
|
103
|
+
#### Platform Mode (Recommended)
|
|
104
|
+
|
|
105
|
+
Uses a Bearer token in the standard Authorization header:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
client = OtariClient(
|
|
109
|
+
api_base="http://localhost:8000",
|
|
110
|
+
platform_token="tk_your_platform_token",
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Non-Platform Mode
|
|
115
|
+
|
|
116
|
+
Sends the API key via a custom `Otari-Key` header:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
client = OtariClient(
|
|
120
|
+
api_base="http://localhost:8000",
|
|
121
|
+
api_key="your-api-key",
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### Auto-Detection from Environment Variables
|
|
126
|
+
|
|
127
|
+
When no explicit credentials are provided, the client reads from environment variables:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# Uses GATEWAY_API_BASE, GATEWAY_PLATFORM_TOKEN, or GATEWAY_API_KEY
|
|
131
|
+
client = OtariClient()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Chat Completions
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
response = await client.completion(
|
|
138
|
+
model="openai:gpt-4o-mini",
|
|
139
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
print(response.choices[0].message.content)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Streaming
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
stream = await client.completion(
|
|
149
|
+
model="openai:gpt-4o-mini",
|
|
150
|
+
messages=[{"role": "user", "content": "Tell me a story."}],
|
|
151
|
+
stream=True,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
async for chunk in stream:
|
|
155
|
+
content = chunk.choices[0].delta.content
|
|
156
|
+
if content:
|
|
157
|
+
print(content, end="", flush=True)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Responses API
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
response = await client.response(
|
|
164
|
+
model="openai:gpt-4o-mini",
|
|
165
|
+
input="Summarize this in one sentence.",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
print(response.output_text)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Embeddings
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
result = await client.embedding(
|
|
175
|
+
model="openai:text-embedding-3-small",
|
|
176
|
+
input="Hello world",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
print(result.data[0].embedding)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Listing Models
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
models = await client.list_models()
|
|
186
|
+
for model in models:
|
|
187
|
+
print(model.id)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Error Handling
|
|
191
|
+
|
|
192
|
+
In platform mode, HTTP errors are mapped to typed exceptions:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from otari import OtariClient, AuthenticationError, RateLimitError
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
response = await client.completion(
|
|
199
|
+
model="openai:gpt-4o-mini",
|
|
200
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
201
|
+
)
|
|
202
|
+
except AuthenticationError as e:
|
|
203
|
+
print(f"Invalid credentials: {e.message}")
|
|
204
|
+
except RateLimitError as e:
|
|
205
|
+
print(f"Rate limited, retry after: {e.retry_after}")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
| HTTP Status | Error Class | Description |
|
|
209
|
+
|------------|-------------|-------------|
|
|
210
|
+
| 400 (capability) | `UnsupportedCapabilityError` | Selected provider does not support the requested capability |
|
|
211
|
+
| 401, 403 | `AuthenticationError` | Invalid or missing credentials |
|
|
212
|
+
| 402 | `InsufficientFundsError` | Budget or credits exhausted |
|
|
213
|
+
| 404 | `ModelNotFoundError` | Model not found or unavailable |
|
|
214
|
+
| 429 | `RateLimitError` | Rate limit exceeded (includes `retry_after`) |
|
|
215
|
+
| 502 | `UpstreamProviderError` | Upstream provider unreachable |
|
|
216
|
+
| 504 | `GatewayTimeoutError` | Gateway timed out waiting for provider |
|
|
217
|
+
|
|
218
|
+
`UnsupportedCapabilityError` surfaces in both platform and non-platform modes; the other mappings are platform-mode only.
|
|
219
|
+
|
|
220
|
+
### Context Manager
|
|
221
|
+
|
|
222
|
+
The client supports async context manager for automatic cleanup:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
async with OtariClient(api_base="http://localhost:8000") as client:
|
|
226
|
+
response = await client.completion(
|
|
227
|
+
model="openai:gpt-4o-mini",
|
|
228
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
229
|
+
)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Why choose `otari`?
|
|
233
|
+
|
|
234
|
+
- **Simple, unified interface** - Single client for all providers through the gateway, switch models with just a string change
|
|
235
|
+
- **Developer friendly** - Full type hints for better IDE support and clear, actionable error messages
|
|
236
|
+
- **Leverages the OpenAI SDK** - Built on the official OpenAI Python SDK for maximum compatibility
|
|
237
|
+
- **Async-first** - Built on `AsyncOpenAI` for modern async Python applications
|
|
238
|
+
- **Stays framework-agnostic** so it can be used across different projects and use cases
|
|
239
|
+
- **Battle-tested** - Powers our own production tools ([any-agent](https://github.com/mozilla-ai/any-agent))
|
|
240
|
+
|
|
241
|
+
## Development
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
# Create a virtual environment
|
|
245
|
+
python -m venv .venv
|
|
246
|
+
source .venv/bin/activate
|
|
247
|
+
|
|
248
|
+
# Install with dev dependencies
|
|
249
|
+
pip install -e ".[dev]"
|
|
250
|
+
|
|
251
|
+
# Run unit tests
|
|
252
|
+
pytest tests/
|
|
253
|
+
|
|
254
|
+
# Lint
|
|
255
|
+
ruff check src/ tests/
|
|
256
|
+
|
|
257
|
+
# Type-check
|
|
258
|
+
mypy src/
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Documentation
|
|
262
|
+
|
|
263
|
+
- **[Full Documentation](https://mozilla-ai.github.io/otari/)** - Complete guides and API reference
|
|
264
|
+
- **[Supported Providers](https://mozilla-ai.github.io/otari/providers/)** - List of all supported LLM providers
|
|
265
|
+
- **[Gateway Documentation](https://mozilla-ai.github.io/otari/gateway/overview/)** - Gateway setup and deployment
|
|
266
|
+
- **[TypeScript SDK](https://github.com/mozilla-ai/otari-sdk-ts)** - The TypeScript SDK for Node.js applications
|
|
267
|
+
- **[otari Platform (Beta)](https://otari.ai/)** - Hosted control plane for key management, usage tracking, and cost visibility
|
|
268
|
+
|
|
269
|
+
## Contributing
|
|
270
|
+
|
|
271
|
+
We welcome contributions from developers of all skill levels! Please see the [Contributing Guide](https://github.com/mozilla-ai/otari/blob/main/CONTRIBUTING.md) or open an issue to discuss changes.
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|