docforge-sdk 0.22.3__tar.gz → 0.24.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.
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/PKG-INFO +1 -1
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/_version.py +1 -1
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/__init__.py +8 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/collections.py +109 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/explorer.py +35 -8
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/ir.py +5 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/search.py +74 -9
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/collections.py +41 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/search.py +17 -8
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/openapi_snapshot.json +477 -23
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/parity_map.py +8 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/route_map.py +1 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_collections.py +55 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_explorer.py +1 -0
- docforge_sdk-0.24.0/tests/unit/test_search.py +105 -0
- docforge_sdk-0.22.3/tests/unit/test_search.py +0 -59
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/.gitignore +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/.python-version +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/LICENSE +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/README.md +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/__init__.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/_exceptions.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/_requestspec.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/_transport_async.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/_transport_base.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/_transport_sync.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/client.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/_shared.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/audit.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/auth.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/blobs.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/capabilities.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/corpus.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/documents.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/estimate.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/health.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/jobs.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/pipelines.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/preview.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/reingest.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/snippets.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/storage.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/trace.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/models/transfers.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/py.typed +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/__init__.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/_base.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/audit.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/auth.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/blobs.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/capabilities.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/corpus.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/documents.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/explorer.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/health.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/jobs.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/pipelines.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/snippets.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/docforge_sdk/resources/transfers.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/pyproject.toml +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/__init__.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/check_schema_drift.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/live/__init__.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/live/test_openapi_parity_live.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/__init__.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_audit.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_auth.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_blobs.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_corpus.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_documents.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_health.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_jobs.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_models_offline_parity.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_pipelines.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_resource_parity.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_route_coverage.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_snippets.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_transfers.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/tests/unit/test_transport.py +0 -0
- {docforge_sdk-0.22.3 → docforge_sdk-0.24.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: docforge-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.0
|
|
4
4
|
Summary: Typed async + sync Python client for the DocForge REST API (standalone, zero server deps).
|
|
5
5
|
Project-URL: Homepage, https://github.com/Florian-BARRE/docforge
|
|
6
6
|
Project-URL: Repository, https://github.com/Florian-BARRE/docforge
|
|
@@ -28,11 +28,14 @@ from .collections import (
|
|
|
28
28
|
BulkReingestAccepted,
|
|
29
29
|
BulkReingestRequest,
|
|
30
30
|
CollectionContractSchemaResponse,
|
|
31
|
+
CollectionDescription,
|
|
31
32
|
CollectionListItem,
|
|
32
33
|
CollectionModel,
|
|
33
34
|
CreateCollectionRequest,
|
|
35
|
+
FieldGuide,
|
|
34
36
|
FieldSpec,
|
|
35
37
|
ReingestJobHandle,
|
|
38
|
+
SearchTargetGuide,
|
|
36
39
|
UpdateCollectionRequest,
|
|
37
40
|
)
|
|
38
41
|
|
|
@@ -162,6 +165,7 @@ from .preview import (
|
|
|
162
165
|
from .search import (
|
|
163
166
|
BlockLocation,
|
|
164
167
|
SearchHealthSummary,
|
|
168
|
+
SearchHint,
|
|
165
169
|
SearchHit,
|
|
166
170
|
SearchRequest,
|
|
167
171
|
SearchResponse,
|
|
@@ -280,6 +284,7 @@ __all__ = [
|
|
|
280
284
|
"SearchTarget",
|
|
281
285
|
"SearchRequest",
|
|
282
286
|
"BlockLocation",
|
|
287
|
+
"SearchHint",
|
|
283
288
|
"SearchHit",
|
|
284
289
|
"SearchResponse",
|
|
285
290
|
"SearchHealthSummary",
|
|
@@ -342,6 +347,9 @@ __all__ = [
|
|
|
342
347
|
"CollectionCost",
|
|
343
348
|
# Discovery + introspection
|
|
344
349
|
"CollectionContractSchemaResponse",
|
|
350
|
+
"CollectionDescription",
|
|
351
|
+
"FieldGuide",
|
|
352
|
+
"SearchTargetGuide",
|
|
345
353
|
"WhoAmI",
|
|
346
354
|
# Transfers
|
|
347
355
|
"TransferAccepted",
|
|
@@ -31,6 +31,7 @@ class FieldSpec(BaseModel):
|
|
|
31
31
|
enum_values (list[str] | None): Allowed values when ``field_type`` is enum.
|
|
32
32
|
origin (FieldOrigin): user (declared at upload) or generated (metagen).
|
|
33
33
|
scope (FieldScope): document or chunk level value.
|
|
34
|
+
description (str | None): What the field means (max 1000 chars).
|
|
34
35
|
"""
|
|
35
36
|
|
|
36
37
|
field_name: str = Field(description="Unique field name within the collection.")
|
|
@@ -48,6 +49,11 @@ class FieldSpec(BaseModel):
|
|
|
48
49
|
scope: FieldScope = Field(
|
|
49
50
|
default=FieldScope.DOCUMENT, description="document or chunk level value."
|
|
50
51
|
)
|
|
52
|
+
description: str | None = Field(
|
|
53
|
+
default=None,
|
|
54
|
+
max_length=1000,
|
|
55
|
+
description="What the field means, in plain words — surfaced to search clients (max 1000).",
|
|
56
|
+
)
|
|
51
57
|
|
|
52
58
|
|
|
53
59
|
class CollectionModel(BaseModel):
|
|
@@ -63,6 +69,7 @@ class CollectionModel(BaseModel):
|
|
|
63
69
|
seconds. None = inherit the worker's global WORKER_JOB_TIMEOUT_SECONDS default.
|
|
64
70
|
needs_reindex (bool): True when a config change requires reindexing.
|
|
65
71
|
created_at (datetime | None): Creation timestamp.
|
|
72
|
+
title_field (str | None): Document-scope field used as the display title (None = parsed).
|
|
66
73
|
pipeline (dict[str, Any]): The ingestion pipeline blob (the graph).
|
|
67
74
|
search (dict[str, Any]): The search pipeline graph blob ({} = the stock default).
|
|
68
75
|
fields (list[FieldSpec]): The metadata schema.
|
|
@@ -97,6 +104,11 @@ class CollectionModel(BaseModel):
|
|
|
97
104
|
)
|
|
98
105
|
needs_reindex: bool = Field(description="True when a config change requires reindexing.")
|
|
99
106
|
created_at: datetime | None = Field(default=None, description="Creation timestamp.")
|
|
107
|
+
title_field: str | None = Field(
|
|
108
|
+
default=None,
|
|
109
|
+
description="Document-scope field whose value is each document's display title (None = the "
|
|
110
|
+
"parser-derived title).",
|
|
111
|
+
)
|
|
100
112
|
pipeline: dict[str, Any] = Field(description="The ingestion pipeline blob (the graph).")
|
|
101
113
|
search: dict[str, Any] = Field(
|
|
102
114
|
description="The search pipeline graph blob ({} = use the stock default)."
|
|
@@ -134,6 +146,7 @@ class CreateCollectionRequest(BaseModel):
|
|
|
134
146
|
max_file_size_bytes (int): Upload size ceiling, bytes.
|
|
135
147
|
job_timeout_seconds (float | None): Per-collection whole-ingest-job wall-clock job timeout,
|
|
136
148
|
seconds. None = inherit the worker's global WORKER_JOB_TIMEOUT_SECONDS default.
|
|
149
|
+
title_field (str | None): Document-scope field used as the display title.
|
|
137
150
|
fields (list[FieldSpec]): The FULL schema, declared up front (vector space is fixed).
|
|
138
151
|
pipeline (dict[str, Any] | None): The pipeline blob; omitted → the product default.
|
|
139
152
|
"""
|
|
@@ -162,6 +175,11 @@ class CreateCollectionRequest(BaseModel):
|
|
|
162
175
|
"WORKER_TRACE_MAX_VERBOSITY)."
|
|
163
176
|
),
|
|
164
177
|
)
|
|
178
|
+
title_field: str | None = Field(
|
|
179
|
+
default=None,
|
|
180
|
+
description="Document-scope field whose value is each document's display title (must name a "
|
|
181
|
+
"document-scope field of the schema); None = the parser-derived title.",
|
|
182
|
+
)
|
|
165
183
|
fields: list[FieldSpec] = Field(
|
|
166
184
|
default_factory=list,
|
|
167
185
|
description="The FULL schema, declared up front (vector space is fixed at creation).",
|
|
@@ -194,6 +212,7 @@ class UpdateCollectionRequest(BaseModel):
|
|
|
194
212
|
max_file_size_bytes (int | None): New size ceiling, bytes.
|
|
195
213
|
job_timeout_seconds (float | None): New per-collection whole-ingest-job wall-clock
|
|
196
214
|
job timeout, seconds. Omitted = leave the current value unchanged.
|
|
215
|
+
title_field (str | None): Display-title field. Omitted = unchanged; explicit null = clear.
|
|
197
216
|
fields (list[FieldSpec] | None): The TARGET schema (diffed by field name).
|
|
198
217
|
pipeline (dict[str, Any] | None): New pipeline blob (validated before storage).
|
|
199
218
|
search (dict[str, Any] | None): New search graph blob ({} = stock default).
|
|
@@ -229,6 +248,12 @@ class UpdateCollectionRequest(BaseModel):
|
|
|
229
248
|
"ceiling WORKER_TRACE_MAX_VERBOSITY."
|
|
230
249
|
),
|
|
231
250
|
)
|
|
251
|
+
title_field: str | None = Field(
|
|
252
|
+
default=None,
|
|
253
|
+
description="Document-scope field shown as each document's display title. Omitted = leave "
|
|
254
|
+
"unchanged; explicit null = clear (back to the parsed title); a name must be a document-scope "
|
|
255
|
+
"field of the post-PATCH schema. Auto-cleared when that field is removed or renamed.",
|
|
256
|
+
)
|
|
232
257
|
fields: list[FieldSpec] | None = Field(
|
|
233
258
|
default=None,
|
|
234
259
|
description="The TARGET schema (diffed by field name; omitted fields are removed).",
|
|
@@ -319,6 +344,87 @@ class CollectionContractSchemaResponse(BaseModel):
|
|
|
319
344
|
)
|
|
320
345
|
|
|
321
346
|
|
|
347
|
+
class FieldGuide(BaseModel):
|
|
348
|
+
"""
|
|
349
|
+
One metadata field, described for a client that wants to filter or search on it.
|
|
350
|
+
|
|
351
|
+
Attributes:
|
|
352
|
+
name (str): The field name (the key used in ``filters`` / ``search_in``).
|
|
353
|
+
type (FieldType): The declared data type.
|
|
354
|
+
description (str | None): What the field means (None when the schema sets none).
|
|
355
|
+
scope (FieldScope): Where the value lives — one per document, or one per chunk.
|
|
356
|
+
origin (FieldOrigin): Who fills it.
|
|
357
|
+
filterable (bool): Usable as a ``filters`` key.
|
|
358
|
+
semantic (bool): Searchable on its dense vector.
|
|
359
|
+
lexical (bool): Searchable on its sparse vector.
|
|
360
|
+
required (bool): Required at upload.
|
|
361
|
+
enum_values (list[str] | None): The allowed values of an enum field.
|
|
362
|
+
example_values (list[str]): Up to 10 stored values, most frequent first.
|
|
363
|
+
distinct_count (int): Number of distinct stored values.
|
|
364
|
+
note (str | None): Why examples are omitted, when they are.
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
name: str = Field(description="The field name (key used in filters / search_in).")
|
|
368
|
+
type: FieldType = Field(description="The declared data type.")
|
|
369
|
+
description: str | None = Field(default=None, description="What the field means.")
|
|
370
|
+
scope: FieldScope = Field(description="One value per document, or one per chunk.")
|
|
371
|
+
origin: FieldOrigin = Field(description="Who fills the field.")
|
|
372
|
+
filterable: bool = Field(description="Usable as a filters key.")
|
|
373
|
+
semantic: bool = Field(description="Searchable on its dense vector.")
|
|
374
|
+
lexical: bool = Field(description="Searchable on its sparse vector.")
|
|
375
|
+
required: bool = Field(description="Required at upload.")
|
|
376
|
+
enum_values: list[str] | None = Field(default=None, description="Allowed values of an enum.")
|
|
377
|
+
example_values: list[str] = Field(
|
|
378
|
+
default_factory=list, description="Up to 10 stored values, most frequent first."
|
|
379
|
+
)
|
|
380
|
+
distinct_count: int = Field(description="Number of distinct stored values.")
|
|
381
|
+
note: str | None = Field(default=None, description="Why examples are omitted, when they are.")
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class SearchTargetGuide(BaseModel):
|
|
385
|
+
"""
|
|
386
|
+
One valid ``search_in`` entry of the collection.
|
|
387
|
+
|
|
388
|
+
Attributes:
|
|
389
|
+
field (str): ``"content"`` (the chunk body) or a metadata field name.
|
|
390
|
+
semantic (bool): The field has a dense vector to query.
|
|
391
|
+
lexical (bool): The field has a sparse vector to query.
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
field: str = Field(description="'content' or a metadata field name.")
|
|
395
|
+
semantic: bool = Field(description="The field has a dense vector to query.")
|
|
396
|
+
lexical: bool = Field(description="The field has a sparse vector to query.")
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class CollectionDescription(BaseModel):
|
|
400
|
+
"""
|
|
401
|
+
The lean agent guide to one collection — what is in it and how to query it.
|
|
402
|
+
|
|
403
|
+
Attributes:
|
|
404
|
+
collection_id (str): The collection id.
|
|
405
|
+
name (str): The collection name.
|
|
406
|
+
document_count (int): Number of documents in the collection.
|
|
407
|
+
title_field (str | None): The document-scope field used as each hit's display title.
|
|
408
|
+
page_numbering (str): How hits locate pages (which field to cite).
|
|
409
|
+
fields (list[FieldGuide]): Every metadata field of the schema.
|
|
410
|
+
searchable_targets (list[SearchTargetGuide]): The valid ``search_in`` entries.
|
|
411
|
+
filter_grammar (list[str]): The filter value forms the search route accepts.
|
|
412
|
+
example_requests (list[dict[str, Any]]): Ready-to-send search request bodies.
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
collection_id: str = Field(description="The collection id.")
|
|
416
|
+
name: str = Field(description="The collection name.")
|
|
417
|
+
document_count: int = Field(description="Number of documents in the collection.")
|
|
418
|
+
title_field: str | None = Field(default=None, description="Display-title field, if any.")
|
|
419
|
+
page_numbering: str = Field(description="How hits locate pages (which field to cite).")
|
|
420
|
+
fields: list[FieldGuide] = Field(description="Every metadata field of the schema.")
|
|
421
|
+
searchable_targets: list[SearchTargetGuide] = Field(description="Valid search_in entries.")
|
|
422
|
+
filter_grammar: list[str] = Field(description="The accepted filter value forms.")
|
|
423
|
+
example_requests: list[dict[str, Any]] = Field(
|
|
424
|
+
description="Ready-to-send search request bodies for this collection."
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
|
|
322
428
|
__all__ = [
|
|
323
429
|
"FieldSpec",
|
|
324
430
|
"CollectionModel",
|
|
@@ -329,4 +435,7 @@ __all__ = [
|
|
|
329
435
|
"ReingestJobHandle",
|
|
330
436
|
"BulkReingestAccepted",
|
|
331
437
|
"CollectionContractSchemaResponse",
|
|
438
|
+
"FieldGuide",
|
|
439
|
+
"SearchTargetGuide",
|
|
440
|
+
"CollectionDescription",
|
|
332
441
|
]
|
|
@@ -41,7 +41,8 @@ class DocumentListItem(BaseModel):
|
|
|
41
41
|
page_count (int | None): Pages once parsed (None before).
|
|
42
42
|
file_size (int): Original size in bytes.
|
|
43
43
|
created_at (datetime | None): Admission timestamp.
|
|
44
|
-
title (str):
|
|
44
|
+
title (str): Raw parsed title ('' before parse).
|
|
45
|
+
display_title (str): The title to display (title_field value, else the parsed title).
|
|
45
46
|
language (str | None): Detected language (None before parse).
|
|
46
47
|
enabled (bool): Document-level searchability toggle.
|
|
47
48
|
chunk_count (int | None): Chunks persisted at ingestion (0 = empty; None = unknown/legacy).
|
|
@@ -55,7 +56,12 @@ class DocumentListItem(BaseModel):
|
|
|
55
56
|
page_count: int | None = Field(default=None, description="Pages once parsed (None before).")
|
|
56
57
|
file_size: int = Field(description="Original size in bytes.")
|
|
57
58
|
created_at: datetime | None = Field(default=None, description="Admission timestamp.")
|
|
58
|
-
title: str = Field(description="
|
|
59
|
+
title: str = Field(description="Raw parsed title ('' before parse).")
|
|
60
|
+
display_title: str = Field(
|
|
61
|
+
default="",
|
|
62
|
+
description="The title to show: the collection's title_field value when set and present, "
|
|
63
|
+
"else the parsed title.",
|
|
64
|
+
)
|
|
59
65
|
language: str | None = Field(default=None, description="Detected language (None before parse).")
|
|
60
66
|
enabled: bool = Field(description="Document-level searchability toggle.")
|
|
61
67
|
chunk_count: int | None = Field(
|
|
@@ -81,7 +87,8 @@ class DocumentDetail(BaseModel):
|
|
|
81
87
|
file_size (int): Original size in bytes.
|
|
82
88
|
page_count (int | None): Pages once parsed.
|
|
83
89
|
language (str | None): Detected document language.
|
|
84
|
-
title (str):
|
|
90
|
+
title (str): Raw parsed title ('' before parse).
|
|
91
|
+
display_title (str): The title to display (title_field value, else the parsed title).
|
|
85
92
|
source_kind (SourceKind): Acquisition routing.
|
|
86
93
|
status (DocumentStatus): Ingestion lifecycle state.
|
|
87
94
|
source_hash (str): Content address of the original bytes (blob key).
|
|
@@ -105,7 +112,12 @@ class DocumentDetail(BaseModel):
|
|
|
105
112
|
file_size: int = Field(description="Original size in bytes.")
|
|
106
113
|
page_count: int | None = Field(default=None, description="Pages once parsed.")
|
|
107
114
|
language: str | None = Field(default=None, description="Detected document language.")
|
|
108
|
-
title: str = Field(description="
|
|
115
|
+
title: str = Field(description="Raw parsed title ('' before parse).")
|
|
116
|
+
display_title: str = Field(
|
|
117
|
+
default="",
|
|
118
|
+
description="The title to show: the collection's title_field value when set and present, "
|
|
119
|
+
"else the parsed title.",
|
|
120
|
+
)
|
|
109
121
|
source_kind: SourceKind = Field(description="Acquisition routing.")
|
|
110
122
|
status: DocumentStatus = Field(description="Ingestion lifecycle state.")
|
|
111
123
|
source_hash: str = Field(description="Content address of the original bytes (blob key).")
|
|
@@ -144,7 +156,8 @@ class PageInfo(BaseModel):
|
|
|
144
156
|
One page's geometry, routing and its render blob reference.
|
|
145
157
|
|
|
146
158
|
Attributes:
|
|
147
|
-
page_number (int): 0-based page
|
|
159
|
+
page_number (int): 0-based page INDEX (legacy; first page = 0).
|
|
160
|
+
page_label (int): 1-based page number as a reader counts it (cite this).
|
|
148
161
|
width (float | None): Page width in points (None if unknown).
|
|
149
162
|
height (float | None): Page height in points (None if unknown).
|
|
150
163
|
is_scanned (bool): Whether this page was routed as image-only (OCR/VLM).
|
|
@@ -152,7 +165,16 @@ class PageInfo(BaseModel):
|
|
|
152
165
|
render_blob_hash (str | None): Blob of the rasterized page (None when render was off).
|
|
153
166
|
"""
|
|
154
167
|
|
|
155
|
-
page_number: int = Field(
|
|
168
|
+
page_number: int = Field(
|
|
169
|
+
description="0-based page INDEX (first page = 0) — NOT the reader's page number; use "
|
|
170
|
+
"page_label for that."
|
|
171
|
+
)
|
|
172
|
+
# Defaulted (not required) so this SDK still parses responses from pre-0.23 servers that
|
|
173
|
+
# don't send the field yet.
|
|
174
|
+
page_label: int | None = Field(
|
|
175
|
+
default=None,
|
|
176
|
+
description="1-based page number as a reader counts it (= page_number + 1).",
|
|
177
|
+
)
|
|
156
178
|
width: float | None = Field(default=None, description="Page width in points (None if unknown).")
|
|
157
179
|
height: float | None = Field(
|
|
158
180
|
default=None, description="Page height in points (None if unknown)."
|
|
@@ -181,7 +203,8 @@ class ChunkInfo(BaseModel):
|
|
|
181
203
|
block_ids (list[str]): Composing IR block ids, in assembly order.
|
|
182
204
|
metadata (list[MetadataValue]): Per-chunk generated metadata values.
|
|
183
205
|
heading_path (list[str]): The chunk's section breadcrumb (outer→inner headings).
|
|
184
|
-
page (int | None): Page of the chunk's primary block; None when
|
|
206
|
+
page (int | None): Page of the chunk's primary block (0-based); None when unlocated.
|
|
207
|
+
page_number (int | None): The same page, 1-based (as a reader counts it); None when unlocated.
|
|
185
208
|
"""
|
|
186
209
|
|
|
187
210
|
id: str = Field(description="The chunk UUID (doubles as the Qdrant point id).")
|
|
@@ -205,7 +228,11 @@ class ChunkInfo(BaseModel):
|
|
|
205
228
|
)
|
|
206
229
|
page: int | None = Field(
|
|
207
230
|
default=None,
|
|
208
|
-
description="Page of the chunk's primary block; None when it has no located block.",
|
|
231
|
+
description="Page of the chunk's primary block (0-based); None when it has no located block.",
|
|
232
|
+
)
|
|
233
|
+
page_number: int | None = Field(
|
|
234
|
+
default=None,
|
|
235
|
+
description="The same page, 1-based (as a reader counts it); None when unlocated.",
|
|
209
236
|
)
|
|
210
237
|
|
|
211
238
|
|
|
@@ -22,6 +22,7 @@ class IRBlock(BaseModel):
|
|
|
22
22
|
id (str): Document-namespaced block id.
|
|
23
23
|
block_type (str): text / heading / list / table / figure / …
|
|
24
24
|
page (int): 0-based page the block sits on (first page = 0).
|
|
25
|
+
page_number (int): 1-based page number as a reader counts it.
|
|
25
26
|
bbox (list[float]): Bounding box [x0, y0, x1, y1] in page coordinates.
|
|
26
27
|
reading_order (int): Global reading-order rank within the document.
|
|
27
28
|
parent_id (str | None): Parent block in the heading tree.
|
|
@@ -34,6 +35,10 @@ class IRBlock(BaseModel):
|
|
|
34
35
|
id: str = Field(description="Document-namespaced block id.")
|
|
35
36
|
block_type: str = Field(description="text / heading / list / table / figure / …")
|
|
36
37
|
page: int = Field(description="0-based page the block sits on (first page = 0).")
|
|
38
|
+
# Defaulted (not required) so this SDK still parses responses from pre-0.23 servers.
|
|
39
|
+
page_number: int | None = Field(
|
|
40
|
+
default=None, description="1-based page number as a reader counts it (= page + 1)."
|
|
41
|
+
)
|
|
37
42
|
bbox: list[float] = Field(description="Bounding box [x0, y0, x1, y1] in page coordinates.")
|
|
38
43
|
reading_order: int = Field(description="Global reading-order rank within the document.")
|
|
39
44
|
parent_id: str | None = Field(default=None, description="Parent block in the heading tree.")
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# router models. ``filters`` and ``debug_info`` are opaque, server-shaped JSON and are typed as dicts.
|
|
4
4
|
|
|
5
5
|
# ====== Standard Library Imports ======
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any, Literal
|
|
7
7
|
|
|
8
8
|
# ====== Third-Party Library Imports ======
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
@@ -41,6 +41,9 @@ class SearchRequest(BaseModel):
|
|
|
41
41
|
limit (int): Number of fused results to return.
|
|
42
42
|
filters (dict[str, Any] | None): Exact/any-of constraints on the FILTERABLE fields.
|
|
43
43
|
search_in (list[SearchTarget] | None): Fields × modalities to search. None → content on both.
|
|
44
|
+
return_fields (list[str] | None): Hit fields to return (lean response). None → the full hit.
|
|
45
|
+
group_by (Literal["document"] | None): ``"document"`` caps hits per document.
|
|
46
|
+
max_per_document (int): The per-document cap under ``group_by`` (1-10).
|
|
44
47
|
"""
|
|
45
48
|
|
|
46
49
|
query: str = Field(min_length=1, description="The natural-language query to search for.")
|
|
@@ -54,6 +57,28 @@ class SearchRequest(BaseModel):
|
|
|
54
57
|
description="Fields × modalities to search (content and/or metadata). None → content on "
|
|
55
58
|
"both semantic and lexical (the unchanged default).",
|
|
56
59
|
)
|
|
60
|
+
return_fields: list[str] | None = Field(
|
|
61
|
+
default=None,
|
|
62
|
+
max_length=64,
|
|
63
|
+
description="The hit fields to return (a lean response): any hit field name (chunk_id, "
|
|
64
|
+
"document_id, filename, document_title, heading_path, metadata, score, text, chunk_index, "
|
|
65
|
+
"token_count, block_ids, page, page_number, bbox, block_locations) or 'metadata.<field>' to "
|
|
66
|
+
"keep one metadata entry. chunk_id and document_id are ALWAYS returned. Omitted fields are "
|
|
67
|
+
"ABSENT from each hit (not null). None → the full hit. An unknown name → 422.",
|
|
68
|
+
)
|
|
69
|
+
group_by: Literal["document"] | None = Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description="'document' → no document contributes more than max_per_document hits; the "
|
|
72
|
+
"ranking keeps its order and is filled with the next best hits of other documents. "
|
|
73
|
+
"None → no grouping.",
|
|
74
|
+
)
|
|
75
|
+
max_per_document: int = Field(
|
|
76
|
+
default=1,
|
|
77
|
+
ge=1,
|
|
78
|
+
le=10,
|
|
79
|
+
description="Per-document hit cap when group_by='document' (default 1). Only valid "
|
|
80
|
+
"together with group_by.",
|
|
81
|
+
)
|
|
57
82
|
|
|
58
83
|
|
|
59
84
|
class BlockLocation(BaseModel):
|
|
@@ -68,7 +93,12 @@ class BlockLocation(BaseModel):
|
|
|
68
93
|
"""
|
|
69
94
|
|
|
70
95
|
page: int | None = Field(
|
|
71
|
-
description="The page the block sits on; None for a page-less document (no page
|
|
96
|
+
description="The page the block sits on (0-based); None for a page-less document (no page "
|
|
97
|
+
"render)."
|
|
98
|
+
)
|
|
99
|
+
page_number: int | None = Field(
|
|
100
|
+
default=None,
|
|
101
|
+
description="The same page, 1-based (as a reader counts it); None for a page-less document.",
|
|
72
102
|
)
|
|
73
103
|
bbox: list[float] = Field(
|
|
74
104
|
description="Bounding box [x0, y0, x1, y1] NORMALISED to [0, 1] — multiply by the page "
|
|
@@ -88,9 +118,13 @@ class SearchHit(BaseModel):
|
|
|
88
118
|
chunk_index (int): The chunk's ordinal within its document.
|
|
89
119
|
token_count (int): The chunk's token count.
|
|
90
120
|
block_ids (list[str]): The IR block ids the chunk was assembled from (assembly order).
|
|
91
|
-
page (int | None): The page of the chunk's primary (leading) block
|
|
121
|
+
page (int | None): The page of the chunk's primary (leading) block, 0-based.
|
|
122
|
+
page_number (int | None): The same page, 1-based — the one to cite.
|
|
92
123
|
bbox (list[float] | None): The primary block's NORMALISED [0, 1] bounding box.
|
|
93
124
|
block_locations (list[BlockLocation]): Every source block's page + bbox (draw them all).
|
|
125
|
+
|
|
126
|
+
Only ``chunk_id`` and ``document_id`` are always present: a request's ``return_fields`` omits the
|
|
127
|
+
unrequested keys (absent, not null), so ``model_fields_set`` tells what the server really sent.
|
|
94
128
|
"""
|
|
95
129
|
|
|
96
130
|
chunk_id: str = Field(description="The chunk's UUID.")
|
|
@@ -112,18 +146,22 @@ class SearchHit(BaseModel):
|
|
|
112
146
|
description="The document's filterable metadata (field → value) — so a hit self-cites "
|
|
113
147
|
"without a second GET /documents/{id}.",
|
|
114
148
|
)
|
|
115
|
-
score: float = Field(description="Fused RRF score (higher is better).")
|
|
116
|
-
text: str = Field(description="The chunk's enriched text.")
|
|
117
|
-
chunk_index: int = Field(description="Ordinal within the document.")
|
|
118
|
-
token_count: int = Field(description="Token count of the chunk.")
|
|
149
|
+
score: float = Field(default=0.0, description="Fused RRF score (higher is better).")
|
|
150
|
+
text: str = Field(default="", description="The chunk's enriched text.")
|
|
151
|
+
chunk_index: int = Field(default=0, description="Ordinal within the document.")
|
|
152
|
+
token_count: int = Field(default=0, description="Token count of the chunk.")
|
|
119
153
|
block_ids: list[str] = Field(
|
|
120
154
|
default_factory=list,
|
|
121
155
|
description="The IR block ids the chunk was assembled from, in assembly order.",
|
|
122
156
|
)
|
|
123
157
|
page: int | None = Field(
|
|
124
158
|
default=None,
|
|
125
|
-
description="The page of the chunk's primary (leading) block — where to draw the
|
|
126
|
-
"None when the chunk carries no block location.",
|
|
159
|
+
description="The page of the chunk's primary (leading) block, 0-based — where to draw the "
|
|
160
|
+
"box. None when the chunk carries no block location.",
|
|
161
|
+
)
|
|
162
|
+
page_number: int | None = Field(
|
|
163
|
+
default=None,
|
|
164
|
+
description="The same page, 1-based (as a reader counts it) — cite this. None when unlocated.",
|
|
127
165
|
)
|
|
128
166
|
bbox: list[float] | None = Field(
|
|
129
167
|
default=None,
|
|
@@ -158,6 +196,26 @@ class SearchCost(BaseModel):
|
|
|
158
196
|
)
|
|
159
197
|
|
|
160
198
|
|
|
199
|
+
class SearchHint(BaseModel):
|
|
200
|
+
"""
|
|
201
|
+
An actionable explanation about one filter, attached to a (200) search response.
|
|
202
|
+
|
|
203
|
+
Attributes:
|
|
204
|
+
field (str): The filtered metadata field the hint is about.
|
|
205
|
+
value (Any): The filter value as sent.
|
|
206
|
+
message (str): Human/agent-readable explanation.
|
|
207
|
+
suggestions (list[str]): Closest stored values to retry with, best first (may be empty).
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
field: str = Field(description="The filtered metadata field the hint is about.")
|
|
211
|
+
value: Any = Field(description="The filter value as sent (one list item, or the whole value).")
|
|
212
|
+
message: str = Field(description="Human/agent-readable explanation of the problem.")
|
|
213
|
+
suggestions: list[str] = Field(
|
|
214
|
+
default_factory=list,
|
|
215
|
+
description="Closest stored values of the field to retry with, best first (may be empty).",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
161
219
|
class SearchResponse(BaseModel):
|
|
162
220
|
"""
|
|
163
221
|
The result of a hybrid search — the echoed query and its ranked hits.
|
|
@@ -168,6 +226,7 @@ class SearchResponse(BaseModel):
|
|
|
168
226
|
score_kind (str): What each hit's ``score`` represents — so the UI can label it correctly.
|
|
169
227
|
cost (SearchCost | None): The run's priced paid-LLM spend, or None when no paid call was made.
|
|
170
228
|
debug_info (dict[str, Any] | None): Non-fatal diagnostics; None when there is nothing to report.
|
|
229
|
+
hints (list[SearchHint]): Actionable filter explanations (unknown value + closest stored values).
|
|
171
230
|
"""
|
|
172
231
|
|
|
173
232
|
query: str = Field(description="The query that was searched.")
|
|
@@ -188,6 +247,11 @@ class SearchResponse(BaseModel):
|
|
|
188
247
|
default=None,
|
|
189
248
|
description="Non-fatal diagnostics about how the search ran. None when empty.",
|
|
190
249
|
)
|
|
250
|
+
hints: list[SearchHint] = Field(
|
|
251
|
+
default_factory=list,
|
|
252
|
+
description="Actionable filter explanations: a filter value no document stores, with the "
|
|
253
|
+
"closest stored values to retry with. Read these before concluding nothing exists.",
|
|
254
|
+
)
|
|
191
255
|
|
|
192
256
|
|
|
193
257
|
class SearchHealthSummary(BaseModel):
|
|
@@ -231,6 +295,7 @@ __all__ = [
|
|
|
231
295
|
"BlockLocation",
|
|
232
296
|
"SearchHit",
|
|
233
297
|
"SearchCost",
|
|
298
|
+
"SearchHint",
|
|
234
299
|
"SearchResponse",
|
|
235
300
|
"SearchHealthSummary",
|
|
236
301
|
]
|
|
@@ -21,6 +21,7 @@ from ..models.collections import (
|
|
|
21
21
|
BulkReingestAccepted,
|
|
22
22
|
BulkReingestRequest,
|
|
23
23
|
CollectionContractSchemaResponse,
|
|
24
|
+
CollectionDescription,
|
|
24
25
|
CollectionListItem,
|
|
25
26
|
CollectionModel,
|
|
26
27
|
CreateCollectionRequest,
|
|
@@ -114,6 +115,18 @@ class _CollectionsSpecs(_ResourceMixin):
|
|
|
114
115
|
"""
|
|
115
116
|
return RequestSpec("GET", f"{self._COLLECTIONS_PATH}/{collection_id}/health")
|
|
116
117
|
|
|
118
|
+
def _describe_spec(self, collection_id: str) -> RequestSpec:
|
|
119
|
+
"""
|
|
120
|
+
Build the spec for the lean agent-oriented collection guide.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
collection_id (str): The collection's UUID.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
RequestSpec: A GET on the collection's describe sub-resource.
|
|
127
|
+
"""
|
|
128
|
+
return RequestSpec("GET", f"{self._COLLECTIONS_PATH}/{collection_id}/describe")
|
|
129
|
+
|
|
117
130
|
def _storage_spec(self, collection_id: str) -> RequestSpec:
|
|
118
131
|
"""
|
|
119
132
|
Build the spec for measuring a collection's material storage footprint.
|
|
@@ -308,6 +321,21 @@ class AsyncCollections(AsyncResource, _CollectionsSpecs):
|
|
|
308
321
|
self._health_spec(collection_id), CollectionHealthResponse
|
|
309
322
|
)
|
|
310
323
|
|
|
324
|
+
async def describe(self, collection_id: str) -> CollectionDescription:
|
|
325
|
+
"""
|
|
326
|
+
Fetch the agent guide: fields (meaning, type, example values), valid search targets, the
|
|
327
|
+
filter grammar and ready-to-send example requests. Never includes pipeline/search config.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
collection_id (str): The collection's UUID.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
CollectionDescription: The lean guide to query this collection.
|
|
334
|
+
"""
|
|
335
|
+
return await self._transport.request(
|
|
336
|
+
self._describe_spec(collection_id), CollectionDescription
|
|
337
|
+
)
|
|
338
|
+
|
|
311
339
|
async def storage(self, collection_id: str) -> CollectionStorageResponse:
|
|
312
340
|
"""
|
|
313
341
|
Measure a collection's material footprint per store (S3 exact, Postgres/Qdrant estimated).
|
|
@@ -562,6 +590,19 @@ class SyncCollections(SyncResource, _CollectionsSpecs):
|
|
|
562
590
|
"""
|
|
563
591
|
return self._transport.request(self._health_spec(collection_id), CollectionHealthResponse)
|
|
564
592
|
|
|
593
|
+
def describe(self, collection_id: str) -> CollectionDescription:
|
|
594
|
+
"""
|
|
595
|
+
Fetch the agent guide: fields (meaning, type, example values), valid search targets, the
|
|
596
|
+
filter grammar and ready-to-send example requests. Never includes pipeline/search config.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
collection_id (str): The collection's UUID.
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
CollectionDescription: The lean guide to query this collection.
|
|
603
|
+
"""
|
|
604
|
+
return self._transport.request(self._describe_spec(collection_id), CollectionDescription)
|
|
605
|
+
|
|
565
606
|
def storage(self, collection_id: str) -> CollectionStorageResponse:
|
|
566
607
|
"""
|
|
567
608
|
Measure a collection's material footprint per store (S3 exact, Postgres/Qdrant estimated).
|
|
@@ -21,16 +21,23 @@ class _SearchSpecs(_ResourceMixin):
|
|
|
21
21
|
|
|
22
22
|
Args:
|
|
23
23
|
collection_id (str): The collection to search.
|
|
24
|
-
request (SearchRequest): The query, filters and
|
|
24
|
+
request (SearchRequest): The query, filters, target modalities and the optional
|
|
25
|
+
``return_fields`` / ``group_by`` / ``max_per_document`` shaping.
|
|
25
26
|
|
|
26
27
|
Returns:
|
|
27
28
|
RequestSpec: A POST to the collection's ``/search`` route carrying the query body.
|
|
28
29
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
30
|
+
# 1. Dump the body, sending the response-shaping knobs only when used: a pre-0.24 server's
|
|
31
|
+
# SearchRequest forbids unknown keys (even a null return_fields/group_by would 422 every
|
|
32
|
+
# plain search), and the backend 422s a max_per_document sent without group_by — so the
|
|
33
|
+
# untouched default (1) is dropped unless grouping is on or the caller set it explicitly.
|
|
34
|
+
body = request.model_dump(mode="json")
|
|
35
|
+
for knob in ("return_fields", "group_by"):
|
|
36
|
+
if body.get(knob) is None:
|
|
37
|
+
body.pop(knob, None)
|
|
38
|
+
if request.group_by is None and "max_per_document" not in request.model_fields_set:
|
|
39
|
+
body.pop("max_per_document")
|
|
40
|
+
return RequestSpec("POST", f"{self._COLLECTIONS_PATH}/{collection_id}/search", json=body)
|
|
34
41
|
|
|
35
42
|
def _search_health_spec(self) -> RequestSpec:
|
|
36
43
|
"""
|
|
@@ -51,7 +58,8 @@ class AsyncSearch(AsyncResource, _SearchSpecs):
|
|
|
51
58
|
|
|
52
59
|
Args:
|
|
53
60
|
collection_id (str): The collection to search.
|
|
54
|
-
request (SearchRequest): The query, filters and
|
|
61
|
+
request (SearchRequest): The query, filters, target modalities and the optional
|
|
62
|
+
``return_fields`` / ``group_by`` / ``max_per_document`` shaping.
|
|
55
63
|
|
|
56
64
|
Returns:
|
|
57
65
|
SearchResponse: The echoed query and its hits, best first.
|
|
@@ -79,7 +87,8 @@ class SyncSearch(SyncResource, _SearchSpecs):
|
|
|
79
87
|
|
|
80
88
|
Args:
|
|
81
89
|
collection_id (str): The collection to search.
|
|
82
|
-
request (SearchRequest): The query, filters and
|
|
90
|
+
request (SearchRequest): The query, filters, target modalities and the optional
|
|
91
|
+
``return_fields`` / ``group_by`` / ``max_per_document`` shaping.
|
|
83
92
|
|
|
84
93
|
Returns:
|
|
85
94
|
SearchResponse: The echoed query and its hits, best first.
|