Skip to main content

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 ergonomicsclient.chat.create(...), client.files.upload(...), OpenAI-SDK-style.
  • Sync and asyncGalene and AsyncGalene share one core and expose an identical API.
  • Typedmsgspec models generated from the spec; mypy --strict clean.
  • Slim — two runtime dependencies: httpx and msgspec.
  • 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:

  1. Run scripts/probe_shapes.py against 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 to probe_output/<operationId>.json, plus a summary table to stdout.
  2. Add a hand-written msgspec.Struct for the observed shape to src/galene_ai/models/_extra.py (see that module's docstring for the existing pattern used for File, VectorStore, etc.).
  3. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

galene_ai-1.0.0.tar.gz (97.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

galene_ai-1.0.0-py3-none-any.whl (120.4 kB view details)

Uploaded Python 3

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

Hashes for galene_ai-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e96f82f35b76ff3ac65f0a12fe3d216dc6f47a13fc9951d551ee769bf1b698f1
MD5 194684df7ccfe4c321f141317e46e21e
BLAKE2b-256 4f2851f410a074c6ab8a4f692e2ffa9cee188da812078f883b5333b6c15a706d

See more details on using hashes here.

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

Hashes for galene_ai-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c22ecabf84f1c2f996923ea04222471ffabfd7509651c8ad79cf34d3570d9dab
MD5 f984d74d43d7c307c37e7a092baa7d6c
BLAKE2b-256 bad71530fcf5741c9fc6214777b3b2352a537629afb0af573a4479d2e2d26e71

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page