Metadata-Version: 2.5
Name: hkubs-genai
Version: 0.2.0
Summary: Student client for the HKU Internal GPT Gateway
Project-URL: Homepage, https://msc-genai-gateway.hkubs.hku.hk
Project-URL: Repository, https://github.com/HKU-Business-School/genai-gw-sdk
Author: HKU Business School
License-Expression: MIT
License-File: LICENSE
Keywords: gateway,genai,hku,hkubs,llm
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Education
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.28.1
Requires-Dist: pydantic>=2.7
Description-Content-Type: text/markdown

# hkubs-genai

Student client for the HKU Internal GPT Gateway. Authenticates with an internal
API key (`hku_live_...`) issued by your professor - never a cloud credential.

## Install

```bash
uv add hkubs-genai
```

## Quick start

```python
import os
from hkubs_genai import GenAIClient

client = GenAIClient(api_key=os.environ["HKU_GATEWAY_API_KEY"])

print(client.available_models())
# ['DeepSeek-V4-Flash', 'gpt-5.6-luna-1']

print(client.complete("Explain quicksort in one paragraph.", model="gpt-5.6-luna-1"))
```

The key can also come straight from the `HKU_GATEWAY_API_KEY` environment
variable (`GenAIClient()` with no arguments).

## Choosing a model

`model` is required on every call and has no default - the gateway serves
several models and picks none of them for you. `available_models()` asks the
gateway which ones your key may use; the answer comes from the grants your
professor issued, so it differs per student and changes when they change it.

## Usage

```python
# Options: system message, history, output format, per-call tuning
client.complete(
    "Summarize my notes as bullet points.",
    model="gpt-5.6-luna-1",
    system_message="You are a concise study assistant.",
    history=[{"role": "user", "content": "..."}],
    output_format="markdown",          # text | json | markdown | list | table
    override={"temperature": 0.2, "max_tokens": 800},
)

# Streaming
for chunk in client.stream("Write a haiku about recursion.", model="gpt-5.6-luna-1"):
    print(chunk, end="", flush=True)

# Tool calling
result = client.complete_with_tools(
    "What's the weather in Hong Kong?",
    model="gpt-5.6-luna-1",
    tools=[{"name": "get_weather", "description": "...", "parameters": {...}}],
)
if result.tool_name:
    ...  # run the function yourself with result.tool_args
```

Quota errors carry your remaining balance:

```python
from hkubs_genai import QuotaExceededError

try:
    client.complete("...", model="gpt-5.6-luna-1")
except QuotaExceededError as e:
    print(e.token_remaining, e.next_renewal_at)
```

Your professor decides which models your course or group may call - the same
list `available_models()` returns and your dashboard shows. Naming one outside
it fails before anything is sent upstream, so no tokens are spent:

```python
from hkubs_genai import ModelNotAllowedError

try:
    client.complete("...", model="gpt-5.6-sol-1")
except ModelNotAllowedError as e:
    print(e)  # names the models you may use instead
```

## Tests

```bash
uv run pytest                      # unit tests (mocked gateway)
uv run pytest -m integration      # requires the local backend on :8000
```
