Metadata-Version: 2.4
Name: vitaai
Version: 1.0.0
Summary: Python SDK for VitaAI self-hosted LLM endpoints — drop-in Gemini API replacement
License-Expression: MIT
Project-URL: Homepage, https://github.com/vitainspire/vitaai-python
Project-URL: Documentation, https://github.com/vitainspire/vitaai-python#readme
Keywords: llm,ai,gemini,sdk,vitaai
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Provides-Extra: requests
Requires-Dist: requests>=2.28; extra == "requests"
Provides-Extra: async
Requires-Dist: aiohttp>=3.8; extra == "async"
Provides-Extra: all
Requires-Dist: requests>=2.28; extra == "all"
Requires-Dist: aiohttp>=3.8; extra == "all"

# vitaai · Python SDK

Python SDK for **VitaAI self-hosted LLM endpoints**.  
Works just like the Google Gemini SDK — `client.models.method()` pattern with a `stream` parameter on every endpoint.

---

## Install

```bash
pip install vitaai
pip install requests  # required for image endpoints
```

---

## Quick start

```python
from vitaai import VitaAI

client = VitaAI(
    base_url="http://your-server-ip:8000",
    api_key="your-api-key"   # optional — not required if server has no auth
)

response = client.models.generate_content(
    model="gemma-4-E2B-it",
    contents="What is AI?",
    max_output_tokens=100
)
print(response.text)
print("Tokens:", response.usage_metadata.total_token_count)
```

---

## Client parameters

| Parameter  | Type | Default | Description |
|------------|------|---------|-------------|
| `base_url` | str  | `https://api.vitaai-api.com` | Your self-hosted server URL |
| `api_key`  | str  | `None` | Optional API key. Or set `VITAAI_API_KEY` env var |
| `timeout`  | int  | `120`  | Request timeout in seconds |

---

## Streaming

Every endpoint supports a `stream` parameter:

- `stream=False` (default) — waits and returns the full response object
- `stream=True` — yields tokens live as the model generates them

```python
# Normal — full response at once
r = client.models.generate_content(model="gemma-4-E2B-it", contents="What is AI?")
print(r.text)

# Streaming — tokens print live
for chunk in client.models.generate_content(model="gemma-4-E2B-it", contents="What is AI?", stream=True):
    print(chunk.text, end="", flush=True)
```

---

## Endpoints

### 1. Generate content

```python
r = client.models.generate_content(
    model="gemma-4-E2B-it",
    contents="What is machine learning?",
    max_output_tokens=150,
    stream=False,  # default
)
print(r.text)
print("Tokens:", r.usage_metadata.total_token_count)
```

**With config (system instruction, temperature, etc.)**

```python
r = client.models.generate_content(
    model="gemma-4-E2B-it",
    contents="Explain deep learning",
    config={
        "system_instruction": "You are a teacher. Explain simply.",
        "temperature": 0.7,
        "top_p": 0.9,
        "max_output_tokens": 200,
    }
)
print(r.text)
```

**Multi-turn conversation**

```python
r = client.models.generate_content(
    model="gemma-4-E2B-it",
    contents=[
        {"role": "user",  "parts": [{"text": "My name is Alex."}]},
        {"role": "model", "parts": [{"text": "Nice to meet you, Alex!"}]},
        {"role": "user",  "parts": [{"text": "What is my name?"}]},
    ]
)
print(r.text)
```

**Streaming**

```python
for chunk in client.models.generate_content(
    model="gemma-4-E2B-it",
    contents="Write a short poem.",
    max_output_tokens=100,
    stream=True,
):
    print(chunk.text, end="", flush=True)
```

| Response field | Type | Description |
|----------------|------|-------------|
| `r.text` | str | Full generated text |
| `r.usage_metadata.prompt_token_count` | int | Input tokens |
| `r.usage_metadata.candidates_token_count` | int | Output tokens |
| `r.usage_metadata.total_token_count` | int | Total tokens |

---

### 2. Summarize

```python
# Normal
r = client.models.summarize(
    contents="Long article text here...",
    format="bullets",       # "paragraph" or "bullets"
    max_output_tokens=150,
)
print(r.summary)

# Streaming
for chunk in client.models.summarize(contents="Long article...", format="paragraph", stream=True):
    print(chunk.text, end="", flush=True)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `contents` | str | — | Text to summarize |
| `format` | str | `"paragraph"` | `"paragraph"` or `"bullets"` |
| `max_output_tokens` | int | `200` | Max summary length |
| `stream` | bool | `False` | Stream tokens live |

| Response field | Type | Description |
|----------------|------|-------------|
| `r.summary` | str | Summarized text |
| `r.format` | str | Format used |
| `r.usage_metadata.total_token_count` | int | Total tokens |

---

### 3. Embeddings

```python
r = client.models.embed_content(
    contents="Artificial intelligence is transforming the world."
)
print("Dimensions:", len(r.embedding.values))
print("Vector:", r.embedding.values[:5])
```

| Response field | Type | Description |
|----------------|------|-------------|
| `r.embedding.values` | List[float] | The embedding vector |

---

### 4. Analyze image

```python
# Normal
r = client.models.analyze_image(
    file_path="photo.jpg",
    prompt="What objects are visible in this image?",
    max_output_tokens=512,
)
print(r.analysis)

