Skip to content

Commit 8325caa

Browse files
committed
add client methods
1 parent b321adf commit 8325caa

7 files changed

Lines changed: 339 additions & 5 deletions

File tree

‎.github/workflows/_test-units.yml‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ jobs:
5959
- "windows-2022"
6060
python-version:
6161
- "3.10"
62-
- "3.11"
63-
- "3.12"
64-
- "3.13"
6562
- "3.14"
6663
runs-on: ${{ matrix.os }}
6764
steps:

‎mindee/v2/client.py‎

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,19 @@
1010
from mindee.logger import logger
1111
from mindee.mindee_http.cancellation_token import CancellationToken
1212
from mindee.parsing.common.common_response import CommonStatus
13+
from mindee.v2.client_options.base_annotation_parameters import BaseAnnotationParameters
1314
from mindee.v2.client_options.base_product_parameters import BaseProductParameters
15+
from mindee.v2.client_options.base_rag_document_upload_parameters import (
16+
BaseRagDocumentUploadParameters,
17+
)
1418
from mindee.v2.client_options.base_search_parameters import (
1519
BaseSearchParameters,
1620
TypeSearchResponse,
1721
)
1822
from mindee.v2.mindee_http.mindee_api_v2 import MindeeAPIV2
23+
from mindee.v2.parsing.base_rag_annotation_response import (
24+
TypeRagAnnotationResponse,
25+
)
1926
from mindee.v2.parsing.inference.base_inference_response import (
2027
TypeBaseInferenceResponse,
2128
)
@@ -166,6 +173,143 @@ def enqueue_and_get_result(
166173

167174
raise MindeeError(f"Couldn't retrieve document after {try_counter + 1} tries.")
168175

176+
def upload_rag_document(
177+
self,
178+
input_source: LocalInputSource,
179+
parameters: BaseRagDocumentUploadParameters[TypeRagAnnotationResponse],
180+
) -> TypeRagAnnotationResponse:
181+
"""
182+
Not recommended for general use, prefer ``upload_and_get_rag_document``.
183+
You will need to poll until the document is ready for use.
184+
Add a document to the RAG database.
185+
"""
186+
return self.mindee_api.req_post_rag_document(input_source, parameters)
187+
188+
def upload_and_get_rag_document(
189+
self,
190+
input_source: LocalInputSource,
191+
parameters: BaseRagDocumentUploadParameters[TypeRagAnnotationResponse],
192+
polling_options: PollingOptions | None = None,
193+
cancellation_token: CancellationToken | None = None,
194+
) -> TypeRagAnnotationResponse:
195+
"""
196+
Add a document to the RAG database and return the initial annotation.
197+
"""
198+
initial_response = self.upload_rag_document(input_source, parameters)
199+
if initial_response.status != "Processing":
200+
return initial_response
201+
if polling_options is None:
202+
polling_options = PollingOptions()
203+
return self._poll_for_rag_document(
204+
initial_response, polling_options, cancellation_token
205+
)
206+
207+
def get_rag_document(
208+
self, response_type: type[TypeRagAnnotationResponse], document_id: str
209+
) -> TypeRagAnnotationResponse:
210+
"""
211+
Not recommended for general use, prefer ``get_ready_rag_document``.
212+
You will need to poll until the document is ready for use.
213+
Get a document's info and annotations from the RAG database.
214+
"""
215+
return self.mindee_api.req_get_rag_annotation(response_type, document_id)
216+
217+
def get_ready_rag_document(
218+
self,
219+
response_type: type[TypeRagAnnotationResponse],
220+
document_id: str,
221+
polling_options: PollingOptions | None = None,
222+
cancellation_token: CancellationToken | None = None,
223+
):
224+
"""
225+
Get a document's info and annotations from the RAG database.
226+
"""
227+
initial_response = self.get_rag_document(response_type, document_id)
228+
if initial_response.status != "Processing":
229+
return initial_response
230+
if polling_options is None:
231+
polling_options = PollingOptions()
232+
return self._poll_for_rag_document(
233+
initial_response, polling_options, cancellation_token
234+
)
235+
236+
def update_rag_annotations(
237+
self, parameters: BaseAnnotationParameters[TypeRagAnnotationResponse]
238+
) -> TypeRagAnnotationResponse:
239+
"""
240+
Not recommended for general use, prefer ``update_and_get_rag_annotations``.
241+
You will need to poll until the document is ready for use.
242+
Update a document's annotations in the RAG database.
243+
"""
244+
return self.mindee_api.req_patch_rag_annotation(parameters)
245+
246+
def update_and_get_rag_annotations(
247+
self,
248+
parameters: BaseAnnotationParameters[TypeRagAnnotationResponse],
249+
polling_options: PollingOptions | None = None,
250+
cancellation_token: CancellationToken | None = None,
251+
) -> TypeRagAnnotationResponse:
252+
"""
253+
Update a document's annotations in the RAG database.
254+
"""
255+
initial_response = self.update_rag_annotations(parameters)
256+
if initial_response.status != "Processing":
257+
return initial_response
258+
if polling_options is None:
259+
polling_options = PollingOptions()
260+
return self._poll_for_rag_document(
261+
initial_response, polling_options, cancellation_token
262+
)
263+
264+
def delete_extraction_rag_document(self, document_id: str) -> bool:
265+
"""
266+
Delete a document from the RAG database.
267+
For extraction models only.
268+
"""
269+
return self.mindee_api.req_delete_extraction_rag_document(document_id)
270+
271+
def _poll_for_rag_document(
272+
self,
273+
initial_response: TypeRagAnnotationResponse,
274+
polling_options: PollingOptions,
275+
cancellation_token: CancellationToken | None = None,
276+
) -> TypeRagAnnotationResponse:
277+
"""
278+
Poll until the document is finished processing or the max number of attempts is reached.
279+
"""
280+
logger.info("Polling for RAG document ID: %s", initial_response.id)
281+
max_retries = polling_options.max_retries + 1
282+
283+
logger.debug(
284+
"Waiting %s seconds before attempting to retrieve the result...",
285+
polling_options.initial_delay_sec,
286+
)
287+
288+
if cancellation_token and cancellation_token.is_canceled:
289+
raise MindeeError("Request canceled through cancellation token.")
290+
291+
sleep(polling_options.initial_delay_sec)
292+
document_id = initial_response.id
293+
retry_count = 1
294+
295+
while retry_count < max_retries:
296+
if cancellation_token and cancellation_token.is_canceled:
297+
raise MindeeError("Request canceled through cancellation token.")
298+
299+
sleep(polling_options.delay_sec)
300+
logger.info("Poll attempt %s of %s", retry_count, max_retries)
301+
302+
response = self.get_rag_document(type(initial_response), document_id)
303+
retry_count += 1
304+
305+
if response.status == "Processing":
306+
continue
307+
if response.status == "Failed":
308+
raise MindeeError("Job failed without an error payload.")
309+
return response
310+
311+
raise MindeeError(f"RAG polling not complete after {retry_count - 1} attempts.")
312+
169313
def search(
170314
self, params: BaseSearchParameters[TypeSearchResponse]
171315
) -> TypeSearchResponse:

‎mindee/v2/error/mindee_http_error_v2.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22

3+
from mindee.logger import logger
34
from mindee.parsing.common.string_dict import StringDict
45
from mindee.v2.parsing.error import ErrorItem, ErrorResponse, IErrorResponse
56

@@ -48,4 +49,6 @@ def handle_error_v2(raw_response: StringDict) -> None:
4849
"""
4950
if "status" not in raw_response or "detail" not in raw_response:
5051
raise MindeeHTTPUnknownErrorV2(json.dumps(raw_response, indent=2))
51-
raise MindeeHTTPErrorV2(ErrorResponse(raw_response))
52+
error_response = ErrorResponse(raw_response)
53+
logger.error("\n%s", error_response)
54+
raise MindeeHTTPErrorV2(error_response)

‎mindee/v2/mindee_http/mindee_api_v2.py‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
from mindee.mindee_http.settings_mixin import SettingsMixin
1212
from mindee.parsing.common.string_dict import StringDict
1313
from mindee.v1.mindee_http.base_settings import USER_AGENT
14+
from mindee.v2.client_options.base_annotation_parameters import BaseAnnotationParameters
1415
from mindee.v2.client_options.base_product_parameters import BaseProductParameters
16+
from mindee.v2.client_options.base_rag_document_upload_parameters import (
17+
BaseRagDocumentUploadParameters,
18+
)
1519
from mindee.v2.client_options.base_search_parameters import (
1620
BaseSearchParameters,
1721
TypeSearchResponse,
@@ -21,6 +25,7 @@
2125
MindeeHTTPUnknownErrorV2,
2226
handle_error_v2,
2327
)
28+
from mindee.v2.parsing.base_rag_annotation_response import TypeRagAnnotationResponse
2429
from mindee.v2.parsing.inference.base_inference_response import (
2530
TypeBaseInferenceResponse,
2631
)
@@ -224,6 +229,74 @@ def req_get_search_models(
224229
handle_error_v2(dict_response)
225230
return SearchResponse(dict_response)
226231

232+
def req_post_rag_document(
233+
self,
234+
input_source: LocalInputSource,
235+
params: BaseRagDocumentUploadParameters[TypeRagAnnotationResponse],
236+
) -> TypeRagAnnotationResponse:
237+
"""Add a document to the RAG database."""
238+
response_class = params.get_response_class()
239+
slug = response_class.get_product_slug()
240+
response = self.post_caller(
241+
url=f"{self.url_root}/v2/products/{slug}/rag-documents",
242+
headers=self.base_headers,
243+
files={"file": input_source.read_contents(params.close_file)},
244+
data=params.get_request_parameters(),
245+
follow_redirects=False,
246+
timeout=self.request_timeout,
247+
)
248+
dict_response = self._response_json(response)
249+
if not is_valid_sync_response(response):
250+
handle_error_v2(dict_response)
251+
return response_class(dict_response)
252+
253+
def req_get_rag_annotation(
254+
self, response_class: type[TypeRagAnnotationResponse], document_id: str
255+
) -> TypeRagAnnotationResponse:
256+
"""Get a document's info and annotations from the RAG database."""
257+
slug = response_class.get_product_slug()
258+
response = self.get_caller(
259+
url=f"{self.url_root}/v2/products/{slug}/rag-documents/{document_id}",
260+
headers=self.base_headers,
261+
follow_redirects=False,
262+
timeout=self.request_timeout,
263+
)
264+
dict_response = self._response_json(response)
265+
if not is_valid_sync_response(response):
266+
handle_error_v2(dict_response)
267+
return response_class(dict_response)
268+
269+
def req_patch_rag_annotation(
270+
self, params: BaseAnnotationParameters[TypeRagAnnotationResponse]
271+
) -> TypeRagAnnotationResponse:
272+
"""Update a document's annotations in the RAG database."""
273+
response_class = params.get_response_class()
274+
slug = response_class.get_product_slug()
275+
response = self.patch_caller(
276+
url=f"{self.url_root}/v2/products/{slug}/rag-documents/{params.document_id}",
277+
headers=self.base_headers,
278+
timeout=self.request_timeout,
279+
follow_redirects=False,
280+
json=params.get_request_parameters(),
281+
)
282+
dict_response = self._response_json(response)
283+
if not is_valid_sync_response(response):
284+
handle_error_v2(dict_response)
285+
return response_class(dict_response)
286+
287+
def req_delete_extraction_rag_document(self, document_id: str) -> bool:
288+
"""
289+
Deletes a document from the RAG database.
290+
For extraction models only.
291+
"""
292+
response = self.delete_caller(
293+
url=f"{self.url_root}/v2/products/extraction/rag-documents/{document_id}",
294+
headers=self.base_headers,
295+
timeout=self.request_timeout,
296+
follow_redirects=False,
297+
)
298+
return response.is_success()
299+
227300
@property
228301
def get_caller(self) -> Callable:
229302
"""Caller for GET requests."""

‎mindee/v2/product/extraction/rag_documents/__init__.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
from mindee.v2.product.extraction.rag_documents.annotated_list_field import (
2+
AnnotatedListField,
3+
)
4+
from mindee.v2.product.extraction.rag_documents.annotated_object_field import (
5+
AnnotatedObjectField,
6+
)
7+
from mindee.v2.product.extraction.rag_documents.annotated_simple_field import (
8+
AnnotatedSimpleField,
9+
)
110
from mindee.v2.product.extraction.rag_documents.extraction_rag_annotation_response import (
211
ExtractionRagAnnotationResponse,
312
)
@@ -10,6 +19,9 @@
1019
from mindee.v2.product.extraction.rag_documents.rag_annotation import RagAnnotation
1120

1221
__all__ = [
22+
"AnnotatedListField",
23+
"AnnotatedObjectField",
24+
"AnnotatedSimpleField",
1325
"ExtractionRagAnnotationResponse",
1426
"RagAnnotation",
1527
"RagDocumentAnnotationParameters",

‎mindee/v2/product/extraction/rag_documents/annotated_base_field.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,4 @@ def build(cls, raw_response: dict) -> AnnotatedFieldsType:
6767
for key, subclass in cls._registry.items():
6868
if key in raw_response:
6969
return subclass(raw_response)
70-
raise ValueError("Invalid structure for field")
70+
raise ValueError(f"Invalid structure for field: '{raw_response}'")

0 commit comments

Comments
 (0)