klavi-experts 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.
- klavi_experts-0.1.0/.env.example +6 -0
- klavi_experts-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +38 -0
- klavi_experts-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- klavi_experts-0.1.0/.github/pull_request_template.md +11 -0
- klavi_experts-0.1.0/.github/workflows/ci.yml +50 -0
- klavi_experts-0.1.0/.github/workflows/release.yml +42 -0
- klavi_experts-0.1.0/.gitignore +16 -0
- klavi_experts-0.1.0/CODE_OF_CONDUCT.md +36 -0
- klavi_experts-0.1.0/CONTRIBUTING.md +56 -0
- klavi_experts-0.1.0/LICENSE +21 -0
- klavi_experts-0.1.0/PKG-INFO +218 -0
- klavi_experts-0.1.0/README.md +187 -0
- klavi_experts-0.1.0/SECURITY.md +33 -0
- klavi_experts-0.1.0/examples/README.md +10 -0
- klavi_experts-0.1.0/examples/cli/ask.py +72 -0
- klavi_experts-0.1.0/examples/django_ingest/ingest.py +58 -0
- klavi_experts-0.1.0/examples/fastapi_chatbox/app.py +74 -0
- klavi_experts-0.1.0/pyproject.toml +66 -0
- klavi_experts-0.1.0/spec/README.md +22 -0
- klavi_experts-0.1.0/spec/openapi.json +8813 -0
- klavi_experts-0.1.0/src/klavi_experts/__init__.py +95 -0
- klavi_experts-0.1.0/src/klavi_experts/_chat_stream.py +245 -0
- klavi_experts-0.1.0/src/klavi_experts/_stream.py +137 -0
- klavi_experts-0.1.0/src/klavi_experts/_transport.py +291 -0
- klavi_experts-0.1.0/src/klavi_experts/async_client.py +313 -0
- klavi_experts-0.1.0/src/klavi_experts/client.py +349 -0
- klavi_experts-0.1.0/src/klavi_experts/errors.py +178 -0
- klavi_experts-0.1.0/src/klavi_experts/py.typed +0 -0
- klavi_experts-0.1.0/src/klavi_experts/resources/__init__.py +0 -0
- klavi_experts-0.1.0/src/klavi_experts/resources/conversations.py +189 -0
- klavi_experts-0.1.0/src/klavi_experts/resources/experts.py +17 -0
- klavi_experts-0.1.0/src/klavi_experts/resources/knowledge.py +121 -0
- klavi_experts-0.1.0/src/klavi_experts/resources/sessions.py +57 -0
- klavi_experts-0.1.0/src/klavi_experts/types.py +330 -0
- klavi_experts-0.1.0/tests/live/test_integration.py +161 -0
- klavi_experts-0.1.0/tests/test_async_parity.py +218 -0
- klavi_experts-0.1.0/tests/test_client.py +304 -0
- klavi_experts-0.1.0/tests/test_stream.py +175 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something the SDK does wrong
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened
|
|
9
|
+
description: What you expected, and what you got instead.
|
|
10
|
+
validations: { required: true }
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: repro
|
|
13
|
+
attributes:
|
|
14
|
+
label: Reproduction
|
|
15
|
+
description: The smallest snippet that shows it. Redact your API key.
|
|
16
|
+
render: python
|
|
17
|
+
validations: { required: true }
|
|
18
|
+
- type: input
|
|
19
|
+
id: versions
|
|
20
|
+
attributes:
|
|
21
|
+
label: Versions
|
|
22
|
+
description: SDK version, Python version, and the install's build pack version.
|
|
23
|
+
placeholder: "klavi-experts 0.1.0, Python 3.12, pack 1.0.49"
|
|
24
|
+
validations: { required: true }
|
|
25
|
+
- type: dropdown
|
|
26
|
+
id: client
|
|
27
|
+
attributes:
|
|
28
|
+
label: Which client
|
|
29
|
+
options: [ExpertsClient (sync), AsyncExpertsClient (async), both]
|
|
30
|
+
validations: { required: true }
|
|
31
|
+
- type: textarea
|
|
32
|
+
id: raw
|
|
33
|
+
attributes:
|
|
34
|
+
label: Raw request and response
|
|
35
|
+
description: >
|
|
36
|
+
If the API itself looks wrong, paste the request and response. Some
|
|
37
|
+
problems are fixed on the server rather than in the SDK, and this is
|
|
38
|
+
how we tell which.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## What this changes
|
|
2
|
+
|
|
3
|
+
<!-- And why. If it fixes a bug, say what the wrong behaviour was. -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] A test that fails without this change
|
|
8
|
+
- [ ] `ruff check`, `mypy src` and `pytest` pass
|
|
9
|
+
- [ ] No new runtime dependencies (httpx is the only one)
|
|
10
|
+
- [ ] Sync and async halves still agree (`tests/test_async_parity.py`)
|
|
11
|
+
- [ ] Comments explain *why*, where the reason is not obvious from the code
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
# 3.10 is the floor (PEP 604 unions in annotations); 3.13 catches
|
|
15
|
+
# what is coming.
|
|
16
|
+
python: ["3.10", "3.11", "3.12", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python }}
|
|
22
|
+
cache: pip
|
|
23
|
+
- run: pip install -e ".[dev]"
|
|
24
|
+
- run: ruff check src tests
|
|
25
|
+
- run: mypy src
|
|
26
|
+
- run: pytest -q
|
|
27
|
+
|
|
28
|
+
# The unit suite stubs the transport, so it cannot see the SDK drifting from
|
|
29
|
+
# the services it wraps. This can. It needs a live install, so it only runs
|
|
30
|
+
# where the secret exists — never on a fork's pull request.
|
|
31
|
+
live:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
- uses: actions/setup-python@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.12"
|
|
39
|
+
cache: pip
|
|
40
|
+
- run: pip install -e ".[dev]"
|
|
41
|
+
- name: Integration tests
|
|
42
|
+
env:
|
|
43
|
+
EXPERTS_BASE_URL: ${{ secrets.EXPERTS_BASE_URL }}
|
|
44
|
+
EXPERTS_TEST_KEY: ${{ secrets.EXPERTS_TEST_KEY }}
|
|
45
|
+
run: |
|
|
46
|
+
if [ -z "$EXPERTS_TEST_KEY" ]; then
|
|
47
|
+
echo "::notice::No EXPERTS_TEST_KEY configured; skipping live tests."
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
pytest tests/live -q
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
# PyPI trusted publishing: PyPI verifies this workflow's OIDC identity
|
|
14
|
+
# instead of us storing a long-lived API token. Same purpose as npm
|
|
15
|
+
# provenance on the Node SDK.
|
|
16
|
+
id-token: write
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
- run: pip install -e ".[dev]" build
|
|
23
|
+
- run: ruff check src tests
|
|
24
|
+
- run: mypy src
|
|
25
|
+
- run: pytest -q
|
|
26
|
+
- name: Check the tag matches the package version
|
|
27
|
+
run: |
|
|
28
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
29
|
+
PKG=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
|
|
30
|
+
if [ "$TAG" != "$PKG" ]; then
|
|
31
|
+
echo "Tag $TAG does not match pyproject version $PKG" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
# __version__ is what a user prints at runtime; a disagreement there
|
|
35
|
+
# is how a bug report ends up naming the wrong release.
|
|
36
|
+
MOD=$(python -c "import re,pathlib; print(re.search(r'__version__ = \"([^\"]+)\"', pathlib.Path('src/klavi_experts/__init__.py').read_text()).group(1))")
|
|
37
|
+
if [ "$TAG" != "$MOD" ]; then
|
|
38
|
+
echo "Tag $TAG does not match __version__ $MOD" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
- run: python -m build
|
|
42
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our pledge
|
|
4
|
+
|
|
5
|
+
We want participation in this project to be a harassment-free experience for
|
|
6
|
+
everyone, regardless of age, body size, visible or invisible disability,
|
|
7
|
+
ethnicity, sex characteristics, gender identity and expression, level of
|
|
8
|
+
experience, education, socio-economic status, nationality, personal appearance,
|
|
9
|
+
race, religion, or sexual identity and orientation.
|
|
10
|
+
|
|
11
|
+
## Our standards
|
|
12
|
+
|
|
13
|
+
Things that help: empathy and kindness, respect for differing opinions and
|
|
14
|
+
experiences, graceful acceptance of constructive feedback, taking
|
|
15
|
+
responsibility for our mistakes, and focusing on what is best for the community
|
|
16
|
+
rather than for ourselves.
|
|
17
|
+
|
|
18
|
+
Things that do not: sexualised language or imagery, trolling, insulting or
|
|
19
|
+
derogatory comments, personal or political attacks, public or private
|
|
20
|
+
harassment, publishing others' private information without permission, and
|
|
21
|
+
other conduct that would reasonably be considered inappropriate in a
|
|
22
|
+
professional setting.
|
|
23
|
+
|
|
24
|
+
## Enforcement
|
|
25
|
+
|
|
26
|
+
Report unacceptable behaviour to **conduct@klavi.ai**. All complaints will be
|
|
27
|
+
reviewed and investigated promptly and fairly, and the privacy and security of
|
|
28
|
+
the reporter will be respected.
|
|
29
|
+
|
|
30
|
+
Maintainers are responsible for clarifying and enforcing these standards, and
|
|
31
|
+
may remove, edit or reject contributions that do not align with them.
|
|
32
|
+
|
|
33
|
+
## Attribution
|
|
34
|
+
|
|
35
|
+
Adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
|
36
|
+
version 2.1.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. This SDK wraps a HTTP API that it does not control, so the
|
|
4
|
+
most valuable contributions are usually about *behaviour we got wrong* rather
|
|
5
|
+
than features we are missing.
|
|
6
|
+
|
|
7
|
+
## Getting set up
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python -m venv .venv && . .venv/bin/activate
|
|
11
|
+
pip install -e ".[dev]"
|
|
12
|
+
|
|
13
|
+
pytest # unit tests, no install required
|
|
14
|
+
ruff check src tests
|
|
15
|
+
mypy src
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Running against a real install
|
|
19
|
+
|
|
20
|
+
The unit tests use a stubbed transport, which cannot catch drift between this
|
|
21
|
+
SDK and the Python services it talks to. The live suite can:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cp .env.example .env # fill in EXPERTS_BASE_URL and EXPERTS_TEST_KEY
|
|
25
|
+
pytest tests/live
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
It creates only hidden conversations and deletes them afterwards. Point it at
|
|
29
|
+
a development install, never production.
|
|
30
|
+
|
|
31
|
+
## Sync and async
|
|
32
|
+
|
|
33
|
+
There are two clients, and they must not drift. Everything that is not I/O —
|
|
34
|
+
request building, response shaping, NDJSON framing, event mapping — is shared,
|
|
35
|
+
so a change to what a call MEANS is made once. `tests/test_async_parity.py`
|
|
36
|
+
compares their signatures and the requests they emit; if you add a parameter
|
|
37
|
+
to one half, that suite tells you about the other.
|
|
38
|
+
|
|
39
|
+
## What we look for in a change
|
|
40
|
+
|
|
41
|
+
- **A test that fails without it.** For a bug, that test should describe the
|
|
42
|
+
behaviour, not the fix.
|
|
43
|
+
- **Comments that explain why, not what.** Most of the surprising code here is
|
|
44
|
+
surprising because the API is, and the comment should say which part.
|
|
45
|
+
- **No new runtime dependencies.** `httpx` is the only one, on purpose.
|
|
46
|
+
|
|
47
|
+
## Reporting an API problem
|
|
48
|
+
|
|
49
|
+
If the API itself misbehaves — a shape that does not match its documentation, a
|
|
50
|
+
silent failure — open an issue with the raw request and response. Those are
|
|
51
|
+
often fixed on the server rather than papered over here, and knowing which is
|
|
52
|
+
the point of the report.
|
|
53
|
+
|
|
54
|
+
## Security
|
|
55
|
+
|
|
56
|
+
Do not open an issue for a vulnerability. See [SECURITY.md](SECURITY.md).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Klavi 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.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: klavi-experts
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Klavi Experts API — conversations, streaming, experts, and knowledge.
|
|
5
|
+
Project-URL: Homepage, https://github.com/klv-ai/experts-python-sdk
|
|
6
|
+
Project-URL: Repository, https://github.com/klv-ai/experts-python-sdk
|
|
7
|
+
Project-URL: Issues, https://github.com/klv-ai/experts-python-sdk/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/klv-ai/experts-python-sdk/releases
|
|
9
|
+
Author: Klavi AI
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai,chatbot,experts,klavi,llm,rag,sdk
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
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 :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx<1,>=0.27
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
28
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# klavi-experts
|
|
33
|
+
|
|
34
|
+
The official Python SDK for the [Klavi Experts](https://github.com/klv-ai) API — AI experts with retrieval over your own documents, running on your own infrastructure.
|
|
35
|
+
|
|
36
|
+
Sync and async. One dependency (`httpx`). Python 3.10+.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install klavi-experts
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Thirty seconds
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from klavi_experts import ExpertsClient
|
|
46
|
+
|
|
47
|
+
experts = ExpertsClient(
|
|
48
|
+
base_url="https://experts.acme.com",
|
|
49
|
+
api_key=os.environ["EXPERTS_API_KEY"], # server-side only
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
for event in experts.conversations.ask("What's our refund policy?"):
|
|
53
|
+
if event.type == "token":
|
|
54
|
+
print(event.content, end="", flush=True)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or, when you just want the answer:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
answer = experts.conversations.ask("Summarise Q3.").text()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Async is the same API:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from klavi_experts import AsyncExpertsClient
|
|
67
|
+
|
|
68
|
+
async with AsyncExpertsClient(base_url=..., api_key=...) as experts:
|
|
69
|
+
stream = await experts.conversations.ask("Summarise Q3.")
|
|
70
|
+
async for event in stream:
|
|
71
|
+
...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Why not just call the API
|
|
75
|
+
|
|
76
|
+
Sending one message is three HTTP calls in a specific order, and getting it wrong fails *silently* — the model answers, the stream looks fine, and nothing is saved:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
POST /api/v1/responses the user's turn {agent: False, done: True}
|
|
80
|
+
POST /api/v1/responses assistant placeholder {agent: True, done: False} ← keep uid
|
|
81
|
+
POST /api/v1/conversations/chat {response: <placeholder uid>, ...}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`conversations.send()` is that sequence. The rest of the SDK is the same idea applied to the parts of the API that are easy to get subtly wrong — see [Things worth knowing](#things-worth-knowing).
|
|
85
|
+
|
|
86
|
+
## Talking to an expert
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
experts_list = experts.experts.list()
|
|
90
|
+
|
|
91
|
+
conversation = experts.conversations.create(
|
|
92
|
+
expert=experts_list[0].uid,
|
|
93
|
+
title="Support",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
with experts.conversations.send(conversation.uid, "Hello") as stream:
|
|
97
|
+
for event in stream:
|
|
98
|
+
match event.type:
|
|
99
|
+
case "token": print(event.content, end="", flush=True)
|
|
100
|
+
case "thinking": ... # reasoning tokens, when the model exposes them
|
|
101
|
+
case "action": ... # "rag_search", "tool_call" — for a status line
|
|
102
|
+
case "done": print(event.usage, event.source_docs)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Everything the answer was grounded in comes back on the terminal event:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
result = stream.result()
|
|
109
|
+
result.text # the full reply
|
|
110
|
+
result.source_docs # documents retrieved, with similarity scores
|
|
111
|
+
result.tools_used # e.g. ["web_search"]
|
|
112
|
+
result.usage.total_ms # milliseconds (the API speaks nanoseconds)
|
|
113
|
+
result.error # set when generation failed inside a 200 — always check
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Stopping
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
stream.cancel() # stops the model
|
|
120
|
+
stream.detach() # stop reading; let it finish and persist
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
These are genuinely different. Generation is **detached** from the HTTP request server-side, so simply walking away stops the relay, not the model — it keeps generating, keeps costing you, and still writes its answer. `cancel()` is the only thing that stops it.
|
|
124
|
+
|
|
125
|
+
## A chatbox on your website
|
|
126
|
+
|
|
127
|
+
An API key is a full user identity on the install, so it must never reach a browser. Instead your Python server mints a short-lived session token bound to one expert, one conversation and one origin.
|
|
128
|
+
|
|
129
|
+
**Register the origin first** against your API key, in the install's admin. That registration is also what supplies CORS for this surface.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# Django / Flask / FastAPI — your /api/chat/session route
|
|
133
|
+
session = experts.sessions.create(expert=EXPERT_UID, origin="https://acme.com")
|
|
134
|
+
return {"token": session.token}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The browser then talks to the install directly, using [`@klv-ai/experts/browser`](https://www.npmjs.com/package/@klv-ai/experts) or plain `fetch`. Your server is involved once, to mint — you are not proxying every token.
|
|
138
|
+
|
|
139
|
+
**The expert must be guest-visible.** A session token carries the guest role, and an expert above that floor resolves to nothing server-side — the chat then answers on the site's default model with no persona and no knowledge, silently, with a normal 200. `sessions.create()` refuses such an expert up front and tells you how to fix it. `experts.experts.list_guest_visible()` gives you the ones that will work.
|
|
140
|
+
|
|
141
|
+
## Knowledge
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
collections = experts.knowledge.list_collections()
|
|
145
|
+
|
|
146
|
+
with open("policy.pdf", "rb") as f:
|
|
147
|
+
doc = experts.knowledge.upload(f, collection=collections[0].uid, filename="policy.pdf")
|
|
148
|
+
|
|
149
|
+
# Uploading is asynchronous: the file is accepted, then split, embedded and
|
|
150
|
+
# indexed by a worker. It is NOT searchable until that finishes.
|
|
151
|
+
experts.knowledge.wait_for_processing(doc.uid)
|
|
152
|
+
|
|
153
|
+
hits = experts.knowledge.search("refund policy")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Errors
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from klavi_experts import RateLimitError, LicenseError
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
experts.conversations.ask("hi").text()
|
|
163
|
+
except RateLimitError as e:
|
|
164
|
+
time.sleep(e.retry_after or 5)
|
|
165
|
+
except LicenseError:
|
|
166
|
+
# The INSTALL's licence has lapsed. Nothing about your request is wrong and
|
|
167
|
+
# no retry will help — its administrator has to renew.
|
|
168
|
+
...
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
429 and 5xx are retried automatically with backoff that honours `Retry-After`. A 4xx never is. Neither is a chat turn — retrying one bills twice and can produce two answers.
|
|
172
|
+
|
|
173
|
+
## Things worth knowing
|
|
174
|
+
|
|
175
|
+
These are the parts of the API that surprise people. The SDK handles each of them; they are listed so you know what it is doing on your behalf.
|
|
176
|
+
|
|
177
|
+
| | |
|
|
178
|
+
|---|---|
|
|
179
|
+
| **Send both credentials and the wrong one wins** | The gateway checks `Authorization` first and that check is terminal. The SDK sends exactly one. |
|
|
180
|
+
| **A terminal chunk can be empty on purpose** | On the tool path the text already streamed. Appending terminal content renders tool-using answers twice. |
|
|
181
|
+
| **Durations are nanoseconds** | Ollama's units, passed straight through. Normalised to `*_ms` fields here. |
|
|
182
|
+
| **HTTP 200 can still be a failure** | Generation errors arrive in the terminal event, not the status code. Check `result.error`. |
|
|
183
|
+
| **404 can mean "forbidden"** | A conversation you may not see answers 404 so a uid probe reveals nothing. The SDK does not guess which it was. |
|
|
184
|
+
| **Casing is mixed by design** | Conversation rows are camelCase; message and expert rows are snake_case; knowledge responses are wrapped in an envelope. All normalised. |
|
|
185
|
+
| **Streaming is plain HTTP** | NDJSON over `POST`. No WebSocket is required for chat, contrary to some older notes. |
|
|
186
|
+
|
|
187
|
+
## OpenAI-compatible endpoint
|
|
188
|
+
|
|
189
|
+
If you already have code written against OpenAI, you may not need this SDK at all. An install also speaks the OpenAI chat-completions protocol, with an expert's uid as the model:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from openai import OpenAI
|
|
193
|
+
|
|
194
|
+
client = OpenAI(
|
|
195
|
+
api_key=os.environ["EXPERTS_API_KEY"],
|
|
196
|
+
base_url="https://experts.acme.com/api/openai/v1",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
client.chat.completions.create(
|
|
200
|
+
model=EXPERT_UID,
|
|
201
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
202
|
+
stream=True,
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Retrieval still happens; source documents come back under a `klavi` key on the final chunk. Use this SDK when you want conversations, knowledge management or browser sessions; use the OpenAI client when you want to drop an install into tooling that already speaks that protocol.
|
|
207
|
+
|
|
208
|
+
## Requirements
|
|
209
|
+
|
|
210
|
+
Python 3.10 or later, and an install running build pack **1.0.49** or later.
|
|
211
|
+
|
|
212
|
+
## Contributing
|
|
213
|
+
|
|
214
|
+
Issues and pull requests welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Security reports go to [SECURITY.md](SECURITY.md), not the issue tracker.
|
|
215
|
+
|
|
216
|
+
## Licence
|
|
217
|
+
|
|
218
|
+
MIT — see [LICENSE](LICENSE).
|