cortexdb-sdk 0.2.0b2__py3-none-any.whl

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.
@@ -0,0 +1,197 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .models import (
6
+ AnswerGroundingReportResponse,
7
+ AnswerGroundingSpanResponse,
8
+ ContextPackResponse,
9
+ GroundedAnswerResponse,
10
+ VerificationReportResponse,
11
+ )
12
+
13
+
14
+ def _tokenize(text: str) -> tuple[str, ...]:
15
+ terms: list[str] = []
16
+ current: list[str] = []
17
+ for character in text.lower():
18
+ if character.isalnum():
19
+ current.append(character)
20
+ elif current:
21
+ term = "".join(current)
22
+ if term not in {"a", "an", "and", "the", "or", "of", "to", "in"}:
23
+ terms.append(term)
24
+ current = []
25
+ if current:
26
+ term = "".join(current)
27
+ if term not in {"a", "an", "and", "the", "or", "of", "to", "in"}:
28
+ terms.append(term)
29
+ return tuple(sorted(set(terms)))
30
+
31
+
32
+ def _split_answer_spans(answer: str) -> tuple[tuple[str, int, int], ...]:
33
+ spans: list[tuple[str, int, int]] = []
34
+ start = 0
35
+ for index, character in enumerate(answer):
36
+ if character in {"!", "?", "\n"} or (
37
+ character == "."
38
+ and not (
39
+ index > 0
40
+ and index + 1 < len(answer)
41
+ and answer[index - 1].isdigit()
42
+ and answer[index + 1].isdigit()
43
+ )
44
+ ):
45
+ _push_answer_span(answer, start, index + 1, spans)
46
+ start = index + 1
47
+ _push_answer_span(answer, start, len(answer), spans)
48
+ return tuple(spans)
49
+
50
+
51
+ def _push_answer_span(
52
+ answer: str,
53
+ start: int,
54
+ end: int,
55
+ spans: list[tuple[str, int, int]],
56
+ ) -> None:
57
+ raw = answer[start:end]
58
+ text = raw.strip()
59
+ if not text:
60
+ return
61
+ leading = len(raw) - len(raw.lstrip())
62
+ trailing = len(raw) - len(raw.rstrip())
63
+ spans.append((text, start + leading, end - trailing))
64
+
65
+
66
+ def _q16_ratio(numerator: int, denominator: int) -> int:
67
+ if denominator == 0:
68
+ return 65535
69
+ return int(numerator * 65535 / denominator)
70
+
71
+
72
+ def _unique(values: list[str] | list[int]) -> tuple[Any, ...]:
73
+ seen = set()
74
+ out = []
75
+ for value in values:
76
+ if value not in seen:
77
+ seen.add(value)
78
+ out.append(value)
79
+ return tuple(out)
80
+
81
+
82
+ def ground_answer(
83
+ context: ContextPackResponse,
84
+ answer: str,
85
+ *,
86
+ min_span_support_q16: int,
87
+ require_citations: bool,
88
+ reject_unsupported: bool,
89
+ ) -> AnswerGroundingReportResponse:
90
+ spans: list[AnswerGroundingSpanResponse] = []
91
+ for text, start, end in _split_answer_spans(answer):
92
+ span_terms = _tokenize(text)
93
+ if not span_terms:
94
+ spans.append(
95
+ AnswerGroundingSpanResponse(
96
+ text=text,
97
+ start_byte=start,
98
+ end_byte=end,
99
+ support_q16=65535,
100
+ supported=True,
101
+ covered_terms=(),
102
+ missing_terms=(),
103
+ supported_by_cell_ids=(),
104
+ citations=(),
105
+ )
106
+ )
107
+ continue
108
+ covered: set[str] = set()
109
+ cell_ids: list[int] = []
110
+ citations: list[str] = []
111
+ for cell in context.cells:
112
+ cell_terms = set(_tokenize(cell.payload_text))
113
+ matched = False
114
+ for term in span_terms:
115
+ if term in cell_terms:
116
+ covered.add(term)
117
+ matched = True
118
+ if matched:
119
+ cell_ids.append(cell.cell_id)
120
+ if cell.citation:
121
+ citations.append(cell.citation)
122
+ support = _q16_ratio(len(covered), len(span_terms))
123
+ supported = support >= min_span_support_q16 and (
124
+ not require_citations or bool(citations)
125
+ )
126
+ spans.append(
127
+ AnswerGroundingSpanResponse(
128
+ text=text,
129
+ start_byte=start,
130
+ end_byte=end,
131
+ support_q16=support,
132
+ supported=supported,
133
+ covered_terms=tuple(sorted(covered)),
134
+ missing_terms=tuple(term for term in span_terms if term not in covered),
135
+ supported_by_cell_ids=_unique(cell_ids),
136
+ citations=_unique(citations),
137
+ )
138
+ )
139
+ supported_count = sum(1 for span in spans if span.supported)
140
+ unsupported_count = len(spans) - supported_count
141
+ average = int(sum(span.support_q16 for span in spans) / len(spans)) if spans else 65535
142
+ return AnswerGroundingReportResponse(
143
+ answer_supported=unsupported_count == 0,
144
+ rejected=reject_unsupported and unsupported_count > 0,
145
+ support_q16=average,
146
+ supported_span_count=supported_count,
147
+ unsupported_span_count=unsupported_count,
148
+ spans=tuple(spans),
149
+ )
150
+
151
+
152
+ def _grounded_answer_response(
153
+ *,
154
+ question: str,
155
+ answer: str,
156
+ retrieve_statement: str,
157
+ verify_statement: str | None,
158
+ context: ContextPackResponse,
159
+ verification: "VerificationReportResponse | None",
160
+ require_citations: bool,
161
+ reject_unsupported: bool,
162
+ ) -> GroundedAnswerResponse:
163
+ grounding = context.ground_answer(
164
+ answer,
165
+ require_citations=require_citations,
166
+ reject_unsupported=reject_unsupported,
167
+ )
168
+ citations = _unique(
169
+ [
170
+ citation
171
+ for span in grounding.spans
172
+ for citation in span.citations
173
+ ]
174
+ + [cell.citation for cell in context.cells if cell.citation]
175
+ )
176
+ used_cell_ids = _unique(
177
+ [
178
+ cell_id
179
+ for span in grounding.spans
180
+ for cell_id in span.supported_by_cell_ids
181
+ ]
182
+ + [cell.cell_id for cell in context.cells]
183
+ )
184
+ return GroundedAnswerResponse(
185
+ question=question,
186
+ answer=answer,
187
+ retrieve_statement=retrieve_statement,
188
+ verify_statement=verify_statement,
189
+ context=context,
190
+ grounding=grounding,
191
+ verification=verification,
192
+ citations=citations,
193
+ used_context_cell_ids=used_cell_ids,
194
+ rejected=grounding.rejected,
195
+ )
196
+
197
+
@@ -0,0 +1,66 @@
1
+ from .context import (
2
+ AnswerGroundingReportResponse,
3
+ AnswerGroundingSpanResponse,
4
+ ContextPackAnomalyResponse,
5
+ ContextPackCellResponse,
6
+ ContextPackResponse,
7
+ ExplainResponse,
8
+ GroundedAnswerResponse,
9
+ SourceRefResponse,
10
+ )
11
+ from .core import (
12
+ AqlQueryCacheStatsResponse,
13
+ AqlCellResponse,
14
+ AqlResponse,
15
+ CellLookupResponse,
16
+ CellResponse,
17
+ HealthResponse,
18
+ PutCellResponse,
19
+ StatsResponse,
20
+ ValidationResponse,
21
+ )
22
+ from .ingestion import DeleteJobResponse, IngestResponse, IngestionJobResponse
23
+ from .memory import RememberResponse
24
+ from .search import (
25
+ AnnEvaluationResponse,
26
+ AnnNoFallbackDecision,
27
+ AnnSearchReport,
28
+ SearchResponse,
29
+ SearchResult,
30
+ SearchRoutingDecision,
31
+ )
32
+ from .verification import EvidenceResponse, GuardResponse, NumericConflictResponse, VerificationReportResponse
33
+
34
+ __all__ = [
35
+ "AnnEvaluationResponse",
36
+ "AnnNoFallbackDecision",
37
+ "AnnSearchReport",
38
+ "AnswerGroundingReportResponse",
39
+ "AnswerGroundingSpanResponse",
40
+ "AqlCellResponse",
41
+ "AqlQueryCacheStatsResponse",
42
+ "AqlResponse",
43
+ "CellLookupResponse",
44
+ "CellResponse",
45
+ "ContextPackAnomalyResponse",
46
+ "ContextPackCellResponse",
47
+ "ContextPackResponse",
48
+ "DeleteJobResponse",
49
+ "EvidenceResponse",
50
+ "ExplainResponse",
51
+ "GroundedAnswerResponse",
52
+ "GuardResponse",
53
+ "HealthResponse",
54
+ "IngestResponse",
55
+ "IngestionJobResponse",
56
+ "NumericConflictResponse",
57
+ "PutCellResponse",
58
+ "RememberResponse",
59
+ "SearchResponse",
60
+ "SearchResult",
61
+ "SearchRoutingDecision",
62
+ "SourceRefResponse",
63
+ "StatsResponse",
64
+ "ValidationResponse",
65
+ "VerificationReportResponse",
66
+ ]
@@ -0,0 +1,163 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class ExplainResponse:
9
+ score: int
10
+ matched_terms: tuple[str, ...]
11
+ why_selected: str
12
+ base_bm25: int
13
+ source_trust_bonus: int
14
+ redundancy_penalty: int
15
+
16
+ @classmethod
17
+ def from_json(cls, value: dict[str, Any]) -> "ExplainResponse":
18
+ return cls(
19
+ score=int(value["score"]),
20
+ matched_terms=tuple(str(row) for row in value["matched_terms"]),
21
+ why_selected=str(value["why_selected"]),
22
+ base_bm25=int(value["base_bm25"]),
23
+ source_trust_bonus=int(value["source_trust_bonus"]),
24
+ redundancy_penalty=int(value["redundancy_penalty"]),
25
+ )
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class SourceRefResponse:
30
+ source_id: str
31
+ document_id: str | None
32
+ page: int | None
33
+ cell_range: str | None
34
+ json_path: str | None
35
+ confidence_q16: int
36
+
37
+ @classmethod
38
+ def from_json(cls, value: dict[str, Any]) -> "SourceRefResponse":
39
+ return cls(
40
+ source_id=str(value["source_id"]),
41
+ document_id=str(value["document_id"]) if value.get("document_id") is not None else None,
42
+ page=int(value["page"]) if value.get("page") is not None else None,
43
+ cell_range=str(value["cell_range"]) if value.get("cell_range") is not None else None,
44
+ json_path=str(value["json_path"]) if value.get("json_path") is not None else None,
45
+ confidence_q16=int(value["confidence_q16"]),
46
+ )
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class ContextPackCellResponse:
51
+ cell_id: int
52
+ estimated_tokens: int
53
+ citation: str | None
54
+ payload_text: str
55
+ explain: ExplainResponse | None
56
+ source_ref: SourceRefResponse | None
57
+
58
+ @classmethod
59
+ def from_json(cls, value: dict[str, Any]) -> "ContextPackCellResponse":
60
+ explain = value.get("explain")
61
+ source_ref = value.get("source_ref")
62
+ return cls(
63
+ cell_id=int(value["cell_id"]),
64
+ estimated_tokens=int(value["estimated_tokens"]),
65
+ citation=str(value["citation"]) if value.get("citation") is not None else None,
66
+ payload_text=str(value["payload_text"]),
67
+ explain=ExplainResponse.from_json(explain) if explain else None,
68
+ source_ref=SourceRefResponse.from_json(source_ref) if source_ref else None,
69
+ )
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class ContextPackAnomalyResponse:
74
+ cell_id: int | None
75
+ code: str
76
+ message: str
77
+
78
+ @classmethod
79
+ def from_json(cls, value: dict[str, Any]) -> "ContextPackAnomalyResponse":
80
+ return cls(
81
+ cell_id=int(value["cell_id"]) if value.get("cell_id") is not None else None,
82
+ code=str(value["code"]),
83
+ message=str(value["message"]),
84
+ )
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class ContextPackResponse:
89
+ schema_version: str
90
+ token_budget_tokens: int
91
+ estimated_tokens: int
92
+ truncated: bool
93
+ citations_required: bool
94
+ cells: tuple[ContextPackCellResponse, ...]
95
+ anomalies: tuple[ContextPackAnomalyResponse, ...]
96
+
97
+ @classmethod
98
+ def from_json(cls, value: dict[str, Any]) -> "ContextPackResponse":
99
+ return cls(
100
+ schema_version=str(value["schema_version"]),
101
+ token_budget_tokens=int(value["token_budget_tokens"]),
102
+ estimated_tokens=int(value["estimated_tokens"]),
103
+ truncated=bool(value["truncated"]),
104
+ citations_required=bool(value["citations_required"]),
105
+ cells=tuple(ContextPackCellResponse.from_json(row) for row in value["cells"]),
106
+ anomalies=tuple(ContextPackAnomalyResponse.from_json(row) for row in value.get("anomalies", [])),
107
+ )
108
+
109
+ def ground_answer(
110
+ self,
111
+ answer: str,
112
+ *,
113
+ min_span_support_q16: int = 65535,
114
+ require_citations: bool = False,
115
+ reject_unsupported: bool = False,
116
+ ) -> "AnswerGroundingReportResponse":
117
+ from ..grounding import ground_answer
118
+
119
+ return ground_answer(
120
+ self,
121
+ answer,
122
+ min_span_support_q16=min_span_support_q16,
123
+ require_citations=require_citations,
124
+ reject_unsupported=reject_unsupported,
125
+ )
126
+
127
+
128
+ @dataclass(frozen=True)
129
+ class AnswerGroundingSpanResponse:
130
+ text: str
131
+ start_byte: int
132
+ end_byte: int
133
+ support_q16: int
134
+ supported: bool
135
+ covered_terms: tuple[str, ...]
136
+ missing_terms: tuple[str, ...]
137
+ supported_by_cell_ids: tuple[int, ...]
138
+ citations: tuple[str, ...]
139
+
140
+
141
+ @dataclass(frozen=True)
142
+ class AnswerGroundingReportResponse:
143
+ answer_supported: bool
144
+ rejected: bool
145
+ support_q16: int
146
+ supported_span_count: int
147
+ unsupported_span_count: int
148
+ spans: tuple[AnswerGroundingSpanResponse, ...]
149
+
150
+
151
+ @dataclass(frozen=True)
152
+ class GroundedAnswerResponse:
153
+ question: str
154
+ answer: str
155
+ retrieve_statement: str
156
+ verify_statement: str | None
157
+ context: ContextPackResponse
158
+ grounding: AnswerGroundingReportResponse
159
+ verification: "VerificationReportResponse | None"
160
+ citations: tuple[str, ...]
161
+ used_context_cell_ids: tuple[int, ...]
162
+ rejected: bool
163
+
@@ -0,0 +1,187 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class HealthResponse:
9
+ status: str
10
+ version: str
11
+ server_version: str
12
+
13
+ @classmethod
14
+ def from_json(cls, value: dict[str, Any]) -> "HealthResponse":
15
+ return cls(
16
+ status=str(value["status"]),
17
+ version=str(value["version"]),
18
+ server_version=str(value["server_version"]),
19
+ )
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class AqlQueryCacheStatsResponse:
24
+ entries: int
25
+ max_entries: int
26
+ hits: int
27
+ misses: int
28
+ evictions: int
29
+ catalog_invalidations: int
30
+ hit_rate_q16: int
31
+
32
+ @classmethod
33
+ def from_json(cls, value: dict[str, Any]) -> "AqlQueryCacheStatsResponse":
34
+ return cls(
35
+ entries=int(value.get("entries", 0)),
36
+ max_entries=int(value.get("max_entries", 0)),
37
+ hits=int(value.get("hits", 0)),
38
+ misses=int(value.get("misses", 0)),
39
+ evictions=int(value.get("evictions", 0)),
40
+ catalog_invalidations=int(value.get("catalog_invalidations", 0)),
41
+ hit_rate_q16=int(value.get("hit_rate_q16", 0)),
42
+ )
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class StatsResponse:
47
+ current_seq: int
48
+ checkpoint_seq: int
49
+ live_segments: int
50
+ retired_segments: int
51
+ memtable_cells: int
52
+ memtable_versions: int
53
+ memtable_payload_bytes: int
54
+ estimated_memtable_bytes: int
55
+ estimated_index_bytes: int
56
+ estimated_context_pack_bytes: int
57
+ estimated_total_memory_bytes: int
58
+ live_segment_bytes: int
59
+ retired_segment_bytes: int
60
+ total_segment_bytes: int
61
+ durable_storage_bytes: int
62
+ live_segment_payload_bytes: int
63
+ logical_payload_bytes: int
64
+ space_amplification_q16: int
65
+ write_amplification_q16: int
66
+ compaction_pressure_q16: int
67
+ wal_size_bytes: int
68
+ wal_writer_records: int
69
+ wal_writer_bytes: int
70
+ wal_writer_fsyncs: int
71
+ wal_writer_batches: int
72
+ aql_query_cache: AqlQueryCacheStatsResponse
73
+
74
+ @classmethod
75
+ def from_json(cls, value: dict[str, Any]) -> "StatsResponse":
76
+ return cls(
77
+ current_seq=int(value["current_seq"]),
78
+ checkpoint_seq=int(value["checkpoint_seq"]),
79
+ live_segments=int(value["live_segments"]),
80
+ retired_segments=int(value["retired_segments"]),
81
+ memtable_cells=int(value["memtable_cells"]),
82
+ memtable_versions=int(value["memtable_versions"]),
83
+ memtable_payload_bytes=int(value.get("memtable_payload_bytes", 0)),
84
+ estimated_memtable_bytes=int(value.get("estimated_memtable_bytes", 0)),
85
+ estimated_index_bytes=int(value.get("estimated_index_bytes", 0)),
86
+ estimated_context_pack_bytes=int(value.get("estimated_context_pack_bytes", 0)),
87
+ estimated_total_memory_bytes=int(value.get("estimated_total_memory_bytes", 0)),
88
+ live_segment_bytes=int(value.get("live_segment_bytes", 0)),
89
+ retired_segment_bytes=int(value.get("retired_segment_bytes", 0)),
90
+ total_segment_bytes=int(value.get("total_segment_bytes", 0)),
91
+ durable_storage_bytes=int(value.get("durable_storage_bytes", 0)),
92
+ live_segment_payload_bytes=int(value.get("live_segment_payload_bytes", 0)),
93
+ logical_payload_bytes=int(value.get("logical_payload_bytes", 0)),
94
+ space_amplification_q16=int(value.get("space_amplification_q16", 0)),
95
+ write_amplification_q16=int(value.get("write_amplification_q16", 0)),
96
+ compaction_pressure_q16=int(value.get("compaction_pressure_q16", 0)),
97
+ wal_size_bytes=int(value["wal_size_bytes"]),
98
+ wal_writer_records=int(value["wal_writer_records"]),
99
+ wal_writer_bytes=int(value["wal_writer_bytes"]),
100
+ wal_writer_fsyncs=int(value["wal_writer_fsyncs"]),
101
+ wal_writer_batches=int(value["wal_writer_batches"]),
102
+ aql_query_cache=AqlQueryCacheStatsResponse.from_json(
103
+ value.get("aql_query_cache", {})
104
+ ),
105
+ )
106
+
107
+
108
+ @dataclass(frozen=True)
109
+ class ValidationResponse:
110
+ ok: bool
111
+ manifest_ok: bool
112
+ wal_ok: bool
113
+ live_segments_checked: int
114
+ bitmap_indexes_checked: int
115
+ lexical_indexes_checked: int
116
+ vector_indexes_checked: int
117
+ hnsw_graphs_checked: int
118
+ cells_checked: int
119
+ wal_records_checked: int
120
+ wal_safe_truncate_offset: int
121
+ errors: tuple[str, ...]
122
+
123
+ @classmethod
124
+ def from_json(cls, value: dict[str, Any]) -> "ValidationResponse":
125
+ return cls(
126
+ ok=bool(value["ok"]),
127
+ manifest_ok=bool(value["manifest_ok"]),
128
+ wal_ok=bool(value["wal_ok"]),
129
+ live_segments_checked=int(value["live_segments_checked"]),
130
+ bitmap_indexes_checked=int(value["bitmap_indexes_checked"]),
131
+ lexical_indexes_checked=int(value["lexical_indexes_checked"]),
132
+ vector_indexes_checked=int(value["vector_indexes_checked"]),
133
+ hnsw_graphs_checked=int(value["hnsw_graphs_checked"]),
134
+ cells_checked=int(value["cells_checked"]),
135
+ wal_records_checked=int(value["wal_records_checked"]),
136
+ wal_safe_truncate_offset=int(value["wal_safe_truncate_offset"]),
137
+ errors=tuple(str(row) for row in value.get("errors", [])),
138
+ )
139
+
140
+
141
+ @dataclass(frozen=True)
142
+ class PutCellResponse:
143
+ seq: int
144
+ cell_id: int
145
+
146
+ @classmethod
147
+ def from_json(cls, value: dict[str, Any]) -> "PutCellResponse":
148
+ return cls(seq=int(value["seq"]), cell_id=int(value["cell_id"]))
149
+
150
+
151
+ @dataclass(frozen=True)
152
+ class CellResponse:
153
+ cell_id: int
154
+ payload: str
155
+
156
+ @classmethod
157
+ def from_json(cls, value: dict[str, Any]) -> "CellResponse":
158
+ return cls(cell_id=int(value["cell_id"]), payload=str(value["payload"]))
159
+
160
+
161
+ @dataclass(frozen=True)
162
+ class CellLookupResponse:
163
+ cell: CellResponse | None
164
+
165
+ @classmethod
166
+ def from_json(cls, value: dict[str, Any]) -> "CellLookupResponse":
167
+ cell = value.get("cell")
168
+ return cls(cell=CellResponse.from_json(cell) if cell else None)
169
+
170
+
171
+ @dataclass(frozen=True)
172
+ class AqlCellResponse:
173
+ cell_id: int
174
+ payload: str
175
+
176
+ @classmethod
177
+ def from_json(cls, value: dict[str, Any]) -> "AqlCellResponse":
178
+ return cls(cell_id=int(value["cell_id"]), payload=str(value["payload"]))
179
+
180
+
181
+ @dataclass(frozen=True)
182
+ class AqlResponse:
183
+ cells: tuple[AqlCellResponse, ...]
184
+
185
+ @classmethod
186
+ def from_json(cls, value: dict[str, Any]) -> "AqlResponse":
187
+ return cls(cells=tuple(AqlCellResponse.from_json(row) for row in value["cells"]))
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class IngestResponse:
9
+ rows_ingested: int
10
+ chunks_ingested: int
11
+ facts_ingested: int
12
+ first_cell_id: int | None
13
+ job_id: int | None
14
+ validation_report: dict[str, Any]
15
+
16
+ @classmethod
17
+ def from_json(cls, value: dict[str, Any]) -> "IngestResponse":
18
+ return cls(
19
+ rows_ingested=int(value["rows_ingested"]),
20
+ chunks_ingested=int(value["chunks_ingested"]),
21
+ facts_ingested=int(value["facts_ingested"]),
22
+ first_cell_id=int(value["first_cell_id"]) if value.get("first_cell_id") is not None else None,
23
+ job_id=int(value["job_id"]) if value.get("job_id") is not None else None,
24
+ validation_report=dict(value.get("validation_report", {})),
25
+ )
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class IngestionJobResponse:
30
+ job_id: int
31
+ label: str
32
+ status: str
33
+ total_items: int | None
34
+ completed_items: int
35
+ failed_items: int
36
+ last_cell_id: int | None
37
+ message: str | None
38
+ retry_count: int = 0
39
+ max_retries: int = 3
40
+
41
+ @classmethod
42
+ def from_json(cls, value: dict[str, Any]) -> "IngestionJobResponse":
43
+ return cls(
44
+ job_id=int(value["job_id"]),
45
+ label=str(value["label"]),
46
+ status=str(value["status"]),
47
+ total_items=int(value["total_items"]) if value.get("total_items") is not None else None,
48
+ completed_items=int(value["completed_items"]),
49
+ failed_items=int(value["failed_items"]),
50
+ last_cell_id=int(value["last_cell_id"]) if value.get("last_cell_id") is not None else None,
51
+ message=str(value["message"]) if value.get("message") is not None else None,
52
+ retry_count=int(value.get("retry_count", 0)),
53
+ max_retries=int(value.get("max_retries", 3)),
54
+ )
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class DeleteJobResponse:
59
+ deleted: bool
60
+
61
+ @classmethod
62
+ def from_json(cls, value: dict[str, Any]) -> "DeleteJobResponse":
63
+ return cls(deleted=bool(value["deleted"]))
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class RememberResponse:
9
+ seq: int
10
+ cell_id: int
11
+ ttl_seconds: int | None
12
+
13
+ @classmethod
14
+ def from_json(cls, value: dict[str, Any]) -> "RememberResponse":
15
+ return cls(
16
+ seq=int(value["seq"]),
17
+ cell_id=int(value["cell_id"]),
18
+ ttl_seconds=int(value["ttl_seconds"]) if value.get("ttl_seconds") is not None else None,
19
+ )
20
+