khai-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- khai_sdk-0.1.0/.github/workflows/release.yml +166 -0
- khai_sdk-0.1.0/.github/workflows/tests.yml +30 -0
- khai_sdk-0.1.0/.gitignore +10 -0
- khai_sdk-0.1.0/CHANGELOG.md +41 -0
- khai_sdk-0.1.0/LICENSE +21 -0
- khai_sdk-0.1.0/PKG-INFO +155 -0
- khai_sdk-0.1.0/README.md +127 -0
- khai_sdk-0.1.0/SECURITY.md +10 -0
- khai_sdk-0.1.0/openapi/khai.json +4518 -0
- khai_sdk-0.1.0/pyproject.toml +46 -0
- khai_sdk-0.1.0/src/khai/__init__.py +37 -0
- khai_sdk-0.1.0/src/khai/_transport.py +141 -0
- khai_sdk-0.1.0/src/khai/_version.py +8 -0
- khai_sdk-0.1.0/src/khai/client.py +320 -0
- khai_sdk-0.1.0/src/khai/errors.py +57 -0
- khai_sdk-0.1.0/src/khai/models.py +175 -0
- khai_sdk-0.1.0/src/khai/py.typed +0 -0
- khai_sdk-0.1.0/tests/test_client.py +414 -0
- khai_sdk-0.1.0/tests/test_contract.py +64 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publish flow for the khai-sdk package. Triggered only by version tags (v1.2.3).
|
|
4
|
+
#
|
|
5
|
+
# build ─► publish-testpypi ─► smoke-testpypi ─► publish-pypi ─► github-release
|
|
6
|
+
#
|
|
7
|
+
# Both publish jobs use PyPI Trusted Publishing: the job requests a short-lived
|
|
8
|
+
# OIDC token (permissions: id-token: write) and PyPI accepts it because a
|
|
9
|
+
# publisher for owner KHAI-BE / repo khai-python / workflow release.yml /
|
|
10
|
+
# environment {testpypi,pypi} is registered there. No API token exists to leak.
|
|
11
|
+
# The `pypi` environment requires a human reviewer, so a real release always
|
|
12
|
+
# has an explicit approval step after the TestPyPI rehearsal has passed.
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
tags: ["v*"]
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
env:
|
|
22
|
+
PACKAGE: khai-sdk
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
build:
|
|
26
|
+
name: Build sdist and wheel
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
timeout-minutes: 10
|
|
29
|
+
outputs:
|
|
30
|
+
version: ${{ steps.version.outputs.version }}
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
|
|
34
|
+
- uses: actions/setup-python@v5
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.12"
|
|
37
|
+
|
|
38
|
+
- name: Check tag matches package version
|
|
39
|
+
id: version
|
|
40
|
+
run: |
|
|
41
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
42
|
+
pkg="$(python -c 'import re,pathlib; print(re.search(r"__version__\s*=\s*\"([^\"]+)\"", pathlib.Path("src/khai/_version.py").read_text()).group(1))')"
|
|
43
|
+
echo "tag=$tag package=$pkg"
|
|
44
|
+
if [ "$tag" != "$pkg" ]; then
|
|
45
|
+
echo "::error::Tag v$tag does not match src/khai/_version.py ($pkg)"; exit 1
|
|
46
|
+
fi
|
|
47
|
+
echo "version=$pkg" >> "$GITHUB_OUTPUT"
|
|
48
|
+
|
|
49
|
+
- name: Run tests
|
|
50
|
+
run: |
|
|
51
|
+
python -m pip install --upgrade pip
|
|
52
|
+
pip install -e ".[dev]"
|
|
53
|
+
pytest -q
|
|
54
|
+
|
|
55
|
+
- name: Build
|
|
56
|
+
run: |
|
|
57
|
+
pip install build
|
|
58
|
+
python -m build
|
|
59
|
+
ls -l dist
|
|
60
|
+
|
|
61
|
+
- uses: actions/upload-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
name: dist
|
|
64
|
+
path: dist/
|
|
65
|
+
if-no-files-found: error
|
|
66
|
+
|
|
67
|
+
publish-testpypi:
|
|
68
|
+
name: Publish to TestPyPI
|
|
69
|
+
needs: build
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
timeout-minutes: 10
|
|
72
|
+
environment:
|
|
73
|
+
name: testpypi
|
|
74
|
+
url: https://test.pypi.org/project/khai-sdk/${{ needs.build.outputs.version }}/
|
|
75
|
+
permissions:
|
|
76
|
+
id-token: write
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/download-artifact@v4
|
|
79
|
+
with:
|
|
80
|
+
name: dist
|
|
81
|
+
path: dist/
|
|
82
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
83
|
+
with:
|
|
84
|
+
repository-url: https://test.pypi.org/legacy/
|
|
85
|
+
|
|
86
|
+
smoke-testpypi:
|
|
87
|
+
name: Install from TestPyPI and import
|
|
88
|
+
needs: [build, publish-testpypi]
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
timeout-minutes: 15
|
|
91
|
+
steps:
|
|
92
|
+
- uses: actions/setup-python@v5
|
|
93
|
+
with:
|
|
94
|
+
python-version: "3.12"
|
|
95
|
+
- name: Install the just-published version
|
|
96
|
+
env:
|
|
97
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
98
|
+
run: |
|
|
99
|
+
# The TestPyPI simple index can lag the upload by a minute; retry.
|
|
100
|
+
for i in 1 2 3 4 5 6 7 8 9 10; do
|
|
101
|
+
if pip install --no-cache-dir \
|
|
102
|
+
--index-url https://test.pypi.org/simple/ \
|
|
103
|
+
--extra-index-url https://pypi.org/simple/ \
|
|
104
|
+
"${PACKAGE}==${VERSION}"; then
|
|
105
|
+
break
|
|
106
|
+
fi
|
|
107
|
+
echo "not indexed yet, retry $i"; sleep 20
|
|
108
|
+
done
|
|
109
|
+
- name: Import and sanity-check
|
|
110
|
+
env:
|
|
111
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
112
|
+
run: |
|
|
113
|
+
python - <<'PY'
|
|
114
|
+
import os
|
|
115
|
+
import khai
|
|
116
|
+
from khai import KhaiClient, KhaiConfigError, IngestTurn
|
|
117
|
+
assert khai.__version__ == os.environ["VERSION"], khai.__version__
|
|
118
|
+
t = IngestTurn("q", "a", "s")
|
|
119
|
+
assert t.response_id and t.timestamp
|
|
120
|
+
try:
|
|
121
|
+
KhaiClient(api_key="x", base_url="http://api.getkhai.ai")
|
|
122
|
+
except KhaiConfigError:
|
|
123
|
+
pass
|
|
124
|
+
else:
|
|
125
|
+
raise SystemExit("http base_url should be rejected")
|
|
126
|
+
print("ok", khai.__version__)
|
|
127
|
+
PY
|
|
128
|
+
|
|
129
|
+
publish-pypi:
|
|
130
|
+
name: Publish to PyPI
|
|
131
|
+
needs: [build, smoke-testpypi]
|
|
132
|
+
runs-on: ubuntu-latest
|
|
133
|
+
timeout-minutes: 10
|
|
134
|
+
environment:
|
|
135
|
+
name: pypi
|
|
136
|
+
url: https://pypi.org/project/khai-sdk/${{ needs.build.outputs.version }}/
|
|
137
|
+
permissions:
|
|
138
|
+
id-token: write
|
|
139
|
+
steps:
|
|
140
|
+
- uses: actions/download-artifact@v4
|
|
141
|
+
with:
|
|
142
|
+
name: dist
|
|
143
|
+
path: dist/
|
|
144
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
145
|
+
|
|
146
|
+
github-release:
|
|
147
|
+
name: Create GitHub Release
|
|
148
|
+
needs: [build, publish-pypi]
|
|
149
|
+
runs-on: ubuntu-latest
|
|
150
|
+
timeout-minutes: 5
|
|
151
|
+
permissions:
|
|
152
|
+
contents: write
|
|
153
|
+
steps:
|
|
154
|
+
- uses: actions/checkout@v4
|
|
155
|
+
- uses: actions/download-artifact@v4
|
|
156
|
+
with:
|
|
157
|
+
name: dist
|
|
158
|
+
path: dist/
|
|
159
|
+
- name: Create release with artifacts
|
|
160
|
+
env:
|
|
161
|
+
GH_TOKEN: ${{ github.token }}
|
|
162
|
+
run: |
|
|
163
|
+
gh release create "$GITHUB_REF_NAME" dist/* \
|
|
164
|
+
--title "$GITHUB_REF_NAME" \
|
|
165
|
+
--generate-notes \
|
|
166
|
+
--verify-tag
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
pytest:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
timeout-minutes: 10
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
cache: pip
|
|
25
|
+
- name: Install
|
|
26
|
+
run: python -m pip install --upgrade pip && pip install -e ".[dev]"
|
|
27
|
+
- name: Test
|
|
28
|
+
run: pytest -q
|
|
29
|
+
- name: Build sdist and wheel
|
|
30
|
+
run: pip install build && python -m build
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the Khai Python SDK. Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer.
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- Release pipeline (`.github/workflows/release.yml`): a `v*` tag builds once,
|
|
10
|
+
publishes to TestPyPI, smoke-installs from there, then publishes to PyPI via
|
|
11
|
+
trusted publishing (OIDC, no stored tokens) behind a required reviewer, and
|
|
12
|
+
creates a GitHub Release with the artifacts.
|
|
13
|
+
- `IngestTurn` now fills `response_id` (UUID4) and `timestamp` (UTC ISO-8601) at
|
|
14
|
+
creation when the caller omits them, so retries are deduplicated server-side
|
|
15
|
+
instead of stored twice.
|
|
16
|
+
- `KHAI_API_KEY` and `KHAI_BASE_URL` environment fallbacks; `api_key` and
|
|
17
|
+
`base_url` are optional constructor arguments.
|
|
18
|
+
- `max_queue_size` (default 10,000) bounds the fire-and-forget queue and the
|
|
19
|
+
retry buffer. Drops are reported via `on_error` as the new `KhaiQueueFullError`.
|
|
20
|
+
- Open clients are flushed once at interpreter exit.
|
|
21
|
+
- `khai.__version__` is the single source of the version (hatch dynamic
|
|
22
|
+
version from `src/khai/_version.py`) and is stamped into the `User-Agent`.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Distribution name is `khai-sdk` (install with `pip install khai-sdk`, then
|
|
26
|
+
`import khai`). PyPI rejects `khai` as confusable with the existing `khal`
|
|
27
|
+
project, so the shorter name is not available.
|
|
28
|
+
- `base_url` must be `https://`; `http://` is accepted only for localhost.
|
|
29
|
+
- `close()` is idempotent.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- Error messages from the backend's auth and rate-limit layer were lost: those
|
|
33
|
+
responses use FastAPI's `{"detail": ...}` envelope, and the SDK only read
|
|
34
|
+
`{"error": ...}`. A rejected key surfaced as "request failed"; it now
|
|
35
|
+
surfaces as "Invalid or inactive API key." 422 validation lists are
|
|
36
|
+
flattened into one message.
|
|
37
|
+
|
|
38
|
+
## [0.1.0] - 2026-08-04
|
|
39
|
+
|
|
40
|
+
Initial hand-written SDK: `ingest_turn`, `ingest_batch`, `ingest_turn_async`,
|
|
41
|
+
`set_redactor`, typed errors, retries with exponential backoff.
|
khai_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 brise.ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
khai_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: khai-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for streaming chatbot turns to Khai for evaluation.
|
|
5
|
+
Project-URL: Homepage, https://getkhai.ai
|
|
6
|
+
Project-URL: Source, https://github.com/KHAI-BE/khai-python
|
|
7
|
+
Project-URL: Changelog, https://github.com/KHAI-BE/khai-python/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/KHAI-BE/khai-python/issues
|
|
9
|
+
Author-email: Khai <support@getkhai.ai>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: chatbot,evaluation,khai,llm,observability
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx>=0.24
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
26
|
+
Requires-Dist: respx>=0.20; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Khai Python SDK
|
|
30
|
+
|
|
31
|
+
Stream chatbot turns to Khai for evaluation from any cloud — no access granted to Khai.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install khai-sdk
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quickstart
|
|
40
|
+
|
|
41
|
+
Set your key in the environment rather than in source:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export KHAI_API_KEY="khai_..."
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from khai import KhaiClient
|
|
49
|
+
|
|
50
|
+
khai = KhaiClient() # reads KHAI_API_KEY; base_url defaults to https://api.getkhai.ai
|
|
51
|
+
|
|
52
|
+
resp = khai.ingest_turn(
|
|
53
|
+
user_query="How do I pay my bill?",
|
|
54
|
+
agent_response="You can pay online at ...",
|
|
55
|
+
session_id="chat-123",
|
|
56
|
+
agent_id="YOUR_AGENT_ID",
|
|
57
|
+
)
|
|
58
|
+
print(resp.status, resp.result.trust_score if resp.evaluated else resp.reason)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Every turn is idempotent
|
|
62
|
+
|
|
63
|
+
You do not have to think about retries. When you omit them, the SDK fills in a
|
|
64
|
+
`timestamp` (UTC, ISO-8601) and a UUID `response_id` **when the turn is created**,
|
|
65
|
+
and keeps them for every retry. The backend deduplicates on `response_id`, so a
|
|
66
|
+
turn resent after a timeout, a 5xx, or a network outage is answered with
|
|
67
|
+
`status: duplicate` instead of being stored twice.
|
|
68
|
+
|
|
69
|
+
If your platform already has a per-reply id (Dialogflow `responseId`, Lex,
|
|
70
|
+
Copilot Studio), pass it as `response_id` and the same guarantee holds even
|
|
71
|
+
across restarts of your own process.
|
|
72
|
+
|
|
73
|
+
## Fire-and-forget
|
|
74
|
+
|
|
75
|
+
`ingest_turn_async` queues the turn and returns immediately, so it never blocks
|
|
76
|
+
your chatbot's response path. Failed deliveries are buffered and retried. Both
|
|
77
|
+
the queue and the buffer are capped at `max_queue_size` (default 10,000 turns);
|
|
78
|
+
when full, the SDK drops a turn and reports it through `on_error` as
|
|
79
|
+
`KhaiQueueFullError` rather than growing memory during an outage. Open clients
|
|
80
|
+
are flushed once at interpreter exit.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
with KhaiClient() as khai:
|
|
84
|
+
khai.ingest_turn_async(
|
|
85
|
+
user_query=user_msg,
|
|
86
|
+
agent_response=bot_reply,
|
|
87
|
+
session_id=session_id,
|
|
88
|
+
agent_id="YOUR_AGENT_ID",
|
|
89
|
+
)
|
|
90
|
+
# ... keep serving; turns flush in the background, and on close().
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Batch
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from khai import IngestTurn
|
|
97
|
+
|
|
98
|
+
khai.ingest_batch([
|
|
99
|
+
IngestTurn(user_query="...", agent_response="...", session_id="s1", agent_id="a1"),
|
|
100
|
+
IngestTurn(user_query="...", agent_response="...", session_id="s1", agent_id="a1"),
|
|
101
|
+
])
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Redact PII before sending
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
def redact(payload: dict) -> dict:
|
|
108
|
+
payload["user_query"] = mask_emails(payload["user_query"])
|
|
109
|
+
return payload
|
|
110
|
+
|
|
111
|
+
khai.set_redactor(redact)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Errors
|
|
115
|
+
|
|
116
|
+
All errors derive from `KhaiError`:
|
|
117
|
+
|
|
118
|
+
| Error | Meaning |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `KhaiAuthError` | API key missing/invalid (401/403) |
|
|
121
|
+
| `KhaiRateLimitError` | Per-key rate limit exceeded (429) |
|
|
122
|
+
| `KhaiAPIError` | Other non-success status |
|
|
123
|
+
| `KhaiConnectionError` | Network/timeout (already retried) |
|
|
124
|
+
| `KhaiQueueFullError` | Fire-and-forget queue/buffer full; a turn was dropped (via `on_error`) |
|
|
125
|
+
| `KhaiConfigError` | Bad client configuration (missing key, non-HTTPS base URL) |
|
|
126
|
+
|
|
127
|
+
## Configuration
|
|
128
|
+
|
|
129
|
+
| Setting | Argument | Environment variable | Default |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| API key | `api_key` | `KHAI_API_KEY` | required |
|
|
132
|
+
| Base URL | `base_url` | `KHAI_BASE_URL` | `https://api.getkhai.ai` |
|
|
133
|
+
| Request timeout (s) | `timeout` | — | `10.0` |
|
|
134
|
+
| Retries on 429/5xx/network | `max_retries` | — | `3` |
|
|
135
|
+
| Queue / buffer cap | `max_queue_size` | — | `10000` |
|
|
136
|
+
|
|
137
|
+
`base_url` must be `https://`; plain `http://` is accepted only for `localhost`.
|
|
138
|
+
|
|
139
|
+
## Releasing (maintainers)
|
|
140
|
+
|
|
141
|
+
Releases are cut from tags and published by CI through PyPI trusted publishing;
|
|
142
|
+
no one uploads from a laptop and no API token is stored anywhere.
|
|
143
|
+
|
|
144
|
+
1. Bump `__version__` in `src/khai/_version.py` and move the `Unreleased`
|
|
145
|
+
entries in `CHANGELOG.md` under the new version. Merge via pull request.
|
|
146
|
+
2. Tag the merged commit and push the tag:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
3. The `Release` workflow builds the sdist and wheel, publishes to TestPyPI,
|
|
153
|
+
installs from TestPyPI and imports the package, then waits for a reviewer to
|
|
154
|
+
approve the `pypi` environment before publishing to PyPI and creating the
|
|
155
|
+
GitHub Release. A tag whose version does not match `__version__` fails fast.
|
khai_sdk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Khai Python SDK
|
|
2
|
+
|
|
3
|
+
Stream chatbot turns to Khai for evaluation from any cloud — no access granted to Khai.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install khai-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
Set your key in the environment rather than in source:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
export KHAI_API_KEY="khai_..."
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from khai import KhaiClient
|
|
21
|
+
|
|
22
|
+
khai = KhaiClient() # reads KHAI_API_KEY; base_url defaults to https://api.getkhai.ai
|
|
23
|
+
|
|
24
|
+
resp = khai.ingest_turn(
|
|
25
|
+
user_query="How do I pay my bill?",
|
|
26
|
+
agent_response="You can pay online at ...",
|
|
27
|
+
session_id="chat-123",
|
|
28
|
+
agent_id="YOUR_AGENT_ID",
|
|
29
|
+
)
|
|
30
|
+
print(resp.status, resp.result.trust_score if resp.evaluated else resp.reason)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Every turn is idempotent
|
|
34
|
+
|
|
35
|
+
You do not have to think about retries. When you omit them, the SDK fills in a
|
|
36
|
+
`timestamp` (UTC, ISO-8601) and a UUID `response_id` **when the turn is created**,
|
|
37
|
+
and keeps them for every retry. The backend deduplicates on `response_id`, so a
|
|
38
|
+
turn resent after a timeout, a 5xx, or a network outage is answered with
|
|
39
|
+
`status: duplicate` instead of being stored twice.
|
|
40
|
+
|
|
41
|
+
If your platform already has a per-reply id (Dialogflow `responseId`, Lex,
|
|
42
|
+
Copilot Studio), pass it as `response_id` and the same guarantee holds even
|
|
43
|
+
across restarts of your own process.
|
|
44
|
+
|
|
45
|
+
## Fire-and-forget
|
|
46
|
+
|
|
47
|
+
`ingest_turn_async` queues the turn and returns immediately, so it never blocks
|
|
48
|
+
your chatbot's response path. Failed deliveries are buffered and retried. Both
|
|
49
|
+
the queue and the buffer are capped at `max_queue_size` (default 10,000 turns);
|
|
50
|
+
when full, the SDK drops a turn and reports it through `on_error` as
|
|
51
|
+
`KhaiQueueFullError` rather than growing memory during an outage. Open clients
|
|
52
|
+
are flushed once at interpreter exit.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
with KhaiClient() as khai:
|
|
56
|
+
khai.ingest_turn_async(
|
|
57
|
+
user_query=user_msg,
|
|
58
|
+
agent_response=bot_reply,
|
|
59
|
+
session_id=session_id,
|
|
60
|
+
agent_id="YOUR_AGENT_ID",
|
|
61
|
+
)
|
|
62
|
+
# ... keep serving; turns flush in the background, and on close().
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Batch
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from khai import IngestTurn
|
|
69
|
+
|
|
70
|
+
khai.ingest_batch([
|
|
71
|
+
IngestTurn(user_query="...", agent_response="...", session_id="s1", agent_id="a1"),
|
|
72
|
+
IngestTurn(user_query="...", agent_response="...", session_id="s1", agent_id="a1"),
|
|
73
|
+
])
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Redact PII before sending
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
def redact(payload: dict) -> dict:
|
|
80
|
+
payload["user_query"] = mask_emails(payload["user_query"])
|
|
81
|
+
return payload
|
|
82
|
+
|
|
83
|
+
khai.set_redactor(redact)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Errors
|
|
87
|
+
|
|
88
|
+
All errors derive from `KhaiError`:
|
|
89
|
+
|
|
90
|
+
| Error | Meaning |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `KhaiAuthError` | API key missing/invalid (401/403) |
|
|
93
|
+
| `KhaiRateLimitError` | Per-key rate limit exceeded (429) |
|
|
94
|
+
| `KhaiAPIError` | Other non-success status |
|
|
95
|
+
| `KhaiConnectionError` | Network/timeout (already retried) |
|
|
96
|
+
| `KhaiQueueFullError` | Fire-and-forget queue/buffer full; a turn was dropped (via `on_error`) |
|
|
97
|
+
| `KhaiConfigError` | Bad client configuration (missing key, non-HTTPS base URL) |
|
|
98
|
+
|
|
99
|
+
## Configuration
|
|
100
|
+
|
|
101
|
+
| Setting | Argument | Environment variable | Default |
|
|
102
|
+
|---|---|---|---|
|
|
103
|
+
| API key | `api_key` | `KHAI_API_KEY` | required |
|
|
104
|
+
| Base URL | `base_url` | `KHAI_BASE_URL` | `https://api.getkhai.ai` |
|
|
105
|
+
| Request timeout (s) | `timeout` | — | `10.0` |
|
|
106
|
+
| Retries on 429/5xx/network | `max_retries` | — | `3` |
|
|
107
|
+
| Queue / buffer cap | `max_queue_size` | — | `10000` |
|
|
108
|
+
|
|
109
|
+
`base_url` must be `https://`; plain `http://` is accepted only for `localhost`.
|
|
110
|
+
|
|
111
|
+
## Releasing (maintainers)
|
|
112
|
+
|
|
113
|
+
Releases are cut from tags and published by CI through PyPI trusted publishing;
|
|
114
|
+
no one uploads from a laptop and no API token is stored anywhere.
|
|
115
|
+
|
|
116
|
+
1. Bump `__version__` in `src/khai/_version.py` and move the `Unreleased`
|
|
117
|
+
entries in `CHANGELOG.md` under the new version. Merge via pull request.
|
|
118
|
+
2. Tag the merged commit and push the tag:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
3. The `Release` workflow builds the sdist and wheel, publishes to TestPyPI,
|
|
125
|
+
installs from TestPyPI and imports the package, then waits for a reviewer to
|
|
126
|
+
approve the `pypi` environment before publishing to PyPI and creating the
|
|
127
|
+
GitHub Release. A tag whose version does not match `__version__` fails fast.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
Report vulnerabilities in this SDK privately to admin@brise.ai. Do not open
|
|
4
|
+
a public issue for security reports. We acknowledge reports within three
|
|
5
|
+
business days.
|
|
6
|
+
|
|
7
|
+
Only the latest minor release receives security fixes.
|
|
8
|
+
|
|
9
|
+
The SDK never logs or prints your API key. Pass it via the `KHAI_API_KEY`
|
|
10
|
+
environment variable rather than embedding it in source.
|