Metadata-Version: 2.4
Name: aiqlick
Version: 0.1.0
Summary: Python client for the AIQLick inference API
Project-URL: Documentation, https://docs.aiqlick.com
Author: AIQLick
License-Expression: MIT
License-File: LICENSE
Keywords: aiqlick,bedrock,inference,llm,openai
Requires-Python: >=3.9
Requires-Dist: openai>=1.40
Provides-Extra: dev
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# aiqlick — Python client

A thin wrapper over the official [`openai`](https://pypi.org/project/openai/) package for the
AIQLick inference API.

Deliberately thin. The whole product claim is that the API is OpenAI-compatible,
so a client that reimplemented the protocol could drift away from that claim
without anyone noticing. `AIQLick` subclasses `OpenAI`, so anything the official
client does — streaming, tool calling, retries, async, whatever it adds next —
works here unchanged.

## Install

```bash
pip install aiqlick
```

## Use

```python
from aiqlick import AIQLick, Models

client = AIQLick(api_key="sk-...")   # or set AIQLICK_API_KEY

reply = client.chat.completions.create(
    model=Models.CHAT_DEFAULT,
    messages=[{"role": "user", "content": "hello"}],
)
print(reply.choices[0].message.content)
```

Streaming works exactly as it does with the official client:

```python
stream = client.chat.completions.create(
    model=Models.CHAT_DEFAULT,
    messages=[{"role": "user", "content": "hello"}],
    stream=True,
)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="")
```

## Models

Use the aliases, not provider model ids:

| Constant | Alias |
|---|---|
| `Models.CHAT_PREMIUM` | `aiqlick/chat-premium` |
| `Models.CHAT_DEFAULT` | `aiqlick/chat-default` |
| `Models.CHAT_CHEAP` | `aiqlick/chat-cheap` |
| `Models.EXTRACT_DEFAULT` | `aiqlick/extract-default` |
| `Models.EXTRACT_FAST` | `aiqlick/extract-fast` |
| `Models.EMBED_DEFAULT` | `aiqlick/embed-default` |

The mapping from an alias to a specific provider model is a platform decision
that changes without callers changing — that is the entire point of the gateway.
Pinning a provider id in your own code gives that up.

Your key may be scoped to a subset. `client.models.list()` returns only what it
can actually call.

## Errors

The OpenAI SDK gives you a status and a message. `specific_error()` promotes our
`code` onto a typed exception, which is what lets you tell "out of credits" from
"subscription lapsed" — the same 402 with entirely different fixes.

```python
import openai
from aiqlick import specific_error, InsufficientCredits

try:
    client.chat.completions.create(...)
except openai.APIStatusError as exc:
    err = specific_error(exc)
    if isinstance(err, InsufficientCredits):
        ...   # top up or upgrade the plan
    raise err or exc
```

`specific_error()` returns `None` for anything it does not recognise rather than
guessing, so a provider error never gets mislabelled as a platform error.

| Exception | `code` |
|---|---|
| `InsufficientCredits` | `INSUFFICIENT_CREDITS` |
| `SubscriptionInactive` | `SUBSCRIPTION_INACTIVE` |
| `ModelNotAllowed` | `LLM_MODEL_NOT_ALLOWED` |
| `GatewayUnavailable` | `LLM_GATEWAY_UNAVAILABLE` |

## Configuration

| Variable | Default |
|---|---|
| `AIQLICK_API_KEY` | — |
| `AIQLICK_BASE_URL` | `https://api.aiqlick.com/llm/v1` |

Reading the base URL from the environment means one build can be pointed at dev
or a self-hosted deployment without a code change.

## Not using this client

You do not have to. The API is OpenAI-compatible, so the official client works
directly — this package only saves you the base URL and the constants:

```python
from openai import OpenAI
client = OpenAI(api_key="sk-...", base_url="https://api.aiqlick.com/llm/v1")
```

Full documentation: <https://docs.aiqlick.com>
