docforge-sdk 0.22.2__tar.gz → 0.23.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 (79) hide show
  1. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/PKG-INFO +1 -1
  2. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/_version.py +1 -1
  3. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/__init__.py +8 -0
  4. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/collections.py +109 -0
  5. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/explorer.py +35 -8
  6. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/ir.py +5 -0
  7. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/search.py +41 -4
  8. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/collections.py +41 -0
  9. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/openapi_snapshot.json +431 -14
  10. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/parity_map.py +8 -0
  11. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/route_map.py +1 -0
  12. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_collections.py +55 -0
  13. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_explorer.py +1 -0
  14. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/.gitignore +0 -0
  15. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/.python-version +0 -0
  16. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/LICENSE +0 -0
  17. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/README.md +0 -0
  18. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/__init__.py +0 -0
  19. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/_exceptions.py +0 -0
  20. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/_requestspec.py +0 -0
  21. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/_transport_async.py +0 -0
  22. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/_transport_base.py +0 -0
  23. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/_transport_sync.py +0 -0
  24. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/client.py +0 -0
  25. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/_shared.py +0 -0
  26. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/audit.py +0 -0
  27. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/auth.py +0 -0
  28. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/blobs.py +0 -0
  29. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/capabilities.py +0 -0
  30. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/corpus.py +0 -0
  31. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/documents.py +0 -0
  32. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/estimate.py +0 -0
  33. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/health.py +0 -0
  34. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/jobs.py +0 -0
  35. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/pipelines.py +0 -0
  36. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/preview.py +0 -0
  37. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/reingest.py +0 -0
  38. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/snippets.py +0 -0
  39. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/storage.py +0 -0
  40. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/trace.py +0 -0
  41. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/models/transfers.py +0 -0
  42. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/py.typed +0 -0
  43. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/__init__.py +0 -0
  44. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/_base.py +0 -0
  45. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/audit.py +0 -0
  46. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/auth.py +0 -0
  47. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/blobs.py +0 -0
  48. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/capabilities.py +0 -0
  49. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/corpus.py +0 -0
  50. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/documents.py +0 -0
  51. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/explorer.py +0 -0
  52. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/health.py +0 -0
  53. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/jobs.py +0 -0
  54. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/pipelines.py +0 -0
  55. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/search.py +0 -0
  56. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/snippets.py +0 -0
  57. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/docforge_sdk/resources/transfers.py +0 -0
  58. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/pyproject.toml +0 -0
  59. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/__init__.py +0 -0
  60. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/check_schema_drift.py +0 -0
  61. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/live/__init__.py +0 -0
  62. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/live/test_openapi_parity_live.py +0 -0
  63. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/__init__.py +0 -0
  64. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_audit.py +0 -0
  65. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_auth.py +0 -0
  66. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_blobs.py +0 -0
  67. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_corpus.py +0 -0
  68. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_documents.py +0 -0
  69. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_health.py +0 -0
  70. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_jobs.py +0 -0
  71. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_models_offline_parity.py +0 -0
  72. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_pipelines.py +0 -0
  73. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_resource_parity.py +0 -0
  74. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_route_coverage.py +0 -0
  75. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_search.py +0 -0
  76. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_snippets.py +0 -0
  77. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_transfers.py +0 -0
  78. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/tests/unit/test_transport.py +0 -0
  79. {docforge_sdk-0.22.2 → docforge_sdk-0.23.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docforge-sdk
3
- Version: 0.22.2
3
+ Version: 0.23.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
@@ -2,4 +2,4 @@
2
2
  # Single source of truth for the package version. Hatchling reads ``__version__`` from this file to
3
3
  # populate the distribution metadata (see ``[tool.hatch.version]`` in pyproject.toml).
4
4
 
5
- __version__ = "0.22.2"
5
+ __version__ = "0.23.0"
@@ -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): Learned title ('' before parse).
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="Learned title ('' before parse).")
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): Learned title ('' before parse).
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="Learned title ('' before parse).")
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 index (first page = 0).
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(description="0-based page index (first page = 0).")
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 it has no located block.
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.")
@@ -68,7 +68,12 @@ class BlockLocation(BaseModel):
68
68
  """
69
69
 
70
70
  page: int | None = Field(
71
- description="The page the block sits on; None for a page-less document (no page render)."
71
+ description="The page the block sits on (0-based); None for a page-less document (no page "
72
+ "render)."
73
+ )
74
+ page_number: int | None = Field(
75
+ default=None,
76
+ description="The same page, 1-based (as a reader counts it); None for a page-less document.",
72
77
  )
73
78
  bbox: list[float] = Field(
74
79
  description="Bounding box [x0, y0, x1, y1] NORMALISED to [0, 1] — multiply by the page "
@@ -88,7 +93,8 @@ class SearchHit(BaseModel):
88
93
  chunk_index (int): The chunk's ordinal within its document.
89
94
  token_count (int): The chunk's token count.
90
95
  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 — draw the box here.
96
+ page (int | None): The page of the chunk's primary (leading) block, 0-based.
97
+ page_number (int | None): The same page, 1-based — the one to cite.
92
98
  bbox (list[float] | None): The primary block's NORMALISED [0, 1] bounding box.
93
99
  block_locations (list[BlockLocation]): Every source block's page + bbox (draw them all).
94
100
  """
