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.
Files changed (38) hide show
  1. klavi_experts-0.1.0/.env.example +6 -0
  2. klavi_experts-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +38 -0
  3. klavi_experts-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. klavi_experts-0.1.0/.github/pull_request_template.md +11 -0
  5. klavi_experts-0.1.0/.github/workflows/ci.yml +50 -0
  6. klavi_experts-0.1.0/.github/workflows/release.yml +42 -0
  7. klavi_experts-0.1.0/.gitignore +16 -0
  8. klavi_experts-0.1.0/CODE_OF_CONDUCT.md +36 -0
  9. klavi_experts-0.1.0/CONTRIBUTING.md +56 -0
  10. klavi_experts-0.1.0/LICENSE +21 -0
  11. klavi_experts-0.1.0/PKG-INFO +218 -0
  12. klavi_experts-0.1.0/README.md +187 -0
  13. klavi_experts-0.1.0/SECURITY.md +33 -0
  14. klavi_experts-0.1.0/examples/README.md +10 -0
  15. klavi_experts-0.1.0/examples/cli/ask.py +72 -0
  16. klavi_experts-0.1.0/examples/django_ingest/ingest.py +58 -0
  17. klavi_experts-0.1.0/examples/fastapi_chatbox/app.py +74 -0
  18. klavi_experts-0.1.0/pyproject.toml +66 -0
  19. klavi_experts-0.1.0/spec/README.md +22 -0
  20. klavi_experts-0.1.0/spec/openapi.json +8813 -0
  21. klavi_experts-0.1.0/src/klavi_experts/__init__.py +95 -0
  22. klavi_experts-0.1.0/src/klavi_experts/_chat_stream.py +245 -0
  23. klavi_experts-0.1.0/src/klavi_experts/_stream.py +137 -0
  24. klavi_experts-0.1.0/src/klavi_experts/_transport.py +291 -0
  25. klavi_experts-0.1.0/src/klavi_experts/async_client.py +313 -0
  26. klavi_experts-0.1.0/src/klavi_experts/client.py +349 -0
  27. klavi_experts-0.1.0/src/klavi_experts/errors.py +178 -0
  28. klavi_experts-0.1.0/src/klavi_experts/py.typed +0 -0
  29. klavi_experts-0.1.0/src/klavi_experts/resources/__init__.py +0 -0
  30. klavi_experts-0.1.0/src/klavi_experts/resources/conversations.py +189 -0
  31. klavi_experts-0.1.0/src/klavi_experts/resources/experts.py +17 -0
  32. klavi_experts-0.1.0/src/klavi_experts/resources/knowledge.py +121 -0
  33. klavi_experts-0.1.0/src/klavi_experts/resources/sessions.py +57 -0
  34. klavi_experts-0.1.0/src/klavi_experts/types.py +330 -0
  35. klavi_experts-0.1.0/tests/live/test_integration.py +161 -0
  36. klavi_experts-0.1.0/tests/test_async_parity.py +218 -0
  37. klavi_experts-0.1.0/tests/test_client.py +304 -0
  38. klavi_experts-0.1.0/tests/test_stream.py +175 -0
@@ -0,0 +1,6 @@
1
+ # Point the live test suite (pytest tests/live) at an install.
2
+ EXPERTS_BASE_URL=http://localhost:5003
3
+ EXPERTS_TEST_KEY=sk-...
4
+ # Optional: an expert uid to exercise, and an origin registered against the key.
5
+ EXPERTS_TEST_EXPERT=
6
+ EXPERTS_TEST_ORIGIN=
@@ -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,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/klv-ai/experts-python-sdk/security/advisories/new
5
+ about: Report privately. Please do not open a public issue.
@@ -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,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ build/
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
13
+ .DS_Store
14
+ .env
15
+ .env.*
16
+ !.env.example
@@ -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).