octoryn-llm 0.1.0__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.
- octoryn_llm-0.1.0/.gitignore +77 -0
- octoryn_llm-0.1.0/CHANGELOG.md +38 -0
- octoryn_llm-0.1.0/PKG-INFO +318 -0
- octoryn_llm-0.1.0/README.md +291 -0
- octoryn_llm-0.1.0/examples/00_chat.py +39 -0
- octoryn_llm-0.1.0/examples/01_images.py +28 -0
- octoryn_llm-0.1.0/examples/02_speech_tts.py +30 -0
- octoryn_llm-0.1.0/examples/03_transcribe.py +31 -0
- octoryn_llm-0.1.0/examples/04_video.py +30 -0
- octoryn_llm-0.1.0/examples/05_realtime.py +28 -0
- octoryn_llm-0.1.0/examples/06_embeddings.py +28 -0
- octoryn_llm-0.1.0/examples/07_moderations.py +22 -0
- octoryn_llm-0.1.0/examples/08_models.py +23 -0
- octoryn_llm-0.1.0/examples/09_async.py +26 -0
- octoryn_llm-0.1.0/examples/10_openai_dropin.py +29 -0
- octoryn_llm-0.1.0/examples/11_oidc.py +41 -0
- octoryn_llm-0.1.0/pyproject.toml +44 -0
- octoryn_llm-0.1.0/src/octoryn/__init__.py +98 -0
- octoryn_llm-0.1.0/src/octoryn/_base_resource.py +18 -0
- octoryn_llm-0.1.0/src/octoryn/_client.py +490 -0
- octoryn_llm-0.1.0/src/octoryn/_credentials.py +351 -0
- octoryn_llm-0.1.0/src/octoryn/_exceptions.py +78 -0
- octoryn_llm-0.1.0/src/octoryn/_stream.py +128 -0
- octoryn_llm-0.1.0/src/octoryn/_utils.py +22 -0
- octoryn_llm-0.1.0/src/octoryn/_version.py +1 -0
- octoryn_llm-0.1.0/src/octoryn/openai_compat.py +55 -0
- octoryn_llm-0.1.0/src/octoryn/resources/__init__.py +1 -0
- octoryn_llm-0.1.0/src/octoryn/resources/audio.py +186 -0
- octoryn_llm-0.1.0/src/octoryn/resources/audit.py +69 -0
- octoryn_llm-0.1.0/src/octoryn/resources/byok.py +51 -0
- octoryn_llm-0.1.0/src/octoryn/resources/chat.py +127 -0
- octoryn_llm-0.1.0/src/octoryn/resources/embeddings.py +63 -0
- octoryn_llm-0.1.0/src/octoryn/resources/images.py +77 -0
- octoryn_llm-0.1.0/src/octoryn/resources/models.py +19 -0
- octoryn_llm-0.1.0/src/octoryn/resources/moderations.py +27 -0
- octoryn_llm-0.1.0/src/octoryn/resources/realtime.py +125 -0
- octoryn_llm-0.1.0/src/octoryn/resources/usage.py +73 -0
- octoryn_llm-0.1.0/src/octoryn/resources/videos.py +156 -0
- octoryn_llm-0.1.0/src/octoryn/types/__init__.py +54 -0
- octoryn_llm-0.1.0/src/octoryn/types/audio.py +57 -0
- octoryn_llm-0.1.0/src/octoryn/types/audit.py +39 -0
- octoryn_llm-0.1.0/src/octoryn/types/byok.py +26 -0
- octoryn_llm-0.1.0/src/octoryn/types/chat.py +62 -0
- octoryn_llm-0.1.0/src/octoryn/types/embeddings.py +25 -0
- octoryn_llm-0.1.0/src/octoryn/types/images.py +35 -0
- octoryn_llm-0.1.0/src/octoryn/types/realtime.py +59 -0
- octoryn_llm-0.1.0/src/octoryn/types/shared.py +20 -0
- octoryn_llm-0.1.0/src/octoryn/types/usage.py +31 -0
- octoryn_llm-0.1.0/src/octoryn/types/videos.py +20 -0
- octoryn_llm-0.1.0/tests/__init__.py +0 -0
- octoryn_llm-0.1.0/tests/conftest.py +7 -0
- octoryn_llm-0.1.0/tests/test_audio.py +119 -0
- octoryn_llm-0.1.0/tests/test_audit.py +63 -0
- octoryn_llm-0.1.0/tests/test_byok.py +53 -0
- octoryn_llm-0.1.0/tests/test_chat_non_stream.py +90 -0
- octoryn_llm-0.1.0/tests/test_chat_stream.py +60 -0
- octoryn_llm-0.1.0/tests/test_chat_stream_async.py +32 -0
- octoryn_llm-0.1.0/tests/test_client.py +49 -0
- octoryn_llm-0.1.0/tests/test_credentials.py +225 -0
- octoryn_llm-0.1.0/tests/test_embeddings.py +74 -0
- octoryn_llm-0.1.0/tests/test_errors.py +151 -0
- octoryn_llm-0.1.0/tests/test_images.py +80 -0
- octoryn_llm-0.1.0/tests/test_openai_drop_in.py +162 -0
- octoryn_llm-0.1.0/tests/test_openai_dropin_modalities.py +219 -0
- octoryn_llm-0.1.0/tests/test_realtime.py +135 -0
- octoryn_llm-0.1.0/tests/test_videos.py +89 -0
- octoryn_llm-0.1.0/uv.lock +579 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
.venv/
|
|
6
|
+
.eggs/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
build/
|
|
9
|
+
dist/
|
|
10
|
+
|
|
11
|
+
# Tooling caches
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
|
|
16
|
+
# Artifacts and checkpoints
|
|
17
|
+
artifacts/
|
|
18
|
+
checkpoints/
|
|
19
|
+
outputs/
|
|
20
|
+
models/
|
|
21
|
+
datasets/
|
|
22
|
+
logs/
|
|
23
|
+
|
|
24
|
+
# System
|
|
25
|
+
.DS_Store
|
|
26
|
+
|
|
27
|
+
# Octoryn LLM (P0-01)
|
|
28
|
+
.env
|
|
29
|
+
.env.*
|
|
30
|
+
!.env.example
|
|
31
|
+
local-artifacts/
|
|
32
|
+
releases/local-runs/
|
|
33
|
+
|
|
34
|
+
# JS / Web
|
|
35
|
+
node_modules/
|
|
36
|
+
apps/*/dist/
|
|
37
|
+
apps/*/build/
|
|
38
|
+
apps/*/.next/
|
|
39
|
+
packages/*/dist/
|
|
40
|
+
|
|
41
|
+
# uv (lockfile is committed if generated; uncomment to ignore)
|
|
42
|
+
# uv.lock
|
|
43
|
+
|
|
44
|
+
# Editor / agent scratch
|
|
45
|
+
.claude/
|
|
46
|
+
.idea/
|
|
47
|
+
.vscode/
|
|
48
|
+
*.tsbuildinfo
|
|
49
|
+
|
|
50
|
+
# Terraform: providers and any state-bearing artifacts NEVER go to remote
|
|
51
|
+
**/.terraform/
|
|
52
|
+
*.tfplan
|
|
53
|
+
*.tfstate
|
|
54
|
+
*.tfstate.*
|
|
55
|
+
crash.log
|
|
56
|
+
crash.*.log
|
|
57
|
+
override.tf
|
|
58
|
+
override.tf.json
|
|
59
|
+
*_override.tf
|
|
60
|
+
*_override.tf.json
|
|
61
|
+
|
|
62
|
+
# Ad-hoc benchmark outputs
|
|
63
|
+
scripts/bench/reports/
|
|
64
|
+
|
|
65
|
+
# Local config / scratch
|
|
66
|
+
*.local
|
|
67
|
+
.history/
|
|
68
|
+
.cache/
|
|
69
|
+
|
|
70
|
+
# OS noise
|
|
71
|
+
Thumbs.db
|
|
72
|
+
Desktop.ini
|
|
73
|
+
|
|
74
|
+
# Never commit npm tokens or git credential blobs
|
|
75
|
+
.npmrc
|
|
76
|
+
!packages/sdk-ts/.npmrc.example
|
|
77
|
+
.netrc
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `octoryn-llm` (Python SDK) will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-05-03
|
|
9
|
+
|
|
10
|
+
First public release of the Octoryn Python SDK.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Multimodal API coverage:
|
|
14
|
+
- `chat.completions.create` (sync + streaming)
|
|
15
|
+
- `images.generate`, `images.edit`, `images.variations`
|
|
16
|
+
- `audio.speech.create`, `audio.transcriptions.create`, `audio.translations.create`
|
|
17
|
+
- `videos.generate` and `videos.generate_and_wait` polling helper
|
|
18
|
+
- `realtime.sessions.create` for ephemeral realtime tokens
|
|
19
|
+
- `embeddings.create`
|
|
20
|
+
- `moderations.create`
|
|
21
|
+
- `models.list` / `models.retrieve`
|
|
22
|
+
- `audit.events.list`, `usage.summary`, `byok.keys.*`
|
|
23
|
+
- Dual-mode authentication:
|
|
24
|
+
- Static API key (`OctorynLLM(api_key=...)`)
|
|
25
|
+
- `OctorynOidcCredential` for federated workload identity (auto-refresh, exchange against `/v1/auth/exchange`)
|
|
26
|
+
- OpenAI drop-in compatibility:
|
|
27
|
+
- Use the upstream `openai` package against `https://api.octopusos.dev/v1`
|
|
28
|
+
- `from octoryn.openai_compat import OpenAI` re-export with sensible defaults
|
|
29
|
+
- `examples/` directory covering chat, streaming, images, audio, video, embeddings, OIDC, OpenAI drop-in
|
|
30
|
+
- Full README rewrite with quickstart, auth modes, and migration notes
|
|
31
|
+
- `X-Octoryn-Client: py/<version>` header now driven by `octoryn._version.__version__`
|
|
32
|
+
|
|
33
|
+
### Notes
|
|
34
|
+
- After tagging `sdk-ts-v0.1.0`, switch `apps/cli` and `apps/web-console` to `"@octoryn/sdk": "^0.1.0"` (currently consumed via `file:` link).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
Copyright (c) 2026 Octopus Core Pty Ltd (ACN 696 931 236). All rights reserved.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: octoryn-llm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for Octoryn LLM (Octopus Core Pty Ltd)
|
|
5
|
+
Project-URL: Homepage, https://octopusos.dev
|
|
6
|
+
Author: Octopus Core Pty Ltd
|
|
7
|
+
License: Proprietary
|
|
8
|
+
Keywords: audit,byok,llm,octoryn,openai-compatible
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: Other/Proprietary License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: httpx>=0.27
|
|
19
|
+
Requires-Dist: pydantic>=2.9
|
|
20
|
+
Requires-Dist: typing-extensions>=4.12
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: openai>=1.50; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
25
|
+
Requires-Dist: respx; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# octoryn-llm
|
|
29
|
+
|
|
30
|
+
**Octoryn LLM Python SDK — OpenAI-compatible client for Octoryn Gateway.**
|
|
31
|
+
|
|
32
|
+
`octoryn-llm` ships an `Octoryn` / `AsyncOctoryn` client that talks to the
|
|
33
|
+
Octoryn Gateway (default: `https://api.octopusos.dev/v1`). The chat / images /
|
|
34
|
+
audio / embeddings / moderations surface is byte-for-byte OpenAI-compatible,
|
|
35
|
+
plus first-class extensions for audit, usage, and BYOK.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install octoryn-llm
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Python 3.10+.
|
|
44
|
+
|
|
45
|
+
## Quickstart
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from octoryn import Octoryn
|
|
49
|
+
|
|
50
|
+
client = Octoryn(api_key="oct_live_...") # or set OCTORYN_API_KEY
|
|
51
|
+
out = client.chat.completions.create(
|
|
52
|
+
model="gpt-4o-mini",
|
|
53
|
+
messages=[{"role": "user", "content": "Hello, Octoryn"}],
|
|
54
|
+
)
|
|
55
|
+
print(out.choices[0].message.content)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Authentication
|
|
59
|
+
|
|
60
|
+
### API key (recommended)
|
|
61
|
+
|
|
62
|
+
Get a key in the web console at <https://app.octopusos.dev>, then set
|
|
63
|
+
`OCTORYN_API_KEY` in your environment:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import os
|
|
67
|
+
from octoryn import Octoryn
|
|
68
|
+
|
|
69
|
+
client = Octoryn(api_key=os.environ["OCTORYN_API_KEY"])
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### OIDC (refresh-token)
|
|
73
|
+
|
|
74
|
+
For human / SSO flows the SDK ships an OAuth2 refresh-token credential. It
|
|
75
|
+
auto-refreshes the short-lived access token (single-flight, thread- and
|
|
76
|
+
asyncio-safe) and lets you persist the new token-set with `on_refresh`:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import json, pathlib
|
|
80
|
+
from octoryn import Octoryn, OctorynOidcCredential
|
|
81
|
+
|
|
82
|
+
TOKEN_PATH = pathlib.Path.home() / ".octoryn" / "tokens.json"
|
|
83
|
+
|
|
84
|
+
def persist(token_set: dict) -> None:
|
|
85
|
+
TOKEN_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
TOKEN_PATH.write_text(json.dumps(token_set))
|
|
87
|
+
|
|
88
|
+
cached = json.loads(TOKEN_PATH.read_text()) if TOKEN_PATH.exists() else {}
|
|
89
|
+
|
|
90
|
+
cred = OctorynOidcCredential(
|
|
91
|
+
identity_url="https://identity.octopusos.dev",
|
|
92
|
+
client_id="cli",
|
|
93
|
+
refresh_token=cached.get("refresh_token", "rt_..."),
|
|
94
|
+
access_token=cached.get("access_token"),
|
|
95
|
+
expires_at=cached.get("expires_at"),
|
|
96
|
+
on_refresh=persist,
|
|
97
|
+
)
|
|
98
|
+
client = Octoryn(credential=cred)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Pass either `api_key=` *or* `credential=`, never both.
|
|
102
|
+
|
|
103
|
+
## Use the official `openai` SDK (drop-in)
|
|
104
|
+
|
|
105
|
+
The Octoryn Gateway is bytewise OpenAI-compatible for chat, images, audio,
|
|
106
|
+
embeddings, and moderations. Point the official `openai` SDK at the Octoryn
|
|
107
|
+
base URL and existing code keeps working:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from openai import OpenAI
|
|
111
|
+
|
|
112
|
+
client = OpenAI(
|
|
113
|
+
base_url="https://api.octopusos.dev/v1",
|
|
114
|
+
api_key="oct_live_...",
|
|
115
|
+
)
|
|
116
|
+
client.chat.completions.create(model="gpt-4o-mini", messages=[...])
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If you'd rather import from `octoryn` while keeping the OpenAI surface, use
|
|
120
|
+
the bundled alias:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from octoryn.openai_compat import OpenAI # subclass of Octoryn
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Modality cookbook
|
|
127
|
+
|
|
128
|
+
### Chat
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
out = client.chat.completions.create(
|
|
132
|
+
model="gpt-4o-mini",
|
|
133
|
+
messages=[{"role": "user", "content": "Summarize the OSI model in one sentence."}],
|
|
134
|
+
)
|
|
135
|
+
print(out.choices[0].message.content)
|
|
136
|
+
|
|
137
|
+
# Stream
|
|
138
|
+
stream = client.chat.completions.create(
|
|
139
|
+
model="gpt-4o-mini",
|
|
140
|
+
messages=[{"role": "user", "content": "Count 1..5"}],
|
|
141
|
+
stream=True,
|
|
142
|
+
)
|
|
143
|
+
for chunk in stream:
|
|
144
|
+
delta = chunk.choices[0].delta.content or ""
|
|
145
|
+
print(delta, end="", flush=True)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Images
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
res = client.images.generate(
|
|
152
|
+
model="dall-e-3",
|
|
153
|
+
prompt="a small octopus drawing a vector logo, flat illustration",
|
|
154
|
+
size="1024x1024",
|
|
155
|
+
)
|
|
156
|
+
print(res.data[0].url)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Speech (TTS)
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
speech = client.audio.speech.create(
|
|
163
|
+
model="tts-1",
|
|
164
|
+
voice="alloy",
|
|
165
|
+
input="Octoryn is online.",
|
|
166
|
+
response_format="mp3",
|
|
167
|
+
)
|
|
168
|
+
with open("out.mp3", "wb") as f:
|
|
169
|
+
f.write(speech.content)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Transcription (STT)
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
tx = client.audio.transcriptions.create(
|
|
176
|
+
file="meeting.mp3", # path, bytes, or file-like
|
|
177
|
+
model="whisper-1",
|
|
178
|
+
language="en",
|
|
179
|
+
)
|
|
180
|
+
print(tx.text)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Video
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
job = client.videos.generate_and_wait(
|
|
187
|
+
model="veo-3",
|
|
188
|
+
prompt="a slow-motion octopus opening a jar underwater",
|
|
189
|
+
duration_seconds=5,
|
|
190
|
+
resolution="720p",
|
|
191
|
+
aspect_ratio="16:9",
|
|
192
|
+
)
|
|
193
|
+
print(job.status, job.video_url)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Realtime
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
session = client.realtime.sessions.create(
|
|
200
|
+
provider="openai",
|
|
201
|
+
model="gpt-4o-realtime-preview",
|
|
202
|
+
voice="verse",
|
|
203
|
+
)
|
|
204
|
+
print(session.ws_url, session.audio_sample_rate)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The SDK does not bundle a WebRTC/WebSocket client. Connect to `ws_url` with
|
|
208
|
+
your transport of choice (e.g. `websockets`, `aiortc`) and exchange events
|
|
209
|
+
following the underlying provider's realtime protocol:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
# import websockets, asyncio, json
|
|
213
|
+
# async with websockets.connect(session.ws_url) as ws:
|
|
214
|
+
# await ws.send(json.dumps({"type": "input_audio_buffer.append", "audio": "..."}))
|
|
215
|
+
# async for msg in ws: print(msg)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Embeddings
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
emb = client.embeddings.create(
|
|
222
|
+
model="text-embedding-3-small",
|
|
223
|
+
input=["hello", "world"],
|
|
224
|
+
)
|
|
225
|
+
print(len(emb.data), len(emb.data[0].embedding))
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Moderations
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
mod = client.moderations.create(input="I love clean code.")
|
|
232
|
+
print(mod["results"][0]["flagged"])
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Models
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
print(client.models.list())
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Async
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
import asyncio
|
|
245
|
+
from octoryn import AsyncOctoryn
|
|
246
|
+
|
|
247
|
+
async def main() -> None:
|
|
248
|
+
async with AsyncOctoryn(api_key="oct_live_...") as client:
|
|
249
|
+
out = await client.chat.completions.create(
|
|
250
|
+
model="gpt-4o-mini",
|
|
251
|
+
messages=[{"role": "user", "content": "ping"}],
|
|
252
|
+
)
|
|
253
|
+
print(out.choices[0].message.content)
|
|
254
|
+
|
|
255
|
+
asyncio.run(main())
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Errors & retries
|
|
259
|
+
|
|
260
|
+
| Class | When |
|
|
261
|
+
| ------------------------------ | -------------------------------------------------- |
|
|
262
|
+
| `OctorynAuthError` | `401` / `403` — bad / missing / unauthorized key |
|
|
263
|
+
| `OctorynRateLimitedError` | `429 rate_limited` — exposes `retry_after_s` |
|
|
264
|
+
| `OctorynQuotaExceededError` | `429 quota_exceeded` — exposes `retry_at` |
|
|
265
|
+
| `OctorynKsiBlockedError` | `422 ksi_blocked` — exposes `reasons`, `channel` |
|
|
266
|
+
| `OctorynUpstreamError` | `503 upstream_*` — exposes `upstream` |
|
|
267
|
+
| `OctorynAPIStatusError` | Any other non-2xx HTTP |
|
|
268
|
+
| `OctorynStreamInterrupted` | SSE stream terminated mid-flight |
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
import time
|
|
272
|
+
from octoryn import OctorynRateLimitedError
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
client.chat.completions.create(model="gpt-4o-mini", messages=[...])
|
|
276
|
+
except OctorynRateLimitedError as e:
|
|
277
|
+
time.sleep(e.retry_after_s or 1.0)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The client retries `5xx` and transport errors up to `max_retries=3` with
|
|
281
|
+
exponential backoff (factor `0.5s`). Override per-client with
|
|
282
|
+
`Octoryn(api_key=..., max_retries=5)`.
|
|
283
|
+
|
|
284
|
+
## BYOK
|
|
285
|
+
|
|
286
|
+
Register an upstream credential the gateway should use on your behalf:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
created = client.byok.create(
|
|
290
|
+
upstream="openai", # openai | anthropic | openrouter | ...
|
|
291
|
+
api_key="sk-...", # never re-displayed by the gateway
|
|
292
|
+
label="prod",
|
|
293
|
+
)
|
|
294
|
+
print(created.id)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Audit & Usage
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
runs = client.audit.list(limit=20)
|
|
301
|
+
detail = client.audit.get(run_id=runs.items[0].run_id)
|
|
302
|
+
verified = client.audit.verify(run_id=detail.run_id)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
summary = client.usage.get()
|
|
307
|
+
records = client.usage.records(model="gpt-4o-mini", limit=50)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Versioning & support
|
|
311
|
+
|
|
312
|
+
Octoryn follows semver from `1.0.0`. Pre-1.0 minor bumps may break.
|
|
313
|
+
See `CHANGELOG.md` (added in Phase 7) and <https://octopusos.dev>.
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
Proprietary © Octopus Core Pty Ltd (ACN 696 931 236). Octoryn™ is a trademark
|
|
318
|
+
of Octopus Core Pty Ltd.
|