docforge-sdk 0.26.0__tar.gz → 0.28.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 (82) hide show
  1. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/PKG-INFO +1 -1
  2. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/__init__.py +28 -0
  3. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/_version.py +1 -1
  4. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/__init__.py +30 -0
  5. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/collections.py +41 -41
  6. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/estimate.py +14 -5
  7. docforge_sdk-0.28.0/docforge_sdk/models/field_spec.py +51 -0
  8. docforge_sdk-0.28.0/docforge_sdk/models/index_rebuild.py +22 -0
  9. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/jobs.py +17 -3
  10. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/pipelines.py +41 -0
  11. docforge_sdk-0.28.0/docforge_sdk/models/schema_ops.py +132 -0
  12. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/search.py +10 -4
  13. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/collections.py +62 -6
  14. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/pipelines.py +66 -0
  15. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/openapi_snapshot.json +877 -75
  16. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/parity_map.py +26 -0
  17. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/route_map.py +2 -0
  18. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_collections.py +22 -1
  19. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/.gitignore +0 -0
  20. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/.python-version +0 -0
  21. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/LICENSE +0 -0
  22. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/README.md +0 -0
  23. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/_exceptions.py +0 -0
  24. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/_requestspec.py +0 -0
  25. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/_transport_async.py +0 -0
  26. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/_transport_base.py +0 -0
  27. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/_transport_sync.py +0 -0
  28. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/client.py +0 -0
  29. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/_shared.py +0 -0
  30. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/audit.py +0 -0
  31. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/auth.py +0 -0
  32. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/blobs.py +0 -0
  33. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/capabilities.py +0 -0
  34. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/corpus.py +0 -0
  35. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/documents.py +0 -0
  36. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/explorer.py +0 -0
  37. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/health.py +0 -0
  38. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/ir.py +0 -0
  39. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/preview.py +0 -0
  40. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/reingest.py +0 -0
  41. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/snippets.py +0 -0
  42. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/storage.py +0 -0
  43. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/trace.py +0 -0
  44. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/models/transfers.py +0 -0
  45. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/py.typed +0 -0
  46. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/__init__.py +0 -0
  47. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/_base.py +0 -0
  48. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/audit.py +0 -0
  49. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/auth.py +0 -0
  50. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/blobs.py +0 -0
  51. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/capabilities.py +0 -0
  52. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/corpus.py +0 -0
  53. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/documents.py +0 -0
  54. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/explorer.py +0 -0
  55. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/health.py +0 -0
  56. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/jobs.py +0 -0
  57. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/search.py +0 -0
  58. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/snippets.py +0 -0
  59. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/docforge_sdk/resources/transfers.py +0 -0
  60. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/pyproject.toml +0 -0
  61. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/__init__.py +0 -0
  62. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/check_schema_drift.py +0 -0
  63. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/live/__init__.py +0 -0
  64. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/live/test_openapi_parity_live.py +0 -0
  65. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/__init__.py +0 -0
  66. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_audit.py +0 -0
  67. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_auth.py +0 -0
  68. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_blobs.py +0 -0
  69. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_corpus.py +0 -0
  70. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_documents.py +0 -0
  71. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_explorer.py +0 -0
  72. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_health.py +0 -0
  73. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_jobs.py +0 -0
  74. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_models_offline_parity.py +0 -0
  75. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_pipelines.py +0 -0
  76. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_resource_parity.py +0 -0
  77. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_route_coverage.py +0 -0
  78. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_search.py +0 -0
  79. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_snippets.py +0 -0
  80. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_transfers.py +0 -0
  81. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/tests/unit/test_transport.py +0 -0
  82. {docforge_sdk-0.26.0 → docforge_sdk-0.28.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docforge-sdk
3
- Version: 0.26.0
3
+ Version: 0.28.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
@@ -48,6 +48,7 @@ from .models.collections import (
48
48
  FieldSpec,
49
49
  ReingestJobHandle,
50
50
  UpdateCollectionRequest,
51
+ UpdateCollectionResponse,
51
52
  )
52
53
 
53
54
  # ------------------- Documents models ------------------- #
@@ -89,6 +90,9 @@ from .models.explorer import (
89
90
  # ------------------- Health models ------------------- #
90
91
  from .models.health import HealthStatus
91
92
 
93
+ # ------------------- Index rebuild models ------------------- #
94
+ from .models.index_rebuild import RebuildIndexAccepted
95
+
92
96
  # ------------------- IR models ------------------- #
93
97
  from .models.ir import (
94
98
  DocumentIRModel,
@@ -136,6 +140,19 @@ from .models.preview import (
136
140
  PreviewTraceNode,
137
141
  )
138
142
 
143
+ # ------------------- Collection schema ops ------------------- #
144
+ from .models.schema_ops import (
145
+ AddFieldOp,
146
+ FieldOp,
147
+ FieldPatch,
148
+ RemoveFieldOp,
149
+ RenameFieldOp,
150
+ SchemaDiff,
151
+ SchemaDiffModifiedField,
152
+ SchemaDiffRename,
153
+ UpdateFieldOp,
154
+ )
155
+
139
156
  # ------------------- Search models ------------------- #
140
157
  from .models.search import (
141
158
  BlockLocation,
@@ -194,6 +211,16 @@ __all__ = [
194
211
  "CollectionModel",
195
212
  "CreateCollectionRequest",
196
213
  "UpdateCollectionRequest",
214
+ "UpdateCollectionResponse",
215
+ "FieldPatch",
216
+ "AddFieldOp",
217
+ "UpdateFieldOp",
218
+ "RemoveFieldOp",
219
+ "RenameFieldOp",
220
+ "FieldOp",
221
+ "SchemaDiff",
222
+ "SchemaDiffModifiedField",
223
+ "SchemaDiffRename",
197
224
  "BulkReingestRequest",
198
225
  "ReingestJobHandle",
199
226
  "BulkReingestAccepted",
@@ -284,6 +311,7 @@ __all__ = [
284
311
  "TransferStatus",
285
312
  # Trace payloads
286
313
  "TracePurgeResult",
314
+ "RebuildIndexAccepted",
287
315
  # Exceptions
288
316
  "DocForgeError",
289
317
  "APIConnectionError",
@@ -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.26.0"
5
+ __version__ = "0.28.0"
@@ -37,6 +37,7 @@ from .collections import (
37
37
  ReingestJobHandle,
38
38
  SearchTargetGuide,
39
39
  UpdateCollectionRequest,
40
+ UpdateCollectionResponse,
40
41
  )
41
42
 
42
43
  # ------------------- Corpus filter + grid models ------------------- #
@@ -112,6 +113,9 @@ from .health import (
112
113
  SearchIndex,
113
114
  )
114
115
 
116
+ # ------------------- Index rebuild models ------------------- #
117
+ from .index_rebuild import RebuildIndexAccepted
118
+
115
119
  # ------------------- IR models ------------------- #
116
120
  from .ir import (
117
121
  DocumentIRModel,
@@ -145,6 +149,7 @@ from .jobs import (
145
149
 
146
150
  # ------------------- Pipelines models ------------------- #
147
151
  from .pipelines import (
152
+ CollectionStageApplyResponse,
148
153
  EditResponse,
149
154
  InspectResponse,
150
155
  PipelineDesignResponse,
@@ -166,6 +171,19 @@ from .preview import (
166
171
  PreviewTraceNode,
167
172
  )
168
173
 
174
+ # ------------------- Collection schema ops ------------------- #
175
+ from .schema_ops import (
176
+ AddFieldOp,
177
+ FieldOp,
178
+ FieldPatch,
179
+ RemoveFieldOp,
180
+ RenameFieldOp,
181
+ SchemaDiff,
182
+ SchemaDiffModifiedField,
183
+ SchemaDiffRename,
184
+ UpdateFieldOp,
185
+ )
186
+
169
187
  # ------------------- Search models ------------------- #
170
188
  from .search import (
171
189
  BlockLocation,
@@ -239,6 +257,16 @@ __all__ = [
239
257
  "CollectionListItem",
240
258
  "CreateCollectionRequest",
241
259
  "UpdateCollectionRequest",
260
+ "UpdateCollectionResponse",
261
+ "FieldPatch",
262
+ "AddFieldOp",
263
+ "UpdateFieldOp",
264
+ "RemoveFieldOp",
265
+ "RenameFieldOp",
266
+ "FieldOp",
267
+ "SchemaDiff",
268
+ "SchemaDiffModifiedField",
269
+ "SchemaDiffRename",
242
270
  "BulkReingestRequest",
243
271
  "ReingestJobHandle",
244
272
  "BulkReingestAccepted",
@@ -331,6 +359,7 @@ __all__ = [
331
359
  "EditResponse",
332
360
  "StageViewResponse",
333
361
  "StageApplyResponse",
362
+ "CollectionStageApplyResponse",
334
363
  # Preview
335
364
  "PreviewIrSummary",
336
365
  "PreviewChunk",
@@ -370,4 +399,5 @@ __all__ = [
370
399
  "TransferStatus",
371
400
  # Trace payloads
372
401
  "TracePurgeResult",
402
+ "RebuildIndexAccepted",
373
403
  ]
@@ -13,47 +13,10 @@ from pydantic import BaseModel, Field
13
13
  # ====== Local Project Imports ======
14
14
  from ._shared import FieldOrigin, FieldScope, FieldType
15
15
  from .estimate import EstimateOverrides
16
+ from .field_spec import FieldSpec
16
17
  from .health import CollectionHealthSummary
17
18
  from .reingest import ReingestJobHandle
18
-
19
-
20
- class FieldSpec(BaseModel):
21
- """
22
- One metadata field of the collection's contract (declared OR generated).
23
-
24
- Attributes:
25
- field_name (str): Unique field name within the collection.
26
- field_type (FieldType): Value type — drives validation and storage.
27
- required (bool): Upload refused without it (user fields).
28
- filterable (bool): Present in the Qdrant payload (lean vector).
29
- lexical (bool): Gets a sparse BM25 named vector.
30
- semantic (bool): Gets a dense named vector.
31
- enum_values (list[str] | None): Allowed values when ``field_type`` is enum.
32
- origin (FieldOrigin): user (declared at upload) or generated (metagen).
33
- scope (FieldScope): document or chunk level value.
34
- description (str | None): What the field means (max 1000 chars).
35
- """
36
-
37
- field_name: str = Field(description="Unique field name within the collection.")
38
- field_type: FieldType = Field(description="Value type — drives validation and storage.")
39
- required: bool = Field(default=False, description="Upload refused without it (user fields).")
40
- filterable: bool = Field(default=False, description="Present in the Qdrant payload (lean).")
41
- lexical: bool = Field(default=False, description="Gets a sparse BM25 named vector.")
42
- semantic: bool = Field(default=False, description="Gets a dense named vector.")
43
- enum_values: list[str] | None = Field(
44
- default=None, description="Allowed values when field_type is enum."
45
- )
46
- origin: FieldOrigin = Field(
47
- default=FieldOrigin.USER, description="user (declared at upload) or generated (metagen)."
48
- )
49
- scope: FieldScope = Field(
50
- default=FieldScope.DOCUMENT, description="document or chunk level value."
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
- )
19
+ from .schema_ops import FieldOp, SchemaDiff
57
20
 
58
21
 
59
22
  class CollectionModel(BaseModel):
@@ -68,6 +31,8 @@ class CollectionModel(BaseModel):
68
31
  job_timeout_seconds (float | None): Per-collection whole-ingest-job wall-clock job timeout,
69
32
  seconds. None = inherit the worker's global WORKER_JOB_TIMEOUT_SECONDS default.
70
33
  needs_reindex (bool): True when a config change requires reindexing.
34
+ missing_vectors (list[str] | None): Named vectors the schema's semantic/lexical fields need
35
+ but the vector store does not declare (an index rebuild is required); None on list rows.
71
36
  created_at (datetime | None): Creation timestamp.
72
37
  title_field (str | None): Document-scope field used as the display title (None = parsed).
73
38
  pipeline (dict[str, Any]): The ingestion pipeline blob (the graph).
@@ -103,6 +68,14 @@ class CollectionModel(BaseModel):
103
68
  ),
104
69
  )
105
70
  needs_reindex: bool = Field(description="True when a config change requires reindexing.")
71
+ missing_vectors: list[str] | None = Field(
72
+ default=None,
73
+ description="Named Qdrant vectors (meta_<slug>_dense / meta_<slug>_bm25) the schema's "
74
+ "semantic/lexical fields need but the vector store does not declare — those fields are not "
75
+ "searchable until the collection's index is rebuilt (a reingest cannot add them). Computed "
76
+ "on the single-collection read and the PATCH response ([] = aligned or never ingested); "
77
+ "null on the fleet list, which never reads the vector store.",
78
+ )
106
79
  created_at: datetime | None = Field(default=None, description="Creation timestamp.")
107
80
  title_field: str | None = Field(
108
81
  default=None,
@@ -213,7 +186,10 @@ class UpdateCollectionRequest(BaseModel):
213
186
  job_timeout_seconds (float | None): New per-collection whole-ingest-job wall-clock
214
187
  job timeout, seconds. Omitted = leave the current value unchanged.
215
188
  title_field (str | None): Display-title field. Omitted = unchanged; explicit null = clear.
216
- fields (list[FieldSpec] | None): The TARGET schema (diffed by field name).
189
+ fields (list[FieldSpec] | None): LEGACY full TARGET schema (omitted fields are removed).
190
+ field_ops (list[FieldOp] | None): Explicit add/update/remove/rename ops (exclusive with
191
+ ``fields``).
192
+ dry_run (bool): Preview only — the response's ``schema_diff`` is computed, nothing written.
217
193
  pipeline (dict[str, Any] | None): New pipeline blob (validated before storage).
218
194
  search (dict[str, Any] | None): New search graph blob ({} = stock default).
219
195
  estimate_overrides (EstimateOverrides | None): Partial cost-estimate overrides. Omitted =
@@ -256,7 +232,17 @@ class UpdateCollectionRequest(BaseModel):
256
232
  )
257
233
  fields: list[FieldSpec] | None = Field(
258
234
  default=None,
259
- description="The TARGET schema (diffed by field name; omitted fields are removed).",
235
+ description="LEGACY full TARGET schema (omitted fields are REMOVED with their values). "
236
+ "Prefer field_ops. Mutually exclusive with field_ops.",
237
+ )
238
+ field_ops: list[FieldOp] | None = Field(
239
+ default=None,
240
+ description="Explicit schema operations applied in order (add / update / remove / rename). "
241
+ "Mutually exclusive with fields.",
242
+ )
243
+ dry_run: bool = Field(
244
+ default=False,
245
+ description="Validate and compute schema_diff only — nothing is written.",
260
246
  )
261
247
  pipeline: dict[str, Any] | None = Field(
262
248
  default=None, description="New pipeline blob (validated before being stored)."
@@ -273,6 +259,19 @@ class UpdateCollectionRequest(BaseModel):
273
259
  note: str | None = Field(default=None, description="Version note shown in the history.")
274
260
 
275
261
 
262
+ class UpdateCollectionResponse(CollectionModel):
263
+ """
264
+ The PATCH result: the collection (unchanged under dry_run) plus its schema diff.
265
+
266
+ Attributes:
267
+ schema_diff (SchemaDiff): The schema change applied (or previewed); empty when untouched.
268
+ dry_run (bool): True when nothing was written.
269
+ """
270
+
271
+ schema_diff: SchemaDiff = Field(description="The metadata-schema change applied or previewed.")
272
+ dry_run: bool = Field(description="True when nothing was written (preview only).")
273
+
274
+
276
275
  class BulkReingestRequest(BaseModel):
277
276
  """
278
277
  The re-run request over a collection's corpus (a full-pipeline re-ingest).
@@ -431,6 +430,7 @@ __all__ = [
431
430
  "CollectionListItem",
432
431
  "CreateCollectionRequest",
433
432
  "UpdateCollectionRequest",
433
+ "UpdateCollectionResponse",
434
434
  "BulkReingestRequest",
435
435
  "ReingestJobHandle",
436
436
  "BulkReingestAccepted",
@@ -323,8 +323,9 @@ class CostEstimate(BaseModel):
323
323
  """
324
324
  The full pre-hoc breakdown — an ESTIMATE, with its assumptions and caveats surfaced.
325
325
 
326
- ``total_cost_usd`` sums only the stages with a known rate; ``cost_complete`` is False when any
327
- enabled cost-incurring stage priced to null, so the total is understood as a lower bound.
326
+ ``total_cost_usd`` is None as soon as any enabled cost-incurring stage priced to null
327
+ (``cost_complete`` False); ``total_cost_lower_bound_usd`` then carries the priced sum, and a
328
+ caveat names each unpriced model + stage + the override path.
328
329
 
329
330
  Attributes:
330
331
  document_count (int): Documents the estimate covers.
@@ -332,8 +333,10 @@ class CostEstimate(BaseModel):
332
333
  volume (VolumeEstimate): Projected material volume.
333
334
  total_prompt_tokens (int): Summed projected prompt/input tokens.
334
335
  total_completion_tokens (int): Summed projected completion tokens.
335
- total_cost_usd (float | None): Summed USD over priced stages (None only when NO stage could
336
- be priced).
336
+ total_cost_usd (float | None): Total projected USD — None when any enabled cost-incurring
337
+ stage has no known rate.
338
+ total_cost_lower_bound_usd (float): Sum of the priced stages only (== the total when
339
+ complete, else a lower bound).
337
340
  cost_complete (bool): True when every enabled cost-incurring stage had a known rate.
338
341
  assumptions (EstimateAssumptions): The assumptions this estimate rests on.
339
342
  caveats (list[str]): Human-readable accuracy caveats for this estimate.
@@ -348,7 +351,13 @@ class CostEstimate(BaseModel):
348
351
  total_prompt_tokens: int = Field(description="Summed projected prompt/input tokens.")
349
352
  total_completion_tokens: int = Field(description="Summed projected completion tokens.")
350
353
  total_cost_usd: float | None = Field(
351
- description="Summed USD over priced stages (None only when NO stage could be priced)."
354
+ description="Total projected USD — None when any enabled cost-incurring stage has no known "
355
+ "rate (see total_cost_lower_bound_usd and the caveats)."
356
+ )
357
+ total_cost_lower_bound_usd: float = Field(
358
+ default=0.0,
359
+ description="Sum of the PRICED stages only — equals total_cost_usd when the cost is "
360
+ "complete, else a lower bound of the true cost.",
352
361
  )
353
362
  cost_complete: bool = Field(
354
363
  description="True when every enabled cost-incurring stage had a known rate."
@@ -0,0 +1,51 @@
1
+ # ====== Code Summary ======
2
+ # FieldSpec — one metadata field of a collection's contract, mirrored from the backend
3
+ # ``FieldSpecModel``. Its own module so both the collections and the schema-ops models build on it.
4
+
5
+ # ====== Third-Party Library Imports ======
6
+ from pydantic import BaseModel, Field
7
+
8
+ # ====== Local Project Imports ======
9
+ from ._shared import FieldOrigin, FieldScope, FieldType
10
+
11
+
12
+ class FieldSpec(BaseModel):
13
+ """
14
+ One metadata field of the collection's contract (declared OR generated).
15
+
16
+ Attributes:
17
+ field_name (str): Unique field name within the collection.
18
+ field_type (FieldType): Value type — drives validation and storage.
19
+ required (bool): Upload refused without it (user fields).
20
+ filterable (bool): Present in the Qdrant payload (lean vector).
21
+ lexical (bool): Gets a sparse BM25 named vector.
22
+ semantic (bool): Gets a dense named vector.
23
+ enum_values (list[str] | None): Allowed values when ``field_type`` is enum.
24
+ origin (FieldOrigin): user (declared at upload) or generated (metagen).
25
+ scope (FieldScope): document or chunk level value.
26
+ description (str | None): What the field means (max 1000 chars).
27
+ """
28
+
29
+ field_name: str = Field(description="Unique field name within the collection.")
30
+ field_type: FieldType = Field(description="Value type — drives validation and storage.")
31
+ required: bool = Field(default=False, description="Upload refused without it (user fields).")
32
+ filterable: bool = Field(default=False, description="Present in the Qdrant payload (lean).")
33
+ lexical: bool = Field(default=False, description="Gets a sparse BM25 named vector.")
34
+ semantic: bool = Field(default=False, description="Gets a dense named vector.")
35
+ enum_values: list[str] | None = Field(
36
+ default=None, description="Allowed values when field_type is enum."
37
+ )
38
+ origin: FieldOrigin = Field(
39
+ default=FieldOrigin.USER, description="user (declared at upload) or generated (metagen)."
40
+ )
41
+ scope: FieldScope = Field(
42
+ default=FieldScope.DOCUMENT, description="document or chunk level value."
43
+ )
44
+ description: str | None = Field(
45
+ default=None,
46
+ max_length=1000,
47
+ description="What the field means, in plain words — surfaced to search clients (max 1000).",
48
+ )
49
+
50
+
51
+ __all__ = ["FieldSpec"]
@@ -0,0 +1,22 @@
1
+ # ====== Code Summary ======
2
+ # Response model for the collection index rebuild, mirrored field-for-field from the DocForge backend
3
+ # (POST /collections/{collection_id}/rebuild-index → 202).
4
+
5
+ # ====== Third-Party Library Imports ======
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class RebuildIndexAccepted(BaseModel):
10
+ """
11
+ A queued index rebuild — poll the job.
12
+
13
+ Attributes:
14
+ collection_id (str): The collection being rebuilt.
15
+ job_id (str): The tracked ``rebuild_index`` job (GET /jobs/{job_id}).
16
+ """
17
+
18
+ collection_id: str = Field(description="The collection whose index is being rebuilt.")
19
+ job_id: str = Field(description="The tracked rebuild_index job — poll GET /jobs/{job_id}.")
20
+
21
+
22
+ __all__ = ["RebuildIndexAccepted"]
@@ -17,12 +17,15 @@ class JobStatus(BaseModel):
17
17
 
18
18
  Attributes:
19
19
  job_id (str): The job row's UUID.
20
- document_id (str): The document being ingested.
20
+ document_id (str | None): The document being ingested (None for a rebuild_index job).
21
21
  document_filename (str | None): The document's filename, joined at read (None if the
22
22
  document is gone).
23
23
  document_title (str | None): The document's metagen-generated title, joined at read — a
24
24
  nicer display label than the filename when present (None if none was generated or the
25
25
  document is gone; the UI falls back to document_filename).
26
+ display_title (str | None): The document's display title — the collection's
27
+ ``title_field`` value when configured and set, else the parsed title (None when
28
+ neither exists, the document is gone, or on the SSE stream).
26
29
  collection_id (str): Its collection.
27
30
  collection_name (str | None): The collection's name, joined at read (None if the
28
31
  collection is gone).
@@ -47,7 +50,9 @@ class JobStatus(BaseModel):
47
50
  """
48
51
 
49
52
  job_id: str = Field(description="The job row's UUID.")
50
- document_id: str = Field(description="The document being ingested.")
53
+ document_id: str | None = Field(
54
+ description="The document being ingested; null for a collection-level job (rebuild_index)."
55
+ )
51
56
  document_filename: str | None = Field(
52
57
  default=None,
53
58
  description="The document's filename, joined at read (None if the document is gone).",
@@ -58,6 +63,14 @@ class JobStatus(BaseModel):
58
63
  "than the filename when present (None if none was generated or the document is gone; the UI "
59
64
  "falls back to document_filename).",
60
65
  )
66
+ display_title: str | None = Field(
67
+ default=None,
68
+ description="The document's DISPLAY title, resolved at read exactly like the document "
69
+ "list/grid/detail `display_title`: the value of the collection's `title_field` when one is "
70
+ "configured and set on this document, else the parsed title. None when neither exists, the "
71
+ "document is gone, or on the SSE stream (which re-reads only the job row). Prefer it over "
72
+ "document_title for labelling; fall back to document_filename.",
73
+ )
61
74
  collection_id: str = Field(description="Its collection.")
62
75
  collection_name: str | None = Field(
63
76
  default=None,
@@ -66,7 +79,8 @@ class JobStatus(BaseModel):
66
79
  status: str = Field(description="queued / running / done / failed / cancelled.")
67
80
  kind: str = Field(
68
81
  description="The kind of work this job tracks: 'ingest' (a full document pipeline run) or "
69
- "'metadata_sync' (a lightweight per-document metadata re-embed after an in-place value edit). "
82
+ "'metadata_sync' (a lightweight per-document metadata re-embed after an in-place value edit) or "
83
+ "'rebuild_index' (a collection-level vector-store rebuild — document_id is null). "
70
84
  "Lets a client render a side-job distinctly from a full ingestion."
71
85
  )
72
86
  cancel_requested: bool = Field(
@@ -195,7 +195,48 @@ class StageApplyResponse(BaseModel):
195
195
  )
196
196
 
197
197
 
198
+ class CollectionStageApplyResponse(BaseModel):
199
+ """
200
+ The outcome of a collection-scoped stage action (applied to the STORED pipeline, persisted if valid).
201
+
202
+ Attributes:
203
+ collection_id (str): The edited collection.
204
+ persisted (bool): True when the new pipeline was stored (a config version written).
205
+ needs_reindex (bool): The collection's derived reindex flag after the write.
206
+ stages (list[dict[str, Any]]): The redacted stage view of the result (opaque engine JSON).
207
+ valid (bool): True when the result built and has zero validation issues.
208
+ issues (list[dict[str, Any]]): Validation problems of the result.
209
+ notices (list[str]): Compiler notices plus the reason nothing was stored, if so.
210
+ build_error (str | None): Builder failure when the result cannot build (not persisted).
211
+ """
212
+
213
+ collection_id: str = Field(description="The edited collection.")
214
+ persisted: bool = Field(
215
+ description="True when the new pipeline was stored (a config version was written); false "
216
+ "when the result is invalid or the action changed nothing."
217
+ )
218
+ needs_reindex: bool = Field(
219
+ default=False,
220
+ description="The collection's derived reindex flag after the write (embed-space change).",
221
+ )
222
+ stages: list[dict[str, Any]] = Field(
223
+ description="The redacted stage view of the resulting pipeline, in run order."
224
+ )
225
+ valid: bool = Field(description="True when the result built and has zero validation issues.")
226
+ issues: list[dict[str, Any]] = Field(
227
+ default_factory=list, description="Validation problems of the result (empty when healthy)."
228
+ )
229
+ notices: list[str] = Field(
230
+ default_factory=list,
231
+ description="Compiler notices (cascades, ignored no-ops) and the reason nothing was stored.",
232
+ )
233
+ build_error: str | None = Field(
234
+ default=None, description="Builder failure when the result cannot build (not persisted)."
235
+ )
236
+
237
+
198
238
  __all__ = [
239
+ "CollectionStageApplyResponse",
199
240
  "PipelineSurface",
200
241
  "PipelineIndexResponse",
201
242
  "PipelineDesignResponse",
@@ -0,0 +1,132 @@
1
+ # ====== Code Summary ======
2
+ # Collection metadata-schema operations, mirrored from the DocForge backend: the ``field_ops``
3
+ # discriminated union of a collection PATCH (add / update / remove / rename) and the ``SchemaDiff``
4
+ # every PATCH response carries (applied, or previewed under ``dry_run``).
5
+
6
+ # ====== Standard Library Imports ======
7
+ from typing import Annotated, Literal
8
+
9
+ # ====== Third-Party Library Imports ======
10
+ from pydantic import BaseModel, Field
11
+
12
+ # ====== Local Project Imports ======
13
+ from ._shared import FieldOrigin, FieldScope, FieldType
14
+ from .field_spec import FieldSpec
15
+
16
+
17
+ class FieldPatch(BaseModel):
18
+ """
19
+ The attributes an ``update`` op changes — only the keys set are sent (null clears).
20
+
21
+ Attributes:
22
+ field_type / required / filterable / lexical / semantic / enum_values / origin / scope /
23
+ description: The new value of each attribute to change.
24
+ """
25
+
26
+ field_type: FieldType | None = Field(default=None, description="New value type.")
27
+ required: bool | None = Field(default=None, description="New upload-required flag.")
28
+ filterable: bool | None = Field(default=None, description="New filterable flag.")
29
+ lexical: bool | None = Field(default=None, description="New lexical (BM25 vector) flag.")
30
+ semantic: bool | None = Field(default=None, description="New semantic (dense vector) flag.")
31
+ enum_values: list[str] | None = Field(
32
+ default=None, description="New allowed values (enum fields)."
33
+ )
34
+ origin: FieldOrigin | None = Field(default=None, description="New origin (user/generated).")
35
+ scope: FieldScope | None = Field(default=None, description="New scope (document/chunk).")
36
+ description: str | None = Field(
37
+ default=None, max_length=1000, description="New description (explicit null clears it)."
38
+ )
39
+
40
+
41
+ class AddFieldOp(BaseModel):
42
+ """Add a new field (its name must not already exist)."""
43
+
44
+ op: Literal["add"] = Field(description="Operation tag.")
45
+ field: FieldSpec = Field(description="The full definition of the new field.")
46
+
47
+
48
+ class UpdateFieldOp(BaseModel):
49
+ """Change some attributes of an existing field in place (its stored values are kept)."""
50
+
51
+ op: Literal["update"] = Field(description="Operation tag.")
52
+ field_name: str = Field(description="The existing field to update.")
53
+ changes: FieldPatch = Field(description="The attributes to change (only the keys set apply).")
54
+
55
+
56
+ class RemoveFieldOp(BaseModel):
57
+ """Remove a field — DESTRUCTIVE: its stored document/chunk values are deleted with it."""
58
+
59
+ op: Literal["remove"] = Field(description="Operation tag.")
60
+ field_name: str = Field(description="The existing field to remove.")
61
+
62
+
63
+ class RenameFieldOp(BaseModel):
64
+ """Rename a field in place — its stored values and a ``title_field`` pointing at it follow."""
65
+
66
+ op: Literal["rename"] = Field(description="Operation tag.")
67
+ field_name: str = Field(description="The existing field to rename.")
68
+ new_name: str = Field(min_length=1, description="Its new name (must not already exist).")
69
+
70
+
71
+ FieldOp = Annotated[
72
+ AddFieldOp | UpdateFieldOp | RemoveFieldOp | RenameFieldOp,
73
+ Field(discriminator="op"),
74
+ ]
75
+
76
+
77
+ class SchemaDiffModifiedField(BaseModel):
78
+ """One field updated in place, with the attributes that changed."""
79
+
80
+ field_name: str = Field(description="The field's (post-PATCH) name.")
81
+ changed_attrs: list[str] = Field(description="The attributes whose value changed.")
82
+
83
+
84
+ class SchemaDiffRename(BaseModel):
85
+ """One field renamed in place (its values are kept)."""
86
+
87
+ from_name: str = Field(description="The field's previous name.")
88
+ to_name: str = Field(description="The field's new name.")
89
+
90
+
91
+ class SchemaDiff(BaseModel):
92
+ """
93
+ The metadata-schema change of a collection PATCH (applied, or previewed under dry_run).
94
+
95
+ Attributes:
96
+ added (list[str]): Fields created.
97
+ modified (list[SchemaDiffModifiedField]): Fields updated in place (values kept).
98
+ removed (list[str]): Fields deleted with their stored values.
99
+ renamed (list[SchemaDiffRename]): Fields renamed in place (values kept).
100
+ values_lost (dict[str, int]): Removed field → stored document + chunk values deleted.
101
+ reindex_required_fields (list[str]): Fields needing a reindex before being searchable.
102
+ """
103
+
104
+ added: list[str] = Field(default_factory=list, description="Fields created.")
105
+ modified: list[SchemaDiffModifiedField] = Field(
106
+ default_factory=list, description="Fields updated in place (values kept)."
107
+ )
108
+ removed: list[str] = Field(
109
+ default_factory=list, description="Fields deleted with their stored values."
110
+ )
111
+ renamed: list[SchemaDiffRename] = Field(
112
+ default_factory=list, description="Fields renamed in place (values kept)."
113
+ )
114
+ values_lost: dict[str, int] = Field(
115
+ default_factory=dict, description="Removed field → stored values deleted with it."
116
+ )
117
+ reindex_required_fields: list[str] = Field(
118
+ default_factory=list, description="Fields whose change needs a reindex to be searchable."
119
+ )
120
+
121
+
122
+ __all__ = [
123
+ "FieldPatch",
124
+ "AddFieldOp",
125
+ "UpdateFieldOp",
126
+ "RemoveFieldOp",
127
+ "RenameFieldOp",
128
+ "FieldOp",
129
+ "SchemaDiff",
130
+ "SchemaDiffModifiedField",
131
+ "SchemaDiffRename",
132
+ ]
@@ -240,16 +240,20 @@ class SearchCost(BaseModel):
240
240
 
241
241
  class SearchHint(BaseModel):
242
242
  """
243
- An actionable explanation about one filter, attached to a (200) search response.
243
+ An actionable explanation attached to a (200) search response — a filter value no document
244
+ stores, the likely culprit of a filtered zero-hit search, a ``min_score`` that dropped every hit,
245
+ or a lexical metadata target the query gave no searchable term to.
244
246
 
245
247
  Attributes:
246
- field (str): The filtered metadata field the hint is about.
248
+ field (str): What the hint is about: a filtered or targeted metadata field, or ``min_score``.
247
249
  value (Any): The filter value as sent.
248
250
  message (str): Human/agent-readable explanation.
249
251
  suggestions (list[str]): Closest stored values to retry with, best first (may be empty).
250
252
  """
251
253
 
252
- field: str = Field(description="The filtered metadata field the hint is about.")
254
+ field: str = Field(
255
+ description="What the hint is about: a filtered or targeted metadata field, or 'min_score'."
256
+ )
253
257
  value: Any = Field(description="The filter value as sent (one list item, or the whole value).")
254
258
  message: str = Field(description="Human/agent-readable explanation of the problem.")
255
259
  suggestions: list[str] = Field(
@@ -278,7 +282,9 @@ class SearchResponse(BaseModel):
278
282
  description="What every hit's ``score`` represents, so the UI labels it honestly: "
279
283
  "'rrf_fusion' (Reciprocal Rank Fusion of the dense+sparse branches — the default; "
280
284
  "rank-based, not a similarity), 'dbsf_fusion' (Distribution-Based Score Fusion), or "
281
- "'cross_encoder_rerank' (a cross-encoder relevance score, when reranking is enabled).",
285
+ "'cross_encoder_rerank' (a cross-encoder relevance score, when reranking is enabled), "
286
+ "'raw_dense' (ONE dense vector queried, no fusion — a cosine similarity) or 'raw_sparse' "
287
+ "(ONE sparse vector — its raw sparse dot / BM25 score, unbounded).",
282
288
  )
283
289
  cost: SearchCost | None = Field(
284
290
  default=None,