Metadata-Version: 2.5
Name: elektric-ai
Version: 0.7.2
Summary: Native Python client for Elektric's automatically routed inference API
Project-URL: Repository, https://github.com/realVasileios/electric
License-Expression: MIT
License-File: LICENSE
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: websockets<16,>=13
Provides-Extra: dev
Requires-Dist: anthropic<1.7,>=1.6; extra == 'dev'
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: google-genai<2.25,>=2.24; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: openai<3.15,>=3.14; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Description-Content-Type: text/markdown

# Elektric Python SDK

Canonical documentation: [Quickstart](../../docs/getting-started.md) · [AI coding agents](../../docs/guides/ai-coding-agents.md) · [llms.txt](../../public/llms.txt)

One provider-neutral Python interface for chat, media, Web, tools, state, jobs, and realtime.

The distribution is `elektric-ai`; the Python import remains `elektric`.

This is the official [`elektric-ai` distribution on PyPI](https://pypi.org/project/elektric-ai/). Do not substitute a similarly named distribution.

## Install and configure

```bash
python -m pip install elektric-ai
```

```python
from elektric import Elektric

with Elektric() as client:
    response = client.chat(
        message="Explain quantum computing simply.",
        user_id="user-123",
        conversation_id="thread-123",
    )
    print(response.message, response.request_id)
```

The production URL is built in; `base_url` is only needed for local or staged deployments. Native chat accepts optional customer-defined `user_id` and `conversation_id`; the `elektric-auto` model is implicit. An optional public `model` is accepted; no provider key or provider selection is needed. AI executions are never automatically retried.

## Observe core (pre-release)

The repository contains the server-side Python Observe delivery core and explicit OpenAI adapter.
They are not in the currently published package release. The dedicated Observe token must be
supplied explicitly; Observe never discovers environment variables or reuses an Elektric inference
key.

```python
import os

from elektric.observe import observe

observe(
    token=os.environ["ELEKTRIC_OBSERVE_TOKEN"],
)
```

The production endpoint is built in. A Benchmark-issued token safely selects benchmark content
capture; legacy and metadata-scoped tokens stay metadata-only. For local development, override the
endpoint explicitly:

```python
observe(
    token=os.environ["ELEKTRIC_OBSERVE_TOKEN"],
    endpoint="http://localhost:8787",
)
```

One initialization installs version-gated hooks on certified public OpenAI, Anthropic, and Google
resource methods. Sync and async clients created before or after initialization are covered. The
automatic OpenAI resource hook uses the conservative `openai_compatible` identity because the
resource does not publicly expose its owning client's `base_url`; use an explicit wrapper when exact
OpenAI or OpenRouter identity matters. No global HTTP, socket, source, filesystem, or credential
interception is used. The explicit `observe_openai`, `observe_anthropic`, and `observe_google` APIs
remain available.

OpenAI Python `>=3.14,<3.15` is the certified adapter range (tested with 3.14.1):

```python
from openai import OpenAI
from elektric.observe import observe_openai

openai = observe_openai(
    OpenAI(api_key=os.environ["OPENAI_API_KEY"]),
    observer,
)

response = openai.responses.create(model="gpt-5.6", input="Explain this design.")
```

The same function accepts `AsyncOpenAI`. It observes sync and async
`responses.create(...)` and `chat.completions.create(...)`, including their `stream=True` forms.
Other OpenAI resources and convenience/raw-response/parsed/stream-manager helpers are not observed.
OpenAI requests and credentials remain entirely inside the OpenAI SDK and continue directly to its
configured endpoint.

OpenRouter uses the same adapter with explicit provider identity; no OpenRouter dashboard change is
required:

```python
from openai import OpenAI
from elektric.observe import observe_openai

openrouter = observe_openai(
    OpenAI(
        base_url="https://openrouter.ai/api/v1",
        api_key=os.environ["OPENROUTER_API_KEY"],
    ),
    observer,
    provider="openrouter",
)

completion = openrouter.chat.completions.create(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Explain this design."}],
)
```

The certified OpenRouter surface is sync and async `chat.completions.create(...)` and
`responses.create(...)`, including raw `stream=True` iteration, through the certified OpenAI SDK.
Provider traffic, keys, attribution/routing headers, retries, and timeouts stay in the original
customer-created client and continue directly to OpenRouter. The explicit wrapper reads only the
OpenAI client's public `base_url` property for provider identity and never reads credentials.

For another endpoint that preserves these tested OpenAI request, response, error, usage, and stream
shapes, pass `provider="openai_compatible"`. This mode is experimental and does not certify the
endpoint. Raw `httpx`/`requests`, the native OpenRouter SDK, arbitrary provider SDKs, altered stream
protocols, and custom clients are unsupported. Provider SDKs remain optional dependencies; install
only the SDKs your application uses.

Anthropic Python `>=1.6,<1.7` is also certified (tested with 1.6.0):

```python
from anthropic import Anthropic
from elektric.observe import observe_anthropic

anthropic = observe_anthropic(
    Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]),
    observer,
)

message = anthropic.messages.create(
    model="claude-sonnet-5",
    max_tokens=256,
    messages=[{"role": "user", "content": "Explain this design."}],
)
```

The same function accepts `AsyncAnthropic`. It observes sync and async `messages.create(...)`,
including raw `stream=True` iteration. Anthropic's accumulating `messages.stream()` helper,
parsing/token-count helpers, batches, beta APIs, files, and legacy completions are outside the
certified surface. Provider calls, credentials, timeouts, and retries remain inside the original
customer-created Anthropic client.

Google GenAI Python `>=2.24,<2.25` is also certified (tested with 2.24.0):

```python
from google import genai
from elektric.observe import observe_google

google = observe_google(
    genai.Client(api_key=os.environ["GOOGLE_API_KEY"]),
    observer,
)

response = google.models.generate_content(
    model="gemini-3.5-flash",
    contents="Explain this design.",
)
```

The wrapper observes sync `models.generate_content(...)` and
`models.generate_content_stream(...)`, plus their async equivalents under `google.aio.models`.
Chats, automatic helper abstractions, image/video generation, embeddings, files, caches, batches,
Live APIs, tuning, token counting, legacy Google generative-AI packages, and other Google Cloud
SDKs are outside the certified surface. Requests and credentials remain inside the original
`google.genai.Client`.

Prompt capture defaults to metadata only. To capture the final direct user text, both the issued
Observe session and local wrapper must use `benchmark_content`; system/developer text, prior turns,
assistant output, tool payloads, and stream output are excluded. If an application has already
merged retrieved context or proprietary instructions into its final user string, Observe cannot
separate that provenance. Use metadata mode when that merged text must not be transmitted.

`observer.enqueue(...)` also accepts an already-normalized, strictly allowlisted event. Use
`observer.flush(timeout=5.0)` in an existing graceful-shutdown path and
`observer.close(timeout=5.0)` to flush and stop the daemon exporter. Delivery is buffered,
bounded, and fail-open after initialization. Initialize Observe after a worker fork; the SDK
discards copied exporter state in forked children. Process crashes and forced serverless
termination can prevent best-effort delivery. Browser, Pyodide, and notebook production use are
not supported or certified.

## Auto and Direct models

Omit `model`, pass `None`, or use `elektric-auto` for automatic routing. Omission and `None` preserve the SDK's existing explicit Auto payload.

```python
with Elektric() as client:
    auto = client.chat(messages=[{"role": "user", "content": "Explain this design."}])
    direct = client.chat(
        model="<public-model-id>",
        messages=[{"role": "user", "content": "Explain this design."}],
    )
    print(direct.model)
```

`<public-model-id>` is a placeholder, not an available model. Named Direct models are live. Direct requests use your selected model with a 1% Elektric fee; Auto selects the model with a 5% fee. The Playground remains Auto-only. `GET /v1/models` is the authority for available IDs.

`AsyncElektric.chat()` and both clients' `chat_stream()` accept the same optional `model: str | None = None`. Explicit strings pass through unchanged; the server validates availability and capabilities. Auto and Direct use the same Elektric API key and base URL. Context IDs and function tools retain their existing serialization.

`ChatResponse.model` preserves the server's public identity. For legacy manually constructed objects or responses without metadata, it is `None`. Stream events carry an optional `model` from their corresponding server chunk. Neither result is inferred from the requested model.

## Standard OpenAI client and discovery

Install the separate `openai` package to use its standard client. The native Elektric SDK uses the root URL; the OpenAI client uses the `/v1` base URL.

```python
import os
from openai import OpenAI

with OpenAI(
    api_key=os.environ["ELEKTRIC_API_KEY"],
    base_url="https://elektric.ai/v1",
) as client:
    for model in client.models.list():
        print(model.id)

    response = client.chat.completions.create(
        model="<public-model-id>",  # Replace with an available ID from the catalog.
        messages=[{"role": "user", "content": "Explain this design."}],
        extra_body={"user_id": "user-123", "conversation_id": "thread-123"},
    )
    print(response.model)
```

Use `elektric-auto` to run this example before named models are activated. Enable the optional Context capabilities in Project Tools when needed. Model entries need not include Auto's extra description fields. Native discovery is also available as `client.models.list()` and `await async_client.models.list()`, returning typed `PublicModel` entries.

Unknown IDs and incompatible parameters surface existing server errors; the SDK discovers models from the server and has no hard-coded model list, alias mappings, or capability matrix.

## Conversations

```python
conversation = client.conversations.get(conversation_id="thread-123")
recent = client.conversations.list(user_id="user-123", limit=20)
client.conversations.delete(conversation_id="thread-123")
```

GET messages are chronological and cursor-paginated with `message_cursor` (default/max 100). LIST is newest-updated-first with opaque cursors (default 20, max 100). Deleting a Conversation does not delete Memory, Knowledge, or other Conversations; a later chat may recreate a fresh thread with the same external ID.

## Memory management

Memory is durable user-specific information across Conversations and remains automatic during chat.

```python
memories = client.memory.list(user_id="user-123")
card = client.memory.get(user_id="user-123", memory_id=memories.data[0].id)
client.memory.update(user_id="user-123", memory_id=card.id, summary="User prefers concise prose.")
client.memory.delete(user_id="user-123", memory_id=card.id)
```

Manual create is deferred; the certified updater owns stable conceptual keys and the 30-card/1,000-token profile limits. Delete deactivates active Memory immediately but does not erase source Conversations or History.

## Async and streaming

```python
from elektric import AsyncElektric

async with AsyncElektric() as client:
    response = await client.chat(message="Hello", user_id="user-123", conversation_id="async-123")
    async for event in client.chat_stream(
        message="Count to three", user_id="user-123", conversation_id="async-stream"
    ):
        if event.type == "content_delta":
            print(event.text, end="")
```

Sync streaming is the same iterator pattern without `async`. Closing a local stream or wait does not imply remote cancellation.

## Video generation

SDK 0.5.0 includes synchronous and asynchronous Video helpers:

```python
video = client.videos.create(
    model="grok-imagine-video-1.5",
    prompt="An orange paper airplane crosses a pale blue studio",
    seconds=5,
    resolution="720p",
    aspect_ratio="16:9",
)
completed = client.videos.wait_for_completion(video.id)
content = client.videos.download_content(completed.id)
```

Use `grok-imagine-video-1.5` for 480p or 720p and `grok-imagine-video` for 480p. Both are text-to-video only and accept 16:9 or 9:16. Video is Direct-only at provider cost +1%, with exact-model execution and no fallback. Provider media is temporary and Elektric does not retain generated video by default.

## Transcription

SDK 0.6.0 includes synchronous and asynchronous exact-product transcription:

```python
transcript = client.audio.transcriptions.create(
    model="gpt-transcribe",
    file=audio_bytes,
    media_type="audio/wav",
    filename="meeting.wav",
)
print(transcript.text)
```

Available products are `gpt-transcribe`, `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, `gpt-4o-transcribe-diarize`, `whisper-1`, `gemini-3.5-transcribe`, and `xai-speech-to-text`. Transcription is Direct-only at provider cost +1%, requires an exact product ID, and never falls back. Timestamp and diarization support differs by product; inspect authenticated `GET /v1/models` before requesting either feature.

## Web and tools

Set `web=True` and read `response.sources`. Tools use Elektric dictionaries with `name`, `description`, and `input_schema`; calls expose `id`, `name`, and parsed `arguments`. Execute them locally, then send the original user message followed by `{"role": "tool", "tool_call_id": call["id"], "content": result}`.

## Files, embeddings, and media

Use `files.upload/get/delete`, `assets.get/download/delete`, `embeddings.create`, and the discoverable `embedding_profiles` alias. Media methods are `audio.transcribe/speech`, `images.generate/edit`, `video.analyze/generate`, and `jobs.get/wait/cancel`. Video generation returns an `ElektricJob`; stopping a local wait never cancels it.

## Realtime

Realtime is intentionally async-only in Python:

```python
async with await client.realtime.connect(input=["text"], output=["text"]) as session:
    await session.send_text("Hello")
    async for event in session:
        print(event.type, event.data)
```

Sessions also provide `send_audio`, `commit_audio`, `interrupt`, and `close`.

## Errors and support

Catch `ElektricError` and inspect `type`, `code`, `status_code`, `request_id`, and safe `details`. Canonical subclasses include authentication, invalid-request, rate-limit, timeout, and service errors. Legacy billing, bad-request, and server subclasses remain compatible. Provider errors and secrets are never exposed or logged.

Python 3.10–3.12 metadata is supported. Both clients are context managers and should be closed. See [Elektric documentation](https://elektric.ai/docs). The native SDK is recommended for the full platform; OpenAI compatibility is for quick migration of existing code.

## Streaming Web sources

Pass `web=True` to synchronous or asynchronous `chat_stream`. A `StreamEvent(type="source")` carries an `ElektricSource`; source events may arrive before, during, or after content deltas and are de-duplicated by URL. The server sends finish before `[DONE]`.

## Knowledge management

Knowledge is persistent project-level reference material used when enabled in Project Tools and relevant. `client.knowledge.add(file)` accepts a path, bytes, or open binary file. Use `list`, `get`, and `delete`, and poll until `status == "ready"`. Retry is deferred: delete and re-upload failed sources.

## Context storage and data controls

Conversation, Memory, and Knowledge are independently configurable in Project Tools and off by default for new Projects. Identifiers alone do not enable them. Disabling a tool preserves existing data. Memory deletion deactivates a card rather than erasing its summary and provenance. Streaming skips same-thread Conversation loading/persistence and automatic Memory updates; enabled Memory reads, historical retrieval and Knowledge can still run.

See [Security and Data](https://elektric.ai/docs/operate/security-data), [Context](https://elektric.ai/docs/concepts/context) and the [Privacy Policy](https://elektric.ai/privacy) before sending personal or confidential information.

Auto selects a model through Elektric and applies a 5% fee. Direct executes the exact selected model with a 1% fee and no cross-model fallback. Available capabilities vary by model.

## Embeddings (0.3.0)

Use `text-embedding-3-small` (1536 dimensions) or `text-embedding-3-large` (3072 dimensions). `client.models.list()` discovers current products.

```python
from elektric import Elektric, AsyncElektric

with Elektric() as client:
    vectors = client.embeddings.create(
        model="text-embedding-3-large",
        input=["first", "second", "third"],
        dimensions=256,
        encoding_format="float",
    )

async def embed():
    async with AsyncElektric() as client:
        return await client.embeddings.create(
            model="text-embedding-3-small",
            input="Elektric",
            encoding_format="float",
        )
```

Each input returns one vector, in order. Embeddings use provider cost +1%, require an exact model, and never route automatically or fall back. Changing embedding models requires re-embedding the corpus.

### Images (0.4.0)

```python
result = client.images.generate(model="gpt-image-2", prompt="A red bicycle")
result = client.images.edit(model="gpt-image-2", prompt="Make it blue", image=png_bytes, media_type="image/png")
print(result.data[0].b64_json)
```

The async client mirrors both methods. An exact model is required; no image Auto. Edits accept one PNG/JPEG byte input or data URI. Generated bytes are not stored by Elektric. URL output is provider-dependent and temporary. Images use provider cost +1%, with no fallback. Discover current availability through models.list().
