Python SDK for the Galene.AI platform
Project description
galene-ai
Python SDK for the Galene.AI platform — a single,
typed client covering all 316 REST operations of the API, generated and
hand-crafted from the vendored OpenAPI spec (spec/openapi.json).
- Familiar ergonomics —
client.chat.create(...),client.files.upload(...), OpenAI-SDK-style. - Sync and async —
GaleneandAsyncGaleneshare one core and expose an identical API. - Typed —
msgspecmodels generated from the spec;mypy --strictclean. - Slim — two runtime dependencies:
httpxandmsgspec. - Production-ready — retries with backoff, pluggable auth (API key or username/password with auto-refresh), SSE streaming, cursor pagination.
Install
pip install galene-ai
Requires Python 3.12+.
Quickstart
from galene_ai import Galene
from galene_ai.models._generated import VectorStoreCreate, VectorStoreFileAdd, VectorStoreSearchRequest
client = Galene(api_key="sk-...") # or set GALENE_AI_API_KEY in the environment
# Chat completion
reply = client.chat.create(
model="Galene/LLM",
messages=[{"role": "user", "content": "Hi"}],
)
print(reply.choices[0].message.content)
# Upload a file
uploaded = client.files.upload(b"some file bytes", purpose="user_data", filename="doc.txt")
# Vector stores (RAG)
vector_store = client.vector_stores.create(VectorStoreCreate(name="kb"))
client.vector_stores.add_file(vector_store.id, VectorStoreFileAdd(file_id=uploaded.id))
results = client.vector_stores.search(vector_store.id, VectorStoreSearchRequest(query="pricing"))
client.close()
VectorStoreCreate, VectorStoreFileAdd, and VectorStoreSearchRequest are
generated request models (see
examples/vector_store_rag.py for the full,
runnable version). Galene is also a context manager:
with Galene(api_key="sk-...") as client:
... # client.close() is called automatically
Authentication
The default is a static API key, supplied explicitly or via environment variable:
from galene_ai import Galene
client = Galene(api_key="sk-...") # explicit
client = Galene() # reads GALENE_AI_API_KEY from the environment
Environment variables:
| Variable | Purpose | Default |
|---|---|---|
GALENE_AI_API_KEY |
API key used by the default ApiKeyAuth |
— |
GALENE_AI_BASE_URL |
API base URL | https://api.playground.galene.ai |
Both are optional — the base URL defaults to the public playground, so point it
at your own deployment via the env var or the base_url= argument. The SDK also auto-loads a .env file from the
working directory (real environment variables always win). Copy .env.example
to .env and fill it in:
cp .env.example .env # then set GALENE_AI_API_KEY / GALENE_AI_BASE_URL
For username/password login with transparent token refresh, pass a
SessionAuth instance as auth=:
from galene_ai import Galene, SessionAuth
client = Galene(auth=SessionAuth(username="me@example.com", password="hunter2"))
SessionAuth logs in via POST /login on the first request, attaches the
resulting access token to every request, and — on a 401 — transparently
calls POST /refresh-token and retries once. Auth is pluggable: any
httpx.Auth subclass can be passed as auth=.
Async usage
AsyncGalene mirrors Galene method-for-method:
import asyncio
from galene_ai import AsyncGalene
async def main() -> None:
async with AsyncGalene(api_key="sk-...") as client:
reply = await client.chat.create(
model="Galene/LLM",
messages=[{"role": "user", "content": "Hi"}],
)
print(reply.choices[0].message.content)
asyncio.run(main())
Streaming
Pass stream=True to chat.create (or responses.create) to get a
Stream[dict] (or AsyncStream[dict]) of raw SSE chunks — the spec defines no
fixed chunk schema, so chunks decode as plain dicts in the standard OpenAI
delta shape:
with client.chat.create(
model="Galene/LLM",
messages=[{"role": "user", "content": "Count to five."}],
stream=True,
) as stream:
for chunk in stream:
delta = (chunk.get("choices") or [{}])[0].get("delta") or {}
print(delta.get("content") or "", end="", flush=True)
The async equivalent uses async for chunk in stream: inside async with.
Pagination
List methods on cursor-paginated namespaces (files, vector_stores, ...)
return a CursorPage[T] (or AsyncCursorPage[T]) with .data, .has_more,
and .auto_paging_iter():
for vector_store in client.vector_stores.list().auto_paging_iter():
print(vector_store.id, vector_store.name)
Async (async for on an AsyncCursorPage already walks every page — no
separate auto_paging_iter() call needed):
async for vector_store in await client.vector_stores.list():
print(vector_store.id)
Namespaces that use offset pagination (e.g. observability) instead expose
plain limit/offset keyword arguments and return a list[...] directly.
Error handling
Every error raised by the SDK subclasses GaleneError. HTTP error responses
raise a status-specific subclass of APIStatusError, mirroring the OpenAI SDK
hierarchy:
from galene_ai import GaleneError, NotFoundError
try:
client.files.retrieve("file-does-not-exist")
except NotFoundError as e:
print(e.status_code, e.request_id, e.body)
except GaleneError as e:
print("Galene API error:", e)
GaleneError
├─ APIConnectionError (network failure)
│ └─ APITimeoutError
└─ APIStatusError (.status_code, .request_id, .response, .body)
├─ BadRequestError 400
├─ AuthenticationError 401
├─ PermissionDeniedError 403
├─ NotFoundError 404
├─ ConflictError 409
├─ UnprocessableEntityError 422
├─ RateLimitError 429
└─ InternalServerError 5xx
Connection failures and 408/409/429/500/502/503/504 responses are retried
automatically with exponential backoff (max_retries=2 by default,
configurable via Galene(max_retries=...)); non-idempotent generation
requests are never retried on a read timeout, since a timed-out generation
keeps running server-side.
Namespaces
Every operation is reachable as client.<namespace>.<method>(...)
(async: AsyncGalene, identical shape). Admin operations nest under
client.admin.<namespace>.<method>(...).
OpenAI-compatible surface (30 ops):
| Namespace | Ops | Namespace | Ops |
|---|---|---|---|
chat |
2 | files |
6 |
responses |
2 | vector_stores |
11 |
embeddings |
2 | audio |
4 |
models |
2 | moderations |
1 |
Platform surface (212 ops):
| Namespace | Ops | Namespace | Ops | Namespace | Ops |
|---|---|---|---|---|---|
agents |
46 | attachments |
10 | api_keys |
6 |
observability |
16 | conversations |
10 | groups |
6 |
tickets |
14 | shield |
9 | notifications |
5 |
users |
13 | mcp_servers |
8 | roles |
4 |
auth |
12 | health |
2 | ||
kb_sync |
12 | organizations |
2 | ||
tts |
12 | release_notes |
2 | ||
database_connectors |
11 | changelog |
1 | ||
kb_connectors |
11 |
Admin surface, nested under client.admin.* (74 ops):
| Namespace | Ops | Namespace | Ops |
|---|---|---|---|
admin.org_config |
33 | admin.tickets |
7 |
admin.global_config |
25 | admin.management |
5 |
admin.deployment |
1 | ||
admin.frontend |
1 | ||
admin.search |
1 | ||
admin.user_api_keys |
1 |
For example:
client.agents.list()
client.observability.list_traces(status="running")
client.admin.org_config.set_favicon(org_uuid, favicon_png_bytes)
observability exposes trace/observation listing, read-audit trails, CSV
exports, and aggregate metrics directly (no further sub-resource nesting);
conversations exposes message- and attachment-related helpers the same way.
Endpoints with mode-prefixed variants (chat, responses, embeddings,
models; mode ∈ direct/shield/appliance_shield) expose a
create_with_mode(mode, ...) (or list_with_mode(mode)) sibling method
alongside the plain /v1/... one.
The full, machine-readable operation → namespace/method mapping lives in
spec/operations.json.
Typing note: a handful of endpoints return dict
spec/openapi.json leaves the 200 response schema empty ({}, no $ref) for
a number of operations — most notably the entire agents namespace (46 ops)
and attachments namespace (10 ops), plus a few others scattered across the
SDK (models.list, audio.voices, the observability .csv export
endpoints, some vector_stores delete/batch responses). datamodel-code-generator
has nothing to generate a model from in those cases, so the SDK decodes the
response as a plain dict[str, Any] rather than guessing a shape that might
not match production.
To upgrade one of these to a typed msgspec.Struct:
- Run
scripts/probe_shapes.pyagainst a reachable Galene instance (GALENE_AI_API_KEY=... python scripts/probe_shapes.py). It only issues GET requests — it never mutates data — and writes the observed JSON shapes toprobe_output/<operationId>.json, plus a summary table to stdout. - Add a hand-written
msgspec.Structfor the observed shape tosrc/galene_ai/models/_extra.py(see that module's docstring for the existing pattern used forFile,VectorStore, etc.). - Update the resource method's
cast_to=argument to the new model.
Development
uv sync --extra dev
- Regenerate models (dev-only, output is committed):
uv run python scripts/generate_models.py - Regenerate the operation registry (dev-only, output is committed, drives
the drift test):
uv run python scripts/build_registry.py - Tests:
uv run pytest - Type-check:
uv run mypy - Lint / format:
uv run ruff check ./uv run ruff format .
tests/test_drift.py asserts that the set of operationIds implemented by
resource methods equals the set in spec/operations.json exactly — any API
addition, removal, or rename that isn't reflected in the SDK fails CI.
License
MIT
Project details
Release history Release notifications | RSS feed
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 galene_ai-1.0.0.tar.gz.
File metadata
- Download URL: galene_ai-1.0.0.tar.gz
- Upload date:
- Size: 97.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e96f82f35b76ff3ac65f0a12fe3d216dc6f47a13fc9951d551ee769bf1b698f1
|
|
| MD5 |
194684df7ccfe4c321f141317e46e21e
|
|
| BLAKE2b-256 |
4f2851f410a074c6ab8a4f692e2ffa9cee188da812078f883b5333b6c15a706d
|
File details
Details for the file galene_ai-1.0.0-py3-none-any.whl.
File metadata
- Download URL: galene_ai-1.0.0-py3-none-any.whl
- Upload date:
- Size: 120.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c22ecabf84f1c2f996923ea04222471ffabfd7509651c8ad79cf34d3570d9dab
|
|
| MD5 |
f984d74d43d7c307c37e7a092baa7d6c
|
|
| BLAKE2b-256 |
bad71530fcf5741c9fc6214777b3b2352a537629afb0af573a4479d2e2d26e71
|