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.
Files changed (67) hide show
  1. octoryn_llm-0.1.0/.gitignore +77 -0
  2. octoryn_llm-0.1.0/CHANGELOG.md +38 -0
  3. octoryn_llm-0.1.0/PKG-INFO +318 -0
  4. octoryn_llm-0.1.0/README.md +291 -0
  5. octoryn_llm-0.1.0/examples/00_chat.py +39 -0
  6. octoryn_llm-0.1.0/examples/01_images.py +28 -0
  7. octoryn_llm-0.1.0/examples/02_speech_tts.py +30 -0
  8. octoryn_llm-0.1.0/examples/03_transcribe.py +31 -0
  9. octoryn_llm-0.1.0/examples/04_video.py +30 -0
  10. octoryn_llm-0.1.0/examples/05_realtime.py +28 -0
  11. octoryn_llm-0.1.0/examples/06_embeddings.py +28 -0
  12. octoryn_llm-0.1.0/examples/07_moderations.py +22 -0
  13. octoryn_llm-0.1.0/examples/08_models.py +23 -0
  14. octoryn_llm-0.1.0/examples/09_async.py +26 -0
  15. octoryn_llm-0.1.0/examples/10_openai_dropin.py +29 -0
  16. octoryn_llm-0.1.0/examples/11_oidc.py +41 -0
  17. octoryn_llm-0.1.0/pyproject.toml +44 -0
  18. octoryn_llm-0.1.0/src/octoryn/__init__.py +98 -0
  19. octoryn_llm-0.1.0/src/octoryn/_base_resource.py +18 -0
  20. octoryn_llm-0.1.0/src/octoryn/_client.py +490 -0
  21. octoryn_llm-0.1.0/src/octoryn/_credentials.py +351 -0
  22. octoryn_llm-0.1.0/src/octoryn/_exceptions.py +78 -0
  23. octoryn_llm-0.1.0/src/octoryn/_stream.py +128 -0
  24. octoryn_llm-0.1.0/src/octoryn/_utils.py +22 -0
  25. octoryn_llm-0.1.0/src/octoryn/_version.py +1 -0
  26. octoryn_llm-0.1.0/src/octoryn/openai_compat.py +55 -0
  27. octoryn_llm-0.1.0/src/octoryn/resources/__init__.py +1 -0
  28. octoryn_llm-0.1.0/src/octoryn/resources/audio.py +186 -0
  29. octoryn_llm-0.1.0/src/octoryn/resources/audit.py +69 -0
  30. octoryn_llm-0.1.0/src/octoryn/resources/byok.py +51 -0
  31. octoryn_llm-0.1.0/src/octoryn/resources/chat.py +127 -0
  32. octoryn_llm-0.1.0/src/octoryn/resources/embeddings.py +63 -0
  33. octoryn_llm-0.1.0/src/octoryn/resources/images.py +77 -0
  34. octoryn_llm-0.1.0/src/octoryn/resources/models.py +19 -0
  35. octoryn_llm-0.1.0/src/octoryn/resources/moderations.py +27 -0
  36. octoryn_llm-0.1.0/src/octoryn/resources/realtime.py +125 -0
  37. octoryn_llm-0.1.0/src/octoryn/resources/usage.py +73 -0
  38. octoryn_llm-0.1.0/src/octoryn/resources/videos.py +156 -0
  39. octoryn_llm-0.1.0/src/octoryn/types/__init__.py +54 -0
  40. octoryn_llm-0.1.0/src/octoryn/types/audio.py +57 -0
  41. octoryn_llm-0.1.0/src/octoryn/types/audit.py +39 -0
  42. octoryn_llm-0.1.0/src/octoryn/types/byok.py +26 -0
  43. octoryn_llm-0.1.0/src/octoryn/types/chat.py +62 -0
  44. octoryn_llm-0.1.0/src/octoryn/types/embeddings.py +25 -0
  45. octoryn_llm-0.1.0/src/octoryn/types/images.py +35 -0
  46. octoryn_llm-0.1.0/src/octoryn/types/realtime.py +59 -0
  47. octoryn_llm-0.1.0/src/octoryn/types/shared.py +20 -0
  48. octoryn_llm-0.1.0/src/octoryn/types/usage.py +31 -0
  49. octoryn_llm-0.1.0/src/octoryn/types/videos.py +20 -0
  50. octoryn_llm-0.1.0/tests/__init__.py +0 -0
  51. octoryn_llm-0.1.0/tests/conftest.py +7 -0
  52. octoryn_llm-0.1.0/tests/test_audio.py +119 -0
  53. octoryn_llm-0.1.0/tests/test_audit.py +63 -0
  54. octoryn_llm-0.1.0/tests/test_byok.py +53 -0
  55. octoryn_llm-0.1.0/tests/test_chat_non_stream.py +90 -0
  56. octoryn_llm-0.1.0/tests/test_chat_stream.py +60 -0
  57. octoryn_llm-0.1.0/tests/test_chat_stream_async.py +32 -0
  58. octoryn_llm-0.1.0/tests/test_client.py +49 -0
  59. octoryn_llm-0.1.0/tests/test_credentials.py +225 -0
  60. octoryn_llm-0.1.0/tests/test_embeddings.py +74 -0
  61. octoryn_llm-0.1.0/tests/test_errors.py +151 -0
  62. octoryn_llm-0.1.0/tests/test_images.py +80 -0
  63. octoryn_llm-0.1.0/tests/test_openai_drop_in.py +162 -0
  64. octoryn_llm-0.1.0/tests/test_openai_dropin_modalities.py +219 -0
  65. octoryn_llm-0.1.0/tests/test_realtime.py +135 -0
  66. octoryn_llm-0.1.0/tests/test_videos.py +89 -0
  67. 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.