# AgniPod Python SDK

Official Python SDK for the [AgniPod](https://agnipod.com) LLM API — chat completions, text generation, batch processing, and more.

## Installation

```bash
pip install agnipod
```

## Quick Start

### 1. Authenticate

```bash
# Option A: CLI login (recommended — saves to ~/.agnipod/credentials)
agnipod login

# On Windows (if 'agnipod' is not recognized):
python -m agnipod login

# Option B: Environment variable
export AGNIPOD_API_KEY="agni_your_key_here"        # Linux/macOS
set AGNIPOD_API_KEY=agni_your_key_here              # Windows CMD
$env:AGNIPOD_API_KEY="agni_your_key_here"           # Windows PowerShell

# Option C: Pass directly in code
from agnipod import AgniPod
client = AgniPod(api_key="agni_your_key_here")
```

### 2. Chat completion

```python
from agnipod import chat

response = chat("What is artificial intelligence?", model="qwen3:8b")
print(response.content)
```

### 3. Text generation

```python
from agnipod import generate

response = generate("Once upon a time", model="qwen3:8b")
print(response.content)
```

---

## CLI Commands

After installing the package, the `agnipod` command is available:

```bash
agnipod login          # Save your API key locally (~/.agnipod/credentials)
agnipod logout         # Remove saved credentials
agnipod status         # Show current auth config & key sources
agnipod models         # List all available models
agnipod version        # Print SDK version
agnipod help           # Show all commands
```

> **Windows:** If `agnipod` is not recognized, use `python -m agnipod <command>` instead.

### Login

```bash
$ agnipod login
  Enter your API key (starts with agni_)
  API Key: ****

  ✓ API key saved to /home/user/.agnipod/credentials
```

You can also pass the key directly: `agnipod login agni_your_key`

---

## Full Client Usage

For production applications, use the `AgniPod` client class:

```python
from agnipod import AgniPod

client = AgniPod()  # reads key from env var or ~/.agnipod/credentials
```

### Chat Completions

```python
response = client.chat.create(
    model="qwen3:8b",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain quantum computing in simple terms"},
    ],
    temperature=0.7,
    max_tokens=512,
)

print(response.content)
print(f"Tokens: {response.prompt_tokens} in / {response.completion_tokens} out")
```

### Streaming

```python
stream = client.chat.create(
    model="qwen3:8b",
    messages=[{"role": "user", "content": "Tell me a story"}],
    stream=True,
)

for chunk in stream:
    print(chunk, end="", flush=True)
print()
```

### Text Generation

```python
response = client.generate.create(
    model="qwen3:8b",
    prompt="Write a Python function to reverse a string",
    system_prompt="You are a senior Python developer.",
    max_tokens=256,
)
print(response.content)
```

### Models

```python
# List all available models
models = client.models.list()
for model in models:
    print(model.id, model.owned_by)

# Get a specific model
model = client.models.get("qwen3:8b")
```

### Batch Processing

```python
# Create a batch
result = client.batches.create(items=[
    {
        "custom_id": "q1",
        "model": "qwen3:8b",
        "messages": [{"role": "user", "content": "What is 2+2?"}],
    },
    {
        "custom_id": "q2",
        "model": "qwen3:8b",
        "messages": [{"role": "user", "content": "What is 3+3?"}],
    },
])
print(f"Batch {result.batch_id} created ({result.total_items} items)")

# Start processing
client.batches.start(result.batch_id)

# Wait for completion (polls automatically)
batch = client.batches.wait(result.batch_id, poll_interval=5)
print(f"Status: {batch.status}")

# Download results
client.batches.results(result.batch_id, save_to="results.jsonl")
```

### Upload JSONL Batch

```python
result = client.batches.create_jsonl(
    file_path="my_batch.jsonl",
    batch_name="Weekly analysis",
)
client.batches.start(result.batch_id)
```

### Billing & Wallet

```python
# Check balance
balance = client.billing.balance()
print(balance)  # $1.2345 USD

# Usage summary
usage = client.billing.usage(days=7)

# Transaction history
for txn in client.billing.transactions(limit=5):
    print(txn.transaction_type, txn.amount)
```

---

## Error Handling

All errors inherit from `AgniPodError` for easy catching:

```python
from agnipod import AgniPod, AgniPodError, AuthenticationError, InsufficientBalanceError

client = AgniPod()

try:
    response = client.chat.create(
        model="qwen3:8b",
        messages=[{"role": "user", "content": "Hello"}],
    )
except AuthenticationError:
    print("Invalid API key")
except InsufficientBalanceError:
    print("Add funds to your wallet")
except AgniPodError as e:
    print(f"API error: {e.message} (status={e.status_code})")
```

### Exception Hierarchy

| Exception | HTTP Code | When |
|---|---|---|
| `AuthenticationError` | 401 | Invalid / revoked API key |
| `InsufficientBalanceError` | 402 | Wallet balance too low |
| `PermissionDeniedError` | 403 | Wallet inactive |
| `BadRequestError` | 400 | Invalid request payload |
| `NotFoundError` | 404 | Model / resource not found |
| `RateLimitError` | 429 | Too many requests |
| `ServiceUnavailableError` | 503 | No GPU workers available |
| `ServerError` | 5xx | Unexpected server error |
| `APIConnectionError` | — | Network unreachable |
| `APITimeoutError` | — | Request timed out |
| `StreamError` | — | Error during streaming |
| `ValidationError` | — | Client-side validation |
| `ConfigurationError` | — | Missing API key / bad config |

---

## Configuration

API key resolution order (first non-empty wins):

| Priority | Method |
|---|---|
| 1 | `AgniPod(api_key="agni_...")` |
| 2 | `AGNIPOD_API_KEY` environment variable |
| 3 | `~/.agnipod/credentials` (via `agnipod login`) |

```python
client = AgniPod(
    api_key="agni_...",            # optional if env/creds set
    timeout=60,                    # request timeout (seconds)
    max_retries=3,                 # retries on transient errors
)
```

### Context Manager

```python
with AgniPod() as client:
    response = client.chat.create(
        model="qwen3:8b",
        messages=[{"role": "user", "content": "Hello"}],
    )
# session auto-closed
```

---

## Requirements

- Python 3.9+
- `requests >= 2.28.0`