# Streaming
for chunk in client.models.analyze_image(file_path="photo.jpg", prompt="What do you see?", stream=True):
    print(chunk.text, end="", flush=True)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | str | — | Path to image (JPG, PNG, WEBP, GIF) |
| `prompt` | str | — | Question or instruction |
| `system_instruction` | str | None | Optional system prompt |
| `temperature` | float | `1.0` | Sampling temperature |
| `max_output_tokens` | int | `512` | Max response length |
| `image_token_budget` | int | `280` | Detail level: `70/140/280/560/1120` |
| `stream` | bool | `False` | Stream tokens live |

| Response field | Type | Description |
|----------------|------|-------------|
| `r.analysis` | str | Model's analysis |
| `r.image_info.size` | tuple | Image dimensions |
| `r.usage_metadata.total_token_count` | int | Total tokens |

---

### 5. Describe image

```python
# Normal
r = client.models.describe_image(file_path="photo.jpg", max_output_tokens=512)
print(r.description)

# Streaming
for chunk in client.models.describe_image(file_path="photo.jpg", stream=True):
    print(chunk.text, end="", flush=True)
```

| Response field | Type | Description |
|----------------|------|-------------|
| `r.description` | str | Full image description |
| `r.image_info.size` | tuple | Image dimensions |
| `r.usage_metadata.total_token_count` | int | Total tokens |

---

### 6. OCR — extract text from image

```python
# Normal
r = client.models.ocr_image(file_path="document.png", language="English")
print(r.extracted_text)

# Streaming
for chunk in client.models.ocr_image(file_path="document.png", stream=True):
    print(chunk.text, end="", flush=True)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | str | — | Path to image |
| `language` | str | `"English"` | Language of text in image |
| `max_output_tokens` | int | `512` | Max response length |
| `image_token_budget` | int | `560` | Higher = better OCR accuracy |
| `stream` | bool | `False` | Stream tokens live |

| Response field | Type | Description |
|----------------|------|-------------|
| `r.extracted_text` | str | All text found in image |
| `r.image_info.language` | str | Language used |
| `r.usage_metadata.total_token_count` | int | Total tokens |

---

### 7. Ping

```python
r = client.models.ping()
print(r.status)   # "ok"
print(r.model)    # "gemma-4-E2B-it"
print(r.device)   # "CUDA"
print(r.version)  # "2.3.0"
print(r.limits.max_input_tokens)
print(r.limits.max_output_tokens)
```

---

### 8. Health

```python
r = client.models.health()
print(r.status)          # "healthy"
print(r.cuda_available)  # True / False
print(r.limits.max_input_tokens)
print(r.limits.max_output_tokens)
```

---

### 9. Stats

```python
s = client.models.stats()
print(s.device)                    # "CUDA"
print(s.gpu_name)                  # "Tesla T4"
print(f"{s.gpu_memory_total_gb:.1f} GB")
print(f"{s.gpu_memory_allocated_gb:.1f} GB")
```

---

### 10. List models

```python
for m in client.models.list():
    print(m["name"])
    print(m["capabilities"])
    print(m["max_output_tokens"])
```

---

## Error handling

```python
from vitaai.transport import AuthenticationError, ServerError, VitaAIError

try:
    r = client.models.generate_content(model="gemma-4-E2B-it", contents="Hello")
    print(r.text)
except AuthenticationError:
    print("Invalid API key")
except ServerError as e:
    print(f"Server error ({e.status_code}): {e}")
except VitaAIError as e:
    print(f"Error: {e}")
```

---

## Server endpoints

| Method | Path | stream support |
|--------|------|----------------|
| GET  | `/` | — |
| GET  | `/health` | — |
| GET  | `/v1/stats` | — |
| GET  | `/v1/models/list` | — |
| POST | `/v1/models/generate` | ✅ `?stream=true` |
| POST | `/v1/models/summarize` | ✅ `?stream=true` |
| POST | `/v1/models/embed` | — |
| POST | `/v1/models/analyze-image` | ✅ `?stream=true` |
| POST | `/v1/models/describe-image` | ✅ `?stream=true` |
| POST | `/v1/models/ocr-image` | ✅ `?stream=true` |

---

## Environment variables

| Variable | Description |
|----------|-------------|
| `VITAAI_API_KEY` | API key (alternative to `api_key=` parameter) |
| `VITAAI_BASE_URL` | Server URL (alternative to `base_url=` parameter) |
