Metadata-Version: 2.5
Name: khai-sdk
Version: 0.1.0
Summary: Official Python SDK for streaming chatbot turns to Khai for evaluation.
Project-URL: Homepage, https://getkhai.ai
Project-URL: Source, https://github.com/KHAI-BE/khai-python
Project-URL: Changelog, https://github.com/KHAI-BE/khai-python/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/KHAI-BE/khai-python/issues
Author-email: Khai <support@getkhai.ai>
License-Expression: MIT
License-File: LICENSE
Keywords: chatbot,evaluation,khai,llm,observability
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.24
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Description-Content-Type: text/markdown

# Khai Python SDK

Stream chatbot turns to Khai for evaluation from any cloud — no access granted to Khai.

## Install

```bash
pip install khai-sdk
```

## Quickstart

Set your key in the environment rather than in source:

```bash
export KHAI_API_KEY="khai_..."
```

```python
from khai import KhaiClient

khai = KhaiClient()  # reads KHAI_API_KEY; base_url defaults to https://api.getkhai.ai

resp = khai.ingest_turn(
    user_query="How do I pay my bill?",
    agent_response="You can pay online at ...",
    session_id="chat-123",
    agent_id="YOUR_AGENT_ID",
)
print(resp.status, resp.result.trust_score if resp.evaluated else resp.reason)
```

## Every turn is idempotent

You do not have to think about retries. When you omit them, the SDK fills in a
`timestamp` (UTC, ISO-8601) and a UUID `response_id` **when the turn is created**,
and keeps them for every retry. The backend deduplicates on `response_id`, so a
turn resent after a timeout, a 5xx, or a network outage is answered with
`status: duplicate` instead of being stored twice.

If your platform already has a per-reply id (Dialogflow `responseId`, Lex,
Copilot Studio), pass it as `response_id` and the same guarantee holds even
across restarts of your own process.

## Fire-and-forget

`ingest_turn_async` queues the turn and returns immediately, so it never blocks
your chatbot's response path. Failed deliveries are buffered and retried. Both
the queue and the buffer are capped at `max_queue_size` (default 10,000 turns);
when full, the SDK drops a turn and reports it through `on_error` as
`KhaiQueueFullError` rather than growing memory during an outage. Open clients
are flushed once at interpreter exit.

```python
with KhaiClient() as khai:
    khai.ingest_turn_async(
        user_query=user_msg,
        agent_response=bot_reply,
        session_id=session_id,
        agent_id="YOUR_AGENT_ID",
    )
    # ... keep serving; turns flush in the background, and on close().
```

## Batch

```python
from khai import IngestTurn

khai.ingest_batch([
    IngestTurn(user_query="...", agent_response="...", session_id="s1", agent_id="a1"),
    IngestTurn(user_query="...", agent_response="...", session_id="s1", agent_id="a1"),
])
```

## Redact PII before sending

```python
def redact(payload: dict) -> dict:
    payload["user_query"] = mask_emails(payload["user_query"])
    return payload

khai.set_redactor(redact)
```

## Errors

All errors derive from `KhaiError`:

| Error | Meaning |
|---|---|
| `KhaiAuthError` | API key missing/invalid (401/403) |
| `KhaiRateLimitError` | Per-key rate limit exceeded (429) |
| `KhaiAPIError` | Other non-success status |
| `KhaiConnectionError` | Network/timeout (already retried) |
| `KhaiQueueFullError` | Fire-and-forget queue/buffer full; a turn was dropped (via `on_error`) |
| `KhaiConfigError` | Bad client configuration (missing key, non-HTTPS base URL) |

## Configuration

| Setting | Argument | Environment variable | Default |
|---|---|---|---|
| API key | `api_key` | `KHAI_API_KEY` | required |
| Base URL | `base_url` | `KHAI_BASE_URL` | `https://api.getkhai.ai` |
| Request timeout (s) | `timeout` | — | `10.0` |
| Retries on 429/5xx/network | `max_retries` | — | `3` |
| Queue / buffer cap | `max_queue_size` | — | `10000` |

`base_url` must be `https://`; plain `http://` is accepted only for `localhost`.

## Releasing (maintainers)

Releases are cut from tags and published by CI through PyPI trusted publishing;
no one uploads from a laptop and no API token is stored anywhere.

1. Bump `__version__` in `src/khai/_version.py` and move the `Unreleased`
   entries in `CHANGELOG.md` under the new version. Merge via pull request.
2. Tag the merged commit and push the tag:

   ```bash
   git tag v0.1.0 && git push origin v0.1.0
   ```

3. The `Release` workflow builds the sdist and wheel, publishes to TestPyPI,
   installs from TestPyPI and imports the package, then waits for a reviewer to
   approve the `pypi` environment before publishing to PyPI and creating the
   GitHub Release. A tag whose version does not match `__version__` fails fast.