@@ -122,8 +128,12 @@ class SearchHit(BaseModel):
122
128
  )
123
129
  page: int | None = Field(
124
130
  default=None,
125
- description="The page of the chunk's primary (leading) block — where to draw the box. "
126
- "None when the chunk carries no block location.",
131
+ description="The page of the chunk's primary (leading) block, 0-based — where to draw the "
132
+ "box. None when the chunk carries no block location.",
133
+ )
134
+ page_number: int | None = Field(
135
+ default=None,
136
+ description="The same page, 1-based (as a reader counts it) — cite this. None when unlocated.",
127
137
  )
128
138
  bbox: list[float] | None = Field(
129
139
  default=None,
@@ -158,6 +168,26 @@ class SearchCost(BaseModel):
158
168
  )
159
169
 
160
170
 
171
+ class SearchHint(BaseModel):
172
+ """
173
+ An actionable explanation about one filter, attached to a (200) search response.
174
+
175
+ Attributes:
176
+ field (str): The filtered metadata field the hint is about.
177
+ value (Any): The filter value as sent.
178
+ message (str): Human/agent-readable explanation.
179
+ suggestions (list[str]): Closest stored values to retry with, best first (may be empty).
180
+ """
181
+
182
+ field: str = Field(description="The filtered metadata field the hint is about.")
183
+ value: Any = Field(description="The filter value as sent (one list item, or the whole value).")
184
+ message: str = Field(description="Human/agent-readable explanation of the problem.")
185
+ suggestions: list[str] = Field(
186
+ default_factory=list,
187
+ description="Closest stored values of the field to retry with, best first (may be empty).",
188
+ )
189
+
190
+
161
191
  class SearchResponse(BaseModel):
162
192
  """
163
193
  The result of a hybrid search — the echoed query and its ranked hits.
@@ -168,6 +198,7 @@ class SearchResponse(BaseModel):
168
198
  score_kind (str): What each hit's ``score`` represents — so the UI can label it correctly.
169
199
  cost (SearchCost | None): The run's priced paid-LLM spend, or None when no paid call was made.
170
200
  debug_info (dict[str, Any] | None): Non-fatal diagnostics; None when there is nothing to report.
201
+ hints (list[SearchHint]): Actionable filter explanations (unknown value + closest stored values).
171
202
  """
172
203
 
173
204
  query: str = Field(description="The query that was searched.")
@@ -188,6 +219,11 @@ class SearchResponse(BaseModel):
188
219
  default=None,
189
220
  description="Non-fatal diagnostics about how the search ran. None when empty.",
190
221
  )
222
+ hints: list[SearchHint] = Field(
223
+ default_factory=list,
224
+ description="Actionable filter explanations: a filter value no document stores, with the "
225
+ "closest stored values to retry with. Read these before concluding nothing exists.",
226
+ )
191
227
 
192
228
 
193
229
  class SearchHealthSummary(BaseModel):
@@ -231,6 +267,7 @@ __all__ = [
231
267
  "BlockLocation",
232
268
  "SearchHit",
233
269
  "SearchCost",
270
+ "SearchHint",
234
271
  "SearchResponse",
235
272
  "SearchHealthSummary",
236
273
  ]
@@ -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).