Metadata-Version: 2.4
Name: datalabrotterdam-nova-sdk
Version: 1.5.0
Summary: Python client for the Nova AI API
Author: Datalab Rotterdam
License-Expression: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# Python SDK

Python client for the Nova AI API.

The package is intentionally small:

- sync methods
- no external dependencies
- typed request and response structures
- simple streaming support for chat completions with a normal `for` loop
- audio helpers for transcription, translation, and speech generation

## Install

```bash
pip install datalabrotterdam-nova-sdk
```

## Create A Client

```python
from nova_ai import NovaAIClient

client = NovaAIClient(
    api_key="sk_live_..."
)
```

If you do not pass `base_url`, the client uses `https://api.nova.datalabrotterdam.nl/v1`.

## Example: Normal Chat

```python
from nova_ai import NovaAIClient

client = NovaAIClient(
    api_key="sk_live_..."
)

completion = client.chat.completions.create({
    "model": "llama3.2",
    "messages": [
        {"role": "system", "content": "You are helpful."},
        {"role": "user", "content": "Explain embeddings in one sentence."}
    ],
    "max_tokens": 256,
    "temperature": 0.7,
})

print(completion["choices"][0]["message"]["content"])
print(completion.get("usage_source"))
print(completion.get("metrics", {}).get("tokens_per_second"))
```

`completion["usage_source"]` is:
- `"provider"` when the upstream provider reported usage
- `"gateway_estimated"` when Nova AI estimated usage to keep the contract consistent

## Example: Streaming Chat

```python
from nova_ai import NovaAIClient, collect_chat_text

client = NovaAIClient(
    api_key="sk_live_..."
)

stream = client.chat.completions.stream({
    "model": "llama3.2",
    "messages": [{"role": "user", "content": "Write three short tips."}]
})

for event in stream:
    if event["type"] == "chunk":
        choices = event["data"].get("choices", [])
        if choices:
            print(choices[0].get("delta", {}).get("content", ""), end="")

print()

print(collect_chat_text(client.chat.completions.stream({
    "model": "llama3.2",
    "messages": [{"role": "user", "content": "Summarize vectors in one line."}]
})))
```

## Example: Embeddings

```python
from nova_ai import NovaAIClient

client = NovaAIClient(
    api_key="sk_live_..."
)

embedding = client.embeddings.create({
    "model": "nomic-embed-text",
    "input": "Nova AI"
})

print(len(embedding["data"][0]["embedding"]))
```

## Example: Audio Transcription

```python
from nova_ai import NovaAIClient

client = NovaAIClient(
    api_key="sk_live_..."
)

transcript = client.audio.transcriptions.create(
    file_path="./sample.wav",
    model="whisper-1",
    language="nl",
)

print(transcript["text"])
```

## Example: Speech Synthesis

```python
from nova_ai import NovaAIClient

client = NovaAIClient(
    api_key="sk_live_..."
)

audio = client.audio.speech.create({
    "model": "tts-1",
    "input": "Hello from Nova AI",
    "voice": "alloy",
    "response_format": "wav",
})

with open("speech.wav", "wb") as target:
    target.write(audio)
```

## Included Methods

- `client.providers.list()`
- `client.models.list(limit=None, after=None)`
- `client.chat.completions.create(payload, config_id=None, request_id=None)`
- `client.chat.completions.stream(payload, config_id=None, request_id=None)`
- `client.embeddings.create(payload, config_id=None, request_id=None)`
- `client.audio.transcriptions.create(file_path=..., model=..., ...)`
- `client.audio.translations.create(file_path=..., model=..., ...)`
- `client.audio.speech.create(payload, config_id=None, request_id=None)`

## Audio Notes

- `client.audio.transcriptions.create()` and `client.audio.translations.create()` upload audio as `multipart/form-data`.
- `client.audio.speech.create()` returns raw bytes.
- The gateway enforces runtime scopes:
  - `audio:transcribe`
  - `audio:translate`
  - `audio:synthesis`

## Why streaming uses `for`, not `async for`

For this Python SDK, a normal iterator is the simplest way to support streaming without extra dependencies. That means you can stream like this:

```python
for event in client.chat.completions.stream({...}):
    ...
```

Using `async for` is also possible, but it usually means introducing an async HTTP client such as `httpx` or `aiohttp`, which makes the first version of the SDK heavier.
