sse-lib 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.
- sse_lib-0.1.0/PKG-INFO +236 -0
- sse_lib-0.1.0/README.md +207 -0
- sse_lib-0.1.0/pyproject.toml +66 -0
- sse_lib-0.1.0/setup.cfg +4 -0
- sse_lib-0.1.0/src/sse_lib/__init__.py +119 -0
- sse_lib-0.1.0/src/sse_lib/client.py +1212 -0
- sse_lib-0.1.0/src/sse_lib/endpoints.py +115 -0
- sse_lib-0.1.0/src/sse_lib/exceptions.py +119 -0
- sse_lib-0.1.0/src/sse_lib/models.py +611 -0
- sse_lib-0.1.0/src/sse_lib/options.py +420 -0
- sse_lib-0.1.0/src/sse_lib/py.typed +0 -0
- sse_lib-0.1.0/src/sse_lib/transport.py +308 -0
- sse_lib-0.1.0/src/sse_lib.egg-info/PKG-INFO +236 -0
- sse_lib-0.1.0/src/sse_lib.egg-info/SOURCES.txt +27 -0
- sse_lib-0.1.0/src/sse_lib.egg-info/dependency_links.txt +1 -0
- sse_lib-0.1.0/src/sse_lib.egg-info/requires.txt +6 -0
- sse_lib-0.1.0/src/sse_lib.egg-info/top_level.txt +1 -0
- sse_lib-0.1.0/tests/test_chat_options.py +134 -0
- sse_lib-0.1.0/tests/test_client.py +860 -0
- sse_lib-0.1.0/tests/test_contract.py +204 -0
- sse_lib-0.1.0/tests/test_deletion.py +117 -0
- sse_lib-0.1.0/tests/test_documents.py +233 -0
- sse_lib-0.1.0/tests/test_health.py +103 -0
- sse_lib-0.1.0/tests/test_history_deletion.py +119 -0
- sse_lib-0.1.0/tests/test_options.py +228 -0
- sse_lib-0.1.0/tests/test_regressions.py +95 -0
- sse_lib-0.1.0/tests/test_required_options.py +141 -0
- sse_lib-0.1.0/tests/test_transport.py +205 -0
- sse_lib-0.1.0/tests/test_visibility.py +109 -0
sse_lib-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sse-lib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Thin Python client for the Semantic Search Engine (SSE) REST API
|
|
5
|
+
Author: RadLab
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/radlab-dev-group/semantic-search-engine
|
|
8
|
+
Project-URL: Repository, https://github.com/radlab-dev-group/semantic-search-engine
|
|
9
|
+
Project-URL: Issues, https://github.com/radlab-dev-group/semantic-search-engine/issues
|
|
10
|
+
Keywords: semantic-search,rag,rest,client,sse,search-engine
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
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.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: requests<3.0,>=2.31
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest<10.0,>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: black<26.0,>=24.1; extra == "dev"
|
|
28
|
+
Requires-Dist: flake8<8.0,>=7.0; extra == "dev"
|
|
29
|
+
|
|
30
|
+
# sse_lib
|
|
31
|
+
|
|
32
|
+
Thin Python client for the **Semantic Search Engine** (SSE) REST API
|
|
33
|
+
(`src/sse_api/` in this repository).
|
|
34
|
+
|
|
35
|
+
`sse_lib` is a client-side layer only: it maps Python calls onto the HTTP
|
|
36
|
+
endpoints of a running SSE backend and turns the JSON answers into small
|
|
37
|
+
dataclasses. It does **not** import Django, `torch`, `pymilvus` or any other
|
|
38
|
+
server dependency — the single runtime requirement is `requests`.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from sse_lib import SSEClient, SearchOptions, GenerativeOptions
|
|
42
|
+
|
|
43
|
+
client = SSEClient("http://localhost:8271", token="...")
|
|
44
|
+
|
|
45
|
+
client.create_collection(
|
|
46
|
+
"my_docs",
|
|
47
|
+
"My Documents",
|
|
48
|
+
"Test collection",
|
|
49
|
+
embedder="radlab/polish-bi-encoder-mean",
|
|
50
|
+
reranker="radlab/polish-cross-encoder",
|
|
51
|
+
index_type="HNSW",
|
|
52
|
+
chunking={"max_tokens_in_chunk": 128, "number_of_overlap_tokens": 0},
|
|
53
|
+
)
|
|
54
|
+
client.upload_files("my_docs", ["invoice.pdf", "regulations.zip"])
|
|
55
|
+
client.index_documents("my_docs", ["Some text to index."], category="Notes")
|
|
56
|
+
|
|
57
|
+
found = client.search("my_docs", "policy on data retention",
|
|
58
|
+
SearchOptions(max_results=20, rerank_results=True))
|
|
59
|
+
answer = client.generate_answer(
|
|
60
|
+
found.query_response_id,
|
|
61
|
+
GenerativeOptions(
|
|
62
|
+
generative_model="radlab/pLLama-3-8B-DPO-L",
|
|
63
|
+
percentage_rank_mass=40,
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
print(answer.answer)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Chunking belongs to the **collection**, not to a single call: declare it with
|
|
70
|
+
`create_collection(..., chunking={...})` (or `strict_chunking=True` to forbid any
|
|
71
|
+
deviation) and the indexing calls above may leave both keys out — the server takes
|
|
72
|
+
the settings of the collection. `client.embedders_info()` returns
|
|
73
|
+
`max_model_length` of every embedder, the largest chunk its model can take in
|
|
74
|
+
(`client.rerankers_info()` the pair budget of the rerankers). Passing
|
|
75
|
+
`max_tokens_in_chunk` / `number_of_overlap_tokens` per call, or once per client
|
|
76
|
+
with `SSEClient(..., default_indexing_options={...})`, still works; a split which
|
|
77
|
+
cannot exist (`overlap >= chunk`) raises `SSEValueError` before the request goes
|
|
78
|
+
out. `percentage_rank_mass` of `generate_answer` is required by the backend and
|
|
79
|
+
has no library-wide default on purpose.
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
From a checkout:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install ./sse_lib # or: pip install -e ./sse_lib (development)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Once published to a registry, `pip install sse-lib` is all that is needed — the
|
|
90
|
+
package is standalone and versioned independently (`sse_lib.__version__`).
|
|
91
|
+
|
|
92
|
+
Python 3.9+, `requests` as the only runtime dependency.
|
|
93
|
+
|
|
94
|
+
## Authentication
|
|
95
|
+
|
|
96
|
+
| Mode | How |
|
|
97
|
+
|------|-----|
|
|
98
|
+
| Pre-existing token (default DRF token auth) | `SSEClient(host, token="...")` — sent as `Authorization: Token <value>` |
|
|
99
|
+
| Login with user and password | `SSEClient(host, username="user", password="...")` — `POST login` runs lazily, before the first request |
|
|
100
|
+
| Keycloak / OAuth (backend started with `ENV_USE_KC_AUTH=1`) | `client.login_url()` → redirect the user → `client.login_with_code(code, state)`; later `client.refresh_access_token()` and `client.logout()` |
|
|
101
|
+
| Custom scheme | `SSEClient(host, token="...", token_type="Bearer")` |
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
| Constructor argument | Environment variable | Default | Meaning |
|
|
106
|
+
|----------------------|----------------------|---------|---------|
|
|
107
|
+
| `api_host` | `SSE_API_HOST` | – | `http://localhost:8271`; the scheme may be omitted (`localhost` → `http`, anything else → `https`) |
|
|
108
|
+
| `api_prefix` | `SSE_API_PREFIX` | `api` | Path between the host and the endpoints; set `api/v1` for versioned deployments (`main.api` in `django-config.json`) |
|
|
109
|
+
| `token` | `SSE_API_TOKEN` | – | Value of the `Authorization` header |
|
|
110
|
+
| `language` | `SSE_API_LANGUAGE` | `pl` | Sent as `lang` on every call (`pl` / `en`) |
|
|
111
|
+
| `timeout` | `SSE_API_TIMEOUT` | `60.0` | Per-request timeout in seconds |
|
|
112
|
+
| `verify`, `session`, `headers`, `max_retries` | – | `True`, new session, `{}`, `2` | TLS verification, shared `requests.Session`, extra headers, GET retries |
|
|
113
|
+
| `default_search_options`, `default_indexing_options` | – | – | Client-wide option defaults merged into every search / indexing call |
|
|
114
|
+
|
|
115
|
+
`SSEClient` is a context manager; leaving the block closes the HTTP session.
|
|
116
|
+
|
|
117
|
+
## API coverage
|
|
118
|
+
|
|
119
|
+
| Client method | REST endpoint |
|
|
120
|
+
|---------------|---------------|
|
|
121
|
+
| `create_collection(...)` | `POST new_collection` |
|
|
122
|
+
| `list_collections()`, `get_collection(name)`, `collection_exists(name)` | `GET collections` |
|
|
123
|
+
| `list_categories(collection)` | `GET categories` |
|
|
124
|
+
| `list_documents(collection, with_disabled=False)`, `list_document_details(collection)` | `GET documents` |
|
|
125
|
+
| `upload_files(collection, files, ...)` | `POST upload_and_index_files` |
|
|
126
|
+
| `document_content(collection, document, from_page=None, max_pages=None, chunk_type=None)` | `GET document_content` |
|
|
127
|
+
| `index_documents(collection, documents, ...)` | `POST add_and_index_texts` |
|
|
128
|
+
| `indexing_status(collection=None, job_id=None)` | `GET indexing_status` |
|
|
129
|
+
| `delete_texts(collection, chunk_ids)` | `POST delete_texts` |
|
|
130
|
+
| `delete_documents(collection, documents=None, document_ids=None)` | `POST delete_documents` |
|
|
131
|
+
| `delete_collection(collection)` | `POST delete_collection` |
|
|
132
|
+
| `set_documents_use_in_search(collection, documents=None, document_ids=None, use_in_search=False)`, `deactivate_documents(...)`, `activate_documents(...)` | `POST set_documents_use_in_search` |
|
|
133
|
+
| `list_question_templates()` | `GET question_templates` |
|
|
134
|
+
| `list_filter_options()` | `GET filter_options` |
|
|
135
|
+
| `search(collection, query, options, ...)` | `POST search_with_options` |
|
|
136
|
+
| `generate_answer(query_response_id, options, ...)` | `POST generative_answer` |
|
|
137
|
+
| `rate_answer(answer_response_id, value, value_max, comment=None)` | `POST rate_generative_answer` |
|
|
138
|
+
| `list_search_history(collection=None, query_response_id=None)`, `get_search_history(query_response_id)`, `delete_search_history(query_ids=None, query_response_ids=None)` | `GET search_history`, `GET get_search_history`, `POST delete_search_history` |
|
|
139
|
+
| `list_generative_models()`, `list_embedders()`, `list_rerankers()` | `GET generative_models`, `GET embedders`, `GET rerankers` |
|
|
140
|
+
| `embedders_info()`, `rerankers_info()` | `GET embedders`, `GET rerankers` — `models_info` with `vector_size`, `device` and `max_model_length` |
|
|
141
|
+
| `new_chat(...)`, `send_chat_message(...)`, `save_chat(...)`, `get_chat_by_hash(...)`, `list_chats()` | `POST new_chat`, `POST add_user_message`, `POST save_chat`, `GET get_chat_by_hash`, `GET chats` |
|
|
142
|
+
| `login()`, `login_with_code()`, `refresh_access_token()`, `logout()`, `login_url()` | `POST login`, `POST refresh_token`, `POST logout`, `POST login_url` |
|
|
143
|
+
| `health()`, `is_healthy()` | `GET healthz` (public — no token, no login) |
|
|
144
|
+
| `request(method, endpoint, ...)`, `get(...)`, `post(...)` | any endpoint, including ones this release does not wrap |
|
|
145
|
+
|
|
146
|
+
Collections may be passed as a name, a `Collection` returned by
|
|
147
|
+
`create_collection()` / `list_collections()`, or any mapping with a `name` key.
|
|
148
|
+
A `Collection` also carries the settings of the collection: `metadata_json`,
|
|
149
|
+
`chunking` (`{}` when none was declared) and `strict_chunking`.
|
|
150
|
+
|
|
151
|
+
`GET documents` has two views. The default one lists the names of the
|
|
152
|
+
documents which take part in the search (`list_documents()`); with
|
|
153
|
+
`with_disabled=True` — and always in `list_document_details()` — the server
|
|
154
|
+
adds the deactivated documents together with their `id` and `use_in_search`
|
|
155
|
+
flag. Those identifiers are what `document_ids[]` of `delete_documents` and
|
|
156
|
+
`set_documents_use_in_search` expects.
|
|
157
|
+
|
|
158
|
+
`send_chat_message(..., collection="my_docs")` requires an explicit collection,
|
|
159
|
+
even if the chat was created with one: the current server resolves the collection
|
|
160
|
+
from each message request. Omitting it raises `SSEValueError` before sending.
|
|
161
|
+
|
|
162
|
+
Set `api_prefix=""` for deployments without an API path prefix; an explicitly
|
|
163
|
+
provided empty prefix overrides `SSE_API_PREFIX`.
|
|
164
|
+
|
|
165
|
+
## Options objects
|
|
166
|
+
|
|
167
|
+
| Class | Used by | Notes |
|
|
168
|
+
|-------|---------|-------|
|
|
169
|
+
| `SearchOptions` | `search()` | Only the fields that are set are sent; `extra` merges unknown keys |
|
|
170
|
+
| `IndexingOptions` | `upload_files()`, `index_documents()` | Serialises the five cleaning keys the server reads with `options["..."]`; the two chunk keys appear only when set, so the collection decides |
|
|
171
|
+
| `GenerativeOptions` | `generate_answer()`, `send_chat_message()` | `None` fields are dropped; carries `use_doc_names_in_response`, the flag the generator reads from the same `query_options` |
|
|
172
|
+
| `ChatOptions` | `new_chat()`, `send_chat_message()` (`chat_options=`) | The four pipeline switches (`use_rag_supervisor`, `use_content_supervisor`, `use_intelligent_query_rewrite`, `query_instruction`) merged into the single `options` object the chat endpoints read |
|
|
173
|
+
|
|
174
|
+
Single options may also be passed as keyword arguments — the two calls below are
|
|
175
|
+
equivalent:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
client.search("my_docs", "query", SearchOptions(max_results=20, hybrid_search=True))
|
|
179
|
+
client.search("my_docs", "query", max_results=20, hybrid_search=True)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Unknown keys in search and generation dictionaries are preserved in `extra`;
|
|
183
|
+
unknown indexing keys raise `SSEValueError`. Per-call dictionaries override only
|
|
184
|
+
the supplied client defaults, while `IndexingOptions` instances supply a complete
|
|
185
|
+
configuration (including their dataclass defaults). Keyword arguments win over
|
|
186
|
+
both.
|
|
187
|
+
|
|
188
|
+
### Wire-format quirks handled by the client
|
|
189
|
+
|
|
190
|
+
The backend is not uniform, and these traps are the reason the wrapper exists:
|
|
191
|
+
|
|
192
|
+
* `options` of `search_with_options`, `query_options` of `generative_answer` and
|
|
193
|
+
`indexing_options` of `upload_and_index_files` are read with `json.loads()`,
|
|
194
|
+
so they are sent as **JSON strings**;
|
|
195
|
+
* `indexing_options` of `add_and_index_texts` and `options`/`search_options` of
|
|
196
|
+
the chat endpoints are read as objects, so they are sent as **JSON objects**;
|
|
197
|
+
* uploaded files use the field name `files[]` and input texts `texts[]`;
|
|
198
|
+
* endpoint paths have **no trailing slash** (that is how Django registers them);
|
|
199
|
+
* business errors come back as HTTP 200 with `{"status": false, "errors": [...]}`.
|
|
200
|
+
|
|
201
|
+
## Return values
|
|
202
|
+
|
|
203
|
+
Methods return dataclasses (`sse_lib.models`): `Collection`, `SearchResponse`
|
|
204
|
+
(with `.hits` of `SearchHit`), `Answer`, `UploadResult`, `IndexingResult`,
|
|
205
|
+
`DocumentRef`, `Chat`, `ChatReply`, `ChatMessage`, `ChatHistory`. Every one of them keeps the
|
|
206
|
+
untouched server payload in `.raw`, and
|
|
207
|
+
`client.request("GET", "some_endpoint")` returns the unwrapped `body` of any
|
|
208
|
+
endpoint for fields that are not modelled yet.
|
|
209
|
+
|
|
210
|
+
## Errors
|
|
211
|
+
|
|
212
|
+
| Exception | Raised when |
|
|
213
|
+
|-----------|-------------|
|
|
214
|
+
| `SSEValueError` | An argument is rejected before the request is sent (empty query, unknown index type, ...) |
|
|
215
|
+
| `SSEConfigError` | Invalid configuration (missing host, non-positive timeout, login without credentials) |
|
|
216
|
+
| `SSEAPIError` | The API answered `{"status": false, "errors": [...]}` — inspect `.error_names`, `.not_given_params` |
|
|
217
|
+
| `SSEAuthenticationError` | HTTP 401/403 |
|
|
218
|
+
| `SSEHTTPError` | Any other non-2xx status |
|
|
219
|
+
| `SSETransportError` | No response at all (DNS, connection refused, timeout) |
|
|
220
|
+
|
|
221
|
+
All of them derive from `SSEError`, so a single `except SSEError` is enough for
|
|
222
|
+
a coarse handler. Idempotent `GET` calls are retried on connection errors and
|
|
223
|
+
on 429/502/503/504; `POST` is never replayed, because indexing twice is a real
|
|
224
|
+
side effect.
|
|
225
|
+
|
|
226
|
+
## Development
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
cd sse_lib
|
|
230
|
+
PYTHONPATH=src python -m unittest discover -s tests -v # no installation needed
|
|
231
|
+
python -m pytest . # pytest, if available
|
|
232
|
+
black --check . && flake8 .
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The tests never open a socket: `tests/support.py` replaces the HTTP session
|
|
236
|
+
with a recording double, which pins the exact payloads sent to the API.
|
sse_lib-0.1.0/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# sse_lib
|
|
2
|
+
|
|
3
|
+
Thin Python client for the **Semantic Search Engine** (SSE) REST API
|
|
4
|
+
(`src/sse_api/` in this repository).
|
|
5
|
+
|
|
6
|
+
`sse_lib` is a client-side layer only: it maps Python calls onto the HTTP
|
|
7
|
+
endpoints of a running SSE backend and turns the JSON answers into small
|
|
8
|
+
dataclasses. It does **not** import Django, `torch`, `pymilvus` or any other
|
|
9
|
+
server dependency — the single runtime requirement is `requests`.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from sse_lib import SSEClient, SearchOptions, GenerativeOptions
|
|
13
|
+
|
|
14
|
+
client = SSEClient("http://localhost:8271", token="...")
|
|
15
|
+
|
|
16
|
+
client.create_collection(
|
|
17
|
+
"my_docs",
|
|
18
|
+
"My Documents",
|
|
19
|
+
"Test collection",
|
|
20
|
+
embedder="radlab/polish-bi-encoder-mean",
|
|
21
|
+
reranker="radlab/polish-cross-encoder",
|
|
22
|
+
index_type="HNSW",
|
|
23
|
+
chunking={"max_tokens_in_chunk": 128, "number_of_overlap_tokens": 0},
|
|
24
|
+
)
|
|
25
|
+
client.upload_files("my_docs", ["invoice.pdf", "regulations.zip"])
|
|
26
|
+
client.index_documents("my_docs", ["Some text to index."], category="Notes")
|
|
27
|
+
|
|
28
|
+
found = client.search("my_docs", "policy on data retention",
|
|
29
|
+
SearchOptions(max_results=20, rerank_results=True))
|
|
30
|
+
answer = client.generate_answer(
|
|
31
|
+
found.query_response_id,
|
|
32
|
+
GenerativeOptions(
|
|
33
|
+
generative_model="radlab/pLLama-3-8B-DPO-L",
|
|
34
|
+
percentage_rank_mass=40,
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
print(answer.answer)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Chunking belongs to the **collection**, not to a single call: declare it with
|
|
41
|
+
`create_collection(..., chunking={...})` (or `strict_chunking=True` to forbid any
|
|
42
|
+
deviation) and the indexing calls above may leave both keys out — the server takes
|
|
43
|
+
the settings of the collection. `client.embedders_info()` returns
|
|
44
|
+
`max_model_length` of every embedder, the largest chunk its model can take in
|
|
45
|
+
(`client.rerankers_info()` the pair budget of the rerankers). Passing
|
|
46
|
+
`max_tokens_in_chunk` / `number_of_overlap_tokens` per call, or once per client
|
|
47
|
+
with `SSEClient(..., default_indexing_options={...})`, still works; a split which
|
|
48
|
+
cannot exist (`overlap >= chunk`) raises `SSEValueError` before the request goes
|
|
49
|
+
out. `percentage_rank_mass` of `generate_answer` is required by the backend and
|
|
50
|
+
has no library-wide default on purpose.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
From a checkout:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install ./sse_lib # or: pip install -e ./sse_lib (development)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Once published to a registry, `pip install sse-lib` is all that is needed — the
|
|
61
|
+
package is standalone and versioned independently (`sse_lib.__version__`).
|
|
62
|
+
|
|
63
|
+
Python 3.9+, `requests` as the only runtime dependency.
|
|
64
|
+
|
|
65
|
+
## Authentication
|
|
66
|
+
|
|
67
|
+
| Mode | How |
|
|
68
|
+
|------|-----|
|
|
69
|
+
| Pre-existing token (default DRF token auth) | `SSEClient(host, token="...")` — sent as `Authorization: Token <value>` |
|
|
70
|
+
| Login with user and password | `SSEClient(host, username="user", password="...")` — `POST login` runs lazily, before the first request |
|
|
71
|
+
| Keycloak / OAuth (backend started with `ENV_USE_KC_AUTH=1`) | `client.login_url()` → redirect the user → `client.login_with_code(code, state)`; later `client.refresh_access_token()` and `client.logout()` |
|
|
72
|
+
| Custom scheme | `SSEClient(host, token="...", token_type="Bearer")` |
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
| Constructor argument | Environment variable | Default | Meaning |
|
|
77
|
+
|----------------------|----------------------|---------|---------|
|
|
78
|
+
| `api_host` | `SSE_API_HOST` | – | `http://localhost:8271`; the scheme may be omitted (`localhost` → `http`, anything else → `https`) |
|
|
79
|
+
| `api_prefix` | `SSE_API_PREFIX` | `api` | Path between the host and the endpoints; set `api/v1` for versioned deployments (`main.api` in `django-config.json`) |
|
|
80
|
+
| `token` | `SSE_API_TOKEN` | – | Value of the `Authorization` header |
|
|
81
|
+
| `language` | `SSE_API_LANGUAGE` | `pl` | Sent as `lang` on every call (`pl` / `en`) |
|
|
82
|
+
| `timeout` | `SSE_API_TIMEOUT` | `60.0` | Per-request timeout in seconds |
|
|
83
|
+
| `verify`, `session`, `headers`, `max_retries` | – | `True`, new session, `{}`, `2` | TLS verification, shared `requests.Session`, extra headers, GET retries |
|
|
84
|
+
| `default_search_options`, `default_indexing_options` | – | – | Client-wide option defaults merged into every search / indexing call |
|
|
85
|
+
|
|
86
|
+
`SSEClient` is a context manager; leaving the block closes the HTTP session.
|
|
87
|
+
|
|
88
|
+
## API coverage
|
|
89
|
+
|
|
90
|
+
| Client method | REST endpoint |
|
|
91
|
+
|---------------|---------------|
|
|
92
|
+
| `create_collection(...)` | `POST new_collection` |
|
|
93
|
+
| `list_collections()`, `get_collection(name)`, `collection_exists(name)` | `GET collections` |
|
|
94
|
+
| `list_categories(collection)` | `GET categories` |
|
|
95
|
+
| `list_documents(collection, with_disabled=False)`, `list_document_details(collection)` | `GET documents` |
|
|
96
|
+
| `upload_files(collection, files, ...)` | `POST upload_and_index_files` |
|
|
97
|
+
| `document_content(collection, document, from_page=None, max_pages=None, chunk_type=None)` | `GET document_content` |
|
|
98
|
+
| `index_documents(collection, documents, ...)` | `POST add_and_index_texts` |
|
|
99
|
+
| `indexing_status(collection=None, job_id=None)` | `GET indexing_status` |
|
|
100
|
+
| `delete_texts(collection, chunk_ids)` | `POST delete_texts` |
|
|
101
|
+
| `delete_documents(collection, documents=None, document_ids=None)` | `POST delete_documents` |
|
|
102
|
+
| `delete_collection(collection)` | `POST delete_collection` |
|
|
103
|
+
| `set_documents_use_in_search(collection, documents=None, document_ids=None, use_in_search=False)`, `deactivate_documents(...)`, `activate_documents(...)` | `POST set_documents_use_in_search` |
|
|
104
|
+
| `list_question_templates()` | `GET question_templates` |
|
|
105
|
+
| `list_filter_options()` | `GET filter_options` |
|
|
106
|
+
| `search(collection, query, options, ...)` | `POST search_with_options` |
|
|
107
|
+
| `generate_answer(query_response_id, options, ...)` | `POST generative_answer` |
|
|
108
|
+
| `rate_answer(answer_response_id, value, value_max, comment=None)` | `POST rate_generative_answer` |
|
|
109
|
+
| `list_search_history(collection=None, query_response_id=None)`, `get_search_history(query_response_id)`, `delete_search_history(query_ids=None, query_response_ids=None)` | `GET search_history`, `GET get_search_history`, `POST delete_search_history` |
|
|
110
|
+
| `list_generative_models()`, `list_embedders()`, `list_rerankers()` | `GET generative_models`, `GET embedders`, `GET rerankers` |
|
|
111
|
+
| `embedders_info()`, `rerankers_info()` | `GET embedders`, `GET rerankers` — `models_info` with `vector_size`, `device` and `max_model_length` |
|
|
112
|
+
| `new_chat(...)`, `send_chat_message(...)`, `save_chat(...)`, `get_chat_by_hash(...)`, `list_chats()` | `POST new_chat`, `POST add_user_message`, `POST save_chat`, `GET get_chat_by_hash`, `GET chats` |
|
|
113
|
+
| `login()`, `login_with_code()`, `refresh_access_token()`, `logout()`, `login_url()` | `POST login`, `POST refresh_token`, `POST logout`, `POST login_url` |
|
|
114
|
+
| `health()`, `is_healthy()` | `GET healthz` (public — no token, no login) |
|
|
115
|
+
| `request(method, endpoint, ...)`, `get(...)`, `post(...)` | any endpoint, including ones this release does not wrap |
|
|
116
|
+
|
|
117
|
+
Collections may be passed as a name, a `Collection` returned by
|
|
118
|
+
`create_collection()` / `list_collections()`, or any mapping with a `name` key.
|
|
119
|
+
A `Collection` also carries the settings of the collection: `metadata_json`,
|
|
120
|
+
`chunking` (`{}` when none was declared) and `strict_chunking`.
|
|
121
|
+
|
|
122
|
+
`GET documents` has two views. The default one lists the names of the
|
|
123
|
+
documents which take part in the search (`list_documents()`); with
|
|
124
|
+
`with_disabled=True` — and always in `list_document_details()` — the server
|
|
125
|
+
adds the deactivated documents together with their `id` and `use_in_search`
|
|
126
|
+
flag. Those identifiers are what `document_ids[]` of `delete_documents` and
|
|
127
|
+
`set_documents_use_in_search` expects.
|
|
128
|
+
|
|
129
|
+
`send_chat_message(..., collection="my_docs")` requires an explicit collection,
|
|
130
|
+
even if the chat was created with one: the current server resolves the collection
|
|
131
|
+
from each message request. Omitting it raises `SSEValueError` before sending.
|
|
132
|
+
|
|
133
|
+
Set `api_prefix=""` for deployments without an API path prefix; an explicitly
|
|
134
|
+
provided empty prefix overrides `SSE_API_PREFIX`.
|
|
135
|
+
|
|
136
|
+
## Options objects
|
|
137
|
+
|
|
138
|
+
| Class | Used by | Notes |
|
|
139
|
+
|-------|---------|-------|
|
|
140
|
+
| `SearchOptions` | `search()` | Only the fields that are set are sent; `extra` merges unknown keys |
|
|
141
|
+
| `IndexingOptions` | `upload_files()`, `index_documents()` | Serialises the five cleaning keys the server reads with `options["..."]`; the two chunk keys appear only when set, so the collection decides |
|
|
142
|
+
| `GenerativeOptions` | `generate_answer()`, `send_chat_message()` | `None` fields are dropped; carries `use_doc_names_in_response`, the flag the generator reads from the same `query_options` |
|
|
143
|
+
| `ChatOptions` | `new_chat()`, `send_chat_message()` (`chat_options=`) | The four pipeline switches (`use_rag_supervisor`, `use_content_supervisor`, `use_intelligent_query_rewrite`, `query_instruction`) merged into the single `options` object the chat endpoints read |
|
|
144
|
+
|
|
145
|
+
Single options may also be passed as keyword arguments — the two calls below are
|
|
146
|
+
equivalent:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
client.search("my_docs", "query", SearchOptions(max_results=20, hybrid_search=True))
|
|
150
|
+
client.search("my_docs", "query", max_results=20, hybrid_search=True)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Unknown keys in search and generation dictionaries are preserved in `extra`;
|
|
154
|
+
unknown indexing keys raise `SSEValueError`. Per-call dictionaries override only
|
|
155
|
+
the supplied client defaults, while `IndexingOptions` instances supply a complete
|
|
156
|
+
configuration (including their dataclass defaults). Keyword arguments win over
|
|
157
|
+
both.
|
|
158
|
+
|
|
159
|
+
### Wire-format quirks handled by the client
|
|
160
|
+
|
|
161
|
+
The backend is not uniform, and these traps are the reason the wrapper exists:
|
|
162
|
+
|
|
163
|
+
* `options` of `search_with_options`, `query_options` of `generative_answer` and
|
|
164
|
+
`indexing_options` of `upload_and_index_files` are read with `json.loads()`,
|
|
165
|
+
so they are sent as **JSON strings**;
|
|
166
|
+
* `indexing_options` of `add_and_index_texts` and `options`/`search_options` of
|
|
167
|
+
the chat endpoints are read as objects, so they are sent as **JSON objects**;
|
|
168
|
+
* uploaded files use the field name `files[]` and input texts `texts[]`;
|
|
169
|
+
* endpoint paths have **no trailing slash** (that is how Django registers them);
|
|
170
|
+
* business errors come back as HTTP 200 with `{"status": false, "errors": [...]}`.
|
|
171
|
+
|
|
172
|
+
## Return values
|
|
173
|
+
|
|
174
|
+
Methods return dataclasses (`sse_lib.models`): `Collection`, `SearchResponse`
|
|
175
|
+
(with `.hits` of `SearchHit`), `Answer`, `UploadResult`, `IndexingResult`,
|
|
176
|
+
`DocumentRef`, `Chat`, `ChatReply`, `ChatMessage`, `ChatHistory`. Every one of them keeps the
|
|
177
|
+
untouched server payload in `.raw`, and
|
|
178
|
+
`client.request("GET", "some_endpoint")` returns the unwrapped `body` of any
|
|
179
|
+
endpoint for fields that are not modelled yet.
|
|
180
|
+
|
|
181
|
+
## Errors
|
|
182
|
+
|
|
183
|
+
| Exception | Raised when |
|
|
184
|
+
|-----------|-------------|
|
|
185
|
+
| `SSEValueError` | An argument is rejected before the request is sent (empty query, unknown index type, ...) |
|
|
186
|
+
| `SSEConfigError` | Invalid configuration (missing host, non-positive timeout, login without credentials) |
|
|
187
|
+
| `SSEAPIError` | The API answered `{"status": false, "errors": [...]}` — inspect `.error_names`, `.not_given_params` |
|
|
188
|
+
| `SSEAuthenticationError` | HTTP 401/403 |
|
|
189
|
+
| `SSEHTTPError` | Any other non-2xx status |
|
|
190
|
+
| `SSETransportError` | No response at all (DNS, connection refused, timeout) |
|
|
191
|
+
|
|
192
|
+
All of them derive from `SSEError`, so a single `except SSEError` is enough for
|
|
193
|
+
a coarse handler. Idempotent `GET` calls are retried on connection errors and
|
|
194
|
+
on 429/502/503/504; `POST` is never replayed, because indexing twice is a real
|
|
195
|
+
side effect.
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
cd sse_lib
|
|
201
|
+
PYTHONPATH=src python -m unittest discover -s tests -v # no installation needed
|
|
202
|
+
python -m pytest . # pytest, if available
|
|
203
|
+
black --check . && flake8 .
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The tests never open a socket: `tests/support.py` replaces the HTTP session
|
|
207
|
+
with a recording double, which pins the exact payloads sent to the API.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Standalone, pip-installable client of the SSE REST API.
|
|
2
|
+
#
|
|
3
|
+
# pip install ./sse_lib # from a checkout
|
|
4
|
+
# pip install -e ./sse_lib # development install
|
|
5
|
+
#
|
|
6
|
+
# It deliberately depends on `requests` only: the library is meant to be added
|
|
7
|
+
# to any project that has to talk to a Semantic Search Engine deployment,
|
|
8
|
+
# without pulling in Django, torch or pymilvus.
|
|
9
|
+
[build-system]
|
|
10
|
+
requires = ["setuptools>=61"]
|
|
11
|
+
build-backend = "setuptools.build_meta"
|
|
12
|
+
|
|
13
|
+
[project]
|
|
14
|
+
name = "sse-lib"
|
|
15
|
+
description = "Thin Python client for the Semantic Search Engine (SSE) REST API"
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
requires-python = ">=3.9"
|
|
18
|
+
license = { text = "Apache-2.0" }
|
|
19
|
+
authors = [{ name = "RadLab" }]
|
|
20
|
+
keywords = ["semantic-search", "rag", "rest", "client", "sse", "search-engine"]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: Apache Software License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.14",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
"Typing :: Typed",
|
|
33
|
+
]
|
|
34
|
+
dependencies = ["requests>=2.31,<3.0"]
|
|
35
|
+
dynamic = ["version"]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=8.0,<10.0",
|
|
40
|
+
"black>=24.1,<26.0",
|
|
41
|
+
"flake8>=7.0,<8.0",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/radlab-dev-group/semantic-search-engine"
|
|
46
|
+
Repository = "https://github.com/radlab-dev-group/semantic-search-engine"
|
|
47
|
+
Issues = "https://github.com/radlab-dev-group/semantic-search-engine/issues"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools]
|
|
50
|
+
package-dir = { "" = "src" }
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.packages.find]
|
|
53
|
+
where = ["src"]
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
sse_lib = ["py.typed"]
|
|
57
|
+
|
|
58
|
+
[tool.setuptools.dynamic]
|
|
59
|
+
version = { attr = "sse_lib.__version__" }
|
|
60
|
+
|
|
61
|
+
[tool.black]
|
|
62
|
+
line-length = 85
|
|
63
|
+
target-version = ['py39', 'py310', 'py311', 'py312']
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
sse_lib-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sse_lib
|
|
3
|
+
-------
|
|
4
|
+
|
|
5
|
+
Thin client library for the **Semantic Search Engine** (SSE) REST API.
|
|
6
|
+
|
|
7
|
+
It wraps the HTTP endpoints of ``sse_api`` (collections, indexing, hybrid
|
|
8
|
+
search, RAG answers, chats and model listings) behind a small Python facade;
|
|
9
|
+
the server side stays untouched::
|
|
10
|
+
|
|
11
|
+
from sse_lib import SSEClient, SearchOptions, GenerativeOptions
|
|
12
|
+
|
|
13
|
+
with SSEClient("http://localhost:8271", token="...") as client:
|
|
14
|
+
collection = client.create_collection(
|
|
15
|
+
"my_docs",
|
|
16
|
+
"My Documents",
|
|
17
|
+
"Test collection",
|
|
18
|
+
embedder="radlab/polish-bi-encoder-mean",
|
|
19
|
+
reranker="radlab/polish-cross-encoder",
|
|
20
|
+
index_type="HNSW",
|
|
21
|
+
)
|
|
22
|
+
client.index_documents(collection.name, ["Some text to index."])
|
|
23
|
+
found = client.search(collection.name, "text", max_results=10)
|
|
24
|
+
answer = client.generate_answer(
|
|
25
|
+
found.query_response_id,
|
|
26
|
+
GenerativeOptions(
|
|
27
|
+
generative_model="radlab/pLLama-3-8B-DPO-L",
|
|
28
|
+
percentage_rank_mass=40,
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
print(answer.answer)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from sse_lib import endpoints
|
|
37
|
+
from sse_lib.client import SSEClient
|
|
38
|
+
from sse_lib.exceptions import (
|
|
39
|
+
SSEAPIError,
|
|
40
|
+
SSEAuthenticationError,
|
|
41
|
+
SSEConfigError,
|
|
42
|
+
SSEError,
|
|
43
|
+
SSEHTTPError,
|
|
44
|
+
SSETransportError,
|
|
45
|
+
SSEValueError,
|
|
46
|
+
)
|
|
47
|
+
from sse_lib.models import (
|
|
48
|
+
Answer,
|
|
49
|
+
Chat,
|
|
50
|
+
ChatHistory,
|
|
51
|
+
ChatMessage,
|
|
52
|
+
ChatReply,
|
|
53
|
+
Collection,
|
|
54
|
+
DocumentPreview,
|
|
55
|
+
DocumentPreviewPage,
|
|
56
|
+
DocumentRef,
|
|
57
|
+
IndexingJobStatus,
|
|
58
|
+
IndexingResult,
|
|
59
|
+
IndexingStatus,
|
|
60
|
+
SearchHistory,
|
|
61
|
+
SearchHistoryAnswer,
|
|
62
|
+
SearchHit,
|
|
63
|
+
SearchResponse,
|
|
64
|
+
UploadResult,
|
|
65
|
+
)
|
|
66
|
+
from sse_lib.options import (
|
|
67
|
+
DEFAULT_INDEX_TYPE,
|
|
68
|
+
INDEX_TYPES,
|
|
69
|
+
ChatOptions,
|
|
70
|
+
GenerativeOptions,
|
|
71
|
+
IndexingOptions,
|
|
72
|
+
SearchOptions,
|
|
73
|
+
build_options,
|
|
74
|
+
text_document,
|
|
75
|
+
)
|
|
76
|
+
from sse_lib.transport import DEFAULT_TIMEOUT, DEFAULT_TOKEN_TYPE, Transport
|
|
77
|
+
|
|
78
|
+
__version__ = "0.1.0"
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"DEFAULT_INDEX_TYPE",
|
|
82
|
+
"DEFAULT_TIMEOUT",
|
|
83
|
+
"DEFAULT_TOKEN_TYPE",
|
|
84
|
+
"INDEX_TYPES",
|
|
85
|
+
"Answer",
|
|
86
|
+
"Chat",
|
|
87
|
+
"ChatHistory",
|
|
88
|
+
"ChatMessage",
|
|
89
|
+
"ChatOptions",
|
|
90
|
+
"ChatReply",
|
|
91
|
+
"Collection",
|
|
92
|
+
"DocumentPreview",
|
|
93
|
+
"DocumentPreviewPage",
|
|
94
|
+
"DocumentRef",
|
|
95
|
+
"GenerativeOptions",
|
|
96
|
+
"IndexingJobStatus",
|
|
97
|
+
"IndexingOptions",
|
|
98
|
+
"IndexingResult",
|
|
99
|
+
"IndexingStatus",
|
|
100
|
+
"SSEAPIError",
|
|
101
|
+
"SSEAuthenticationError",
|
|
102
|
+
"SSEClient",
|
|
103
|
+
"SSEConfigError",
|
|
104
|
+
"SSEError",
|
|
105
|
+
"SSEHTTPError",
|
|
106
|
+
"SSETransportError",
|
|
107
|
+
"SSEValueError",
|
|
108
|
+
"SearchHistory",
|
|
109
|
+
"SearchHistoryAnswer",
|
|
110
|
+
"SearchHit",
|
|
111
|
+
"SearchOptions",
|
|
112
|
+
"SearchResponse",
|
|
113
|
+
"Transport",
|
|
114
|
+
"UploadResult",
|
|
115
|
+
"build_options",
|
|
116
|
+
"endpoints",
|
|
117
|
+
"text_document",
|
|
118
|
+
"__version__",
|
|
119
|
+
]
|