rememberstack 0.1.0__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.
- rememberstack/__init__.py +9 -0
- rememberstack/adapters/__init__.py +42 -0
- rememberstack/adapters/codex_writer.py +221 -0
- rememberstack/adapters/markitdown_converter.py +42 -0
- rememberstack/adapters/openrouter.py +136 -0
- rememberstack/adapters/selfhost/__init__.py +54 -0
- rememberstack/adapters/selfhost/forget.py +66 -0
- rememberstack/adapters/selfhost/git.py +374 -0
- rememberstack/adapters/selfhost/lance.py +328 -0
- rememberstack/adapters/selfhost/minio.py +279 -0
- rememberstack/adapters/selfhost/mounts.py +249 -0
- rememberstack/adapters/selfhost/object_store.py +130 -0
- rememberstack/adapters/selfhost/projection.py +80 -0
- rememberstack/adapters/selfhost/queue.py +137 -0
- rememberstack/adapters/selfhost/telemetry.py +45 -0
- rememberstack/adapters/selfhost/watcher.py +70 -0
- rememberstack/adapters/testing/__init__.py +15 -0
- rememberstack/adapters/testing/cost_meter.py +13 -0
- rememberstack/adapters/testing/model_provider.py +83 -0
- rememberstack/adapters/testing/queue.py +43 -0
- rememberstack/adapters/testing/telemetry.py +22 -0
- rememberstack/client.py +19 -0
- rememberstack/core/__init__.py +127 -0
- rememberstack/core/blockizer.py +189 -0
- rememberstack/core/chunker.py +216 -0
- rememberstack/core/consumption_skill.py +275 -0
- rememberstack/core/conversion.py +76 -0
- rememberstack/core/core_manifest.py +598 -0
- rememberstack/core/extension_packs.py +124 -0
- rememberstack/core/forget.py +17 -0
- rememberstack/core/knowledge_authored.py +276 -0
- rememberstack/core/knowledge_compile.py +215 -0
- rememberstack/core/knowledge_fact_sheet.py +210 -0
- rememberstack/core/knowledge_hashing.py +68 -0
- rememberstack/core/knowledge_planner.py +64 -0
- rememberstack/core/knowledge_writer.py +175 -0
- rememberstack/core/ranking.py +200 -0
- rememberstack/core/recipe_linter.py +149 -0
- rememberstack/core/section_snap.py +209 -0
- rememberstack/core/storage_routing.py +27 -0
- rememberstack/eval/__init__.py +53 -0
- rememberstack/eval/consumption.py +141 -0
- rememberstack/eval/contradiction.py +184 -0
- rememberstack/eval/harness.py +136 -0
- rememberstack/eval/lifecycle.py +400 -0
- rememberstack/eval/operational_scale.py +49 -0
- rememberstack/eval/resolution.py +255 -0
- rememberstack/eval/retrieval_spikes.py +50 -0
- rememberstack/eval/skeleton.py +231 -0
- rememberstack/llm/__init__.py +1 -0
- rememberstack/model/__init__.py +589 -0
- rememberstack/model/adjudication.py +100 -0
- rememberstack/model/auth.py +27 -0
- rememberstack/model/blocks.py +30 -0
- rememberstack/model/chunks.py +190 -0
- rememberstack/model/claims.py +162 -0
- rememberstack/model/client.py +98 -0
- rememberstack/model/clustering.py +54 -0
- rememberstack/model/component_version.py +124 -0
- rememberstack/model/consumption.py +88 -0
- rememberstack/model/conversion.py +31 -0
- rememberstack/model/deployment.py +53 -0
- rememberstack/model/documents.py +168 -0
- rememberstack/model/envelope.py +513 -0
- rememberstack/model/evaluation.py +72 -0
- rememberstack/model/forget.py +143 -0
- rememberstack/model/git.py +13 -0
- rememberstack/model/knowledge.py +840 -0
- rememberstack/model/knowledge_authored.py +325 -0
- rememberstack/model/knowledge_planner.py +431 -0
- rememberstack/model/lifecycle.py +42 -0
- rememberstack/model/model_provider.py +78 -0
- rememberstack/model/mounts.py +24 -0
- rememberstack/model/object_store.py +21 -0
- rememberstack/model/operational_scale.py +59 -0
- rememberstack/model/operations.py +153 -0
- rememberstack/model/processing.py +228 -0
- rememberstack/model/queue.py +73 -0
- rememberstack/model/recipes.py +83 -0
- rememberstack/model/relations.py +79 -0
- rememberstack/model/resolution.py +83 -0
- rememberstack/model/retrieval_spikes.py +62 -0
- rememberstack/model/sections.py +120 -0
- rememberstack/model/telemetry.py +30 -0
- rememberstack/ports/__init__.py +29 -0
- rememberstack/ports/auth.py +16 -0
- rememberstack/ports/connector.py +23 -0
- rememberstack/ports/cost_meter.py +17 -0
- rememberstack/ports/forget.py +20 -0
- rememberstack/ports/git.py +20 -0
- rememberstack/ports/model_provider.py +28 -0
- rememberstack/ports/mounts.py +16 -0
- rememberstack/ports/object_store.py +27 -0
- rememberstack/ports/p1_index.py +92 -0
- rememberstack/ports/purge.py +93 -0
- rememberstack/ports/queue.py +23 -0
- rememberstack/ports/telemetry.py +21 -0
- rememberstack/profiles/__init__.py +22 -0
- rememberstack/profiles/selfhost.py +324 -0
- rememberstack/profiles/selfhost_forget.py +158 -0
- rememberstack/profiles/selfhost_operations.py +95 -0
- rememberstack/py.typed +1 -0
- rememberstack/spine/__init__.py +93 -0
- rememberstack/spine/admission.py +26 -0
- rememberstack/spine/backfill.py +168 -0
- rememberstack/spine/catalog_contract.py +742 -0
- rememberstack/spine/chunk_catalog.py +237 -0
- rememberstack/spine/claim_catalog.py +298 -0
- rememberstack/spine/clustering.py +740 -0
- rememberstack/spine/component_versions.py +208 -0
- rememberstack/spine/consumption.py +81 -0
- rememberstack/spine/deployment_bootstrap.py +445 -0
- rememberstack/spine/document_catalog.py +621 -0
- rememberstack/spine/entity_registry.py +205 -0
- rememberstack/spine/extension_packs.py +220 -0
- rememberstack/spine/fact_catalog.py +571 -0
- rememberstack/spine/forget.py +1753 -0
- rememberstack/spine/knowledge.py +5467 -0
- rememberstack/spine/lifecycle.py +1071 -0
- rememberstack/spine/migrations/__init__.py +1 -0
- rememberstack/spine/migrations/_helpers.py +153 -0
- rememberstack/spine/migrations/env.py +58 -0
- rememberstack/spine/migrations/script.py.mako +27 -0
- rememberstack/spine/migrations/versions/__init__.py +1 -0
- rememberstack/spine/migrations/versions/p0_02_0001_extensions_enums.py +189 -0
- rememberstack/spine/migrations/versions/p0_02_0002_infrastructure_registries.py +321 -0
- rememberstack/spine/migrations/versions/p0_02_0003_entities_evaluation_e0_e1.py +631 -0
- rememberstack/spine/migrations/versions/p0_02_0004_claims_facts_evidence.py +411 -0
- rememberstack/spine/migrations/versions/p0_02_0005_projection_knowledge_retrieval.py +391 -0
- rememberstack/spine/migrations/versions/p0_02_0006_partitions_views.py +158 -0
- rememberstack/spine/migrations/versions/p2_06_0007_invalidated_outcome.py +26 -0
- rememberstack/spine/migrations/versions/p3_01_0008_document_version_target.py +58 -0
- rememberstack/spine/migrations/versions/p3_05_0009_reconcile_stage.py +27 -0
- rememberstack/spine/migrations/versions/p3_07_0010_lifecycle_eval_suite.py +25 -0
- rememberstack/spine/migrations/versions/p4_01_0011_survivor_view_rewrite.py +57 -0
- rememberstack/spine/migrations/versions/p6_02_0012_knowledge_compile_recovery.py +58 -0
- rememberstack/spine/migrations/versions/p6_04_0013_knowledge_writer_ledger.py +46 -0
- rememberstack/spine/migrations/versions/p6_05_0014_knowledge_planner_runtime.py +217 -0
- rememberstack/spine/migrations/versions/p6_06_0015_authored_dispatch_runtime.py +38 -0
- rememberstack/spine/migrations/versions/p7_02_0016_operational_eval_suite.py +19 -0
- rememberstack/spine/migrations/versions/p7_05_0017_hard_forget.py +55 -0
- rememberstack/spine/observation_adjudication.py +778 -0
- rememberstack/spine/operations.py +298 -0
- rememberstack/spine/projection.py +662 -0
- rememberstack/spine/recipes.py +276 -0
- rememberstack/spine/resolver.py +763 -0
- rememberstack/spine/review.py +650 -0
- rememberstack/spine/settings.py +22 -0
- rememberstack/spine/supersession.py +510 -0
- rememberstack/spine/sync.py +128 -0
- rememberstack/spine/work_ledger.py +816 -0
- rememberstack/surfaces/__init__.py +110 -0
- rememberstack/surfaces/cli.py +447 -0
- rememberstack/surfaces/consumption_skill.py +87 -0
- rememberstack/surfaces/graph_queries.py +698 -0
- rememberstack/surfaces/http_api.py +377 -0
- rememberstack/surfaces/mcp.py +67 -0
- rememberstack/surfaces/query_engine.py +1591 -0
- rememberstack/surfaces/recipe_executor.py +185 -0
- rememberstack/surfaces/recipe_surface.py +219 -0
- rememberstack/surfaces/remote_mcp.py +133 -0
- rememberstack/surfaces/sdk.py +324 -0
- rememberstack/workers/__init__.py +155 -0
- rememberstack/workers/base.py +312 -0
- rememberstack/workers/e0.py +577 -0
- rememberstack/workers/e1.py +425 -0
- rememberstack/workers/e2.py +525 -0
- rememberstack/workers/e3.py +434 -0
- rememberstack/workers/forget.py +299 -0
- rememberstack/workers/knowledge_authored.py +146 -0
- rememberstack/workers/knowledge_driver.py +735 -0
- rememberstack/workers/knowledge_fact_sheet.py +123 -0
- rememberstack/workers/knowledge_planner.py +325 -0
- rememberstack/workers/knowledge_writer.py +393 -0
- rememberstack/workers/operations.py +42 -0
- rememberstack/workers/p1.py +234 -0
- rememberstack/workers/p2.py +513 -0
- rememberstack/workers/p2_analytics.py +276 -0
- rememberstack/workers/p3.py +673 -0
- rememberstack/workers/reconcile.py +485 -0
- rememberstack/workers/sync.py +168 -0
- rememberstack-0.1.0.dist-info/METADATA +213 -0
- rememberstack-0.1.0.dist-info/RECORD +186 -0
- rememberstack-0.1.0.dist-info/WHEEL +4 -0
- rememberstack-0.1.0.dist-info/entry_points.txt +2 -0
- rememberstack-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"""The E1 chain (D58): anchor-stabilized chunking, context prefixes, embeddings.
|
|
2
|
+
|
|
3
|
+
The chunk stage packs the representation's block grid into section-bounded
|
|
4
|
+
chunks and records their reuse keys; the embed stage writes each chunk's
|
|
5
|
+
context prefix (the D63 conventional-mode branch), embeds prefix + text as one
|
|
6
|
+
per-document batch, and lands the vectors in the P1 chunk index.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
import json
|
|
11
|
+
from typing import Final
|
|
12
|
+
from uuid import UUID
|
|
13
|
+
from uuid import uuid4
|
|
14
|
+
|
|
15
|
+
from pydantic import Field
|
|
16
|
+
from pydantic_settings import BaseSettings
|
|
17
|
+
from pydantic_settings import SettingsConfigDict
|
|
18
|
+
|
|
19
|
+
from rememberstack.core import CHUNKER_VERSION
|
|
20
|
+
from rememberstack.core import chunker_version
|
|
21
|
+
from rememberstack.core import ChunkerParams
|
|
22
|
+
from rememberstack.core import extraction_input_hash
|
|
23
|
+
from rememberstack.core import pack_blocks
|
|
24
|
+
from rememberstack.model import Block
|
|
25
|
+
from rememberstack.model import CarryForwardSource
|
|
26
|
+
from rememberstack.model import ChunkForEmbedding
|
|
27
|
+
from rememberstack.model import ChunkRecord
|
|
28
|
+
from rememberstack.model import ChunkSource
|
|
29
|
+
from rememberstack.model import ClaimedWork
|
|
30
|
+
from rememberstack.model import ContextPrefix
|
|
31
|
+
from rememberstack.model import EmbeddingRequest
|
|
32
|
+
from rememberstack.model import EmbeddingUpdate
|
|
33
|
+
from rememberstack.model import EnqueueWork
|
|
34
|
+
from rememberstack.model import ModelRequest
|
|
35
|
+
from rememberstack.model import NonRetryableHandlerError
|
|
36
|
+
from rememberstack.model import ObjectKey
|
|
37
|
+
from rememberstack.model import P1ChunkRow
|
|
38
|
+
from rememberstack.model import PackedChunk
|
|
39
|
+
from rememberstack.model import PipelineStage
|
|
40
|
+
from rememberstack.ports.cost_meter import CostMeterPort
|
|
41
|
+
from rememberstack.ports.model_provider import ModelProviderPort
|
|
42
|
+
from rememberstack.ports.object_store import ObjectStorePort
|
|
43
|
+
from rememberstack.ports.p1_index import ChunkIndexPort
|
|
44
|
+
from rememberstack.spine.chunk_catalog import ChunkCatalog
|
|
45
|
+
from rememberstack.workers.base import HandlerOutcome
|
|
46
|
+
|
|
47
|
+
E1_CHUNK_VERSION: Final = CHUNKER_VERSION
|
|
48
|
+
"""The chunk stage's component version IS the chunker version (D12/D58)."""
|
|
49
|
+
|
|
50
|
+
E1_EMBED_VERSION: Final = "e1-embed-2026.07"
|
|
51
|
+
"""The embed stage's component version (model identity rides settings/stamps)."""
|
|
52
|
+
|
|
53
|
+
E1_PREFIXER_VERSION: Final = "e1-prefix-2026.07"
|
|
54
|
+
"""The context-prefix call's prompt generation (D58; conventional mode, D63)."""
|
|
55
|
+
|
|
56
|
+
E2_EXTRACTOR_VERSION: Final = "e2-extract-2026.07"
|
|
57
|
+
"""The extractor generation baked into extraction_input_hash (D56); the E2
|
|
58
|
+
stage (WP-1.3) binds its handler to this same constant."""
|
|
59
|
+
|
|
60
|
+
_PREFIX_PROMPT_TEMPLATE: Final = (
|
|
61
|
+
"In one sentence, state where this passage sits in the document — "
|
|
62
|
+
"document title, section, and what surrounds it. Passage from "
|
|
63
|
+
"{title!r}, section path {section_path}, chunk {ordinal}:\n\n{head}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class E1Settings(BaseSettings):
|
|
68
|
+
"""The E1 model bindings: per-deployment port configuration (D61/D63)."""
|
|
69
|
+
|
|
70
|
+
model_config = SettingsConfigDict(env_prefix="REMEMBERSTACK_E1_")
|
|
71
|
+
|
|
72
|
+
embedding_model: str = Field(default="qwen/qwen3-embedding-8b")
|
|
73
|
+
prefix_model: str = Field(default="openai/gpt-5.6-luna")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ChunkHandler:
|
|
77
|
+
"""The chunk stage: block grid → section-bounded, anchor-stabilized runs."""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
*,
|
|
82
|
+
catalog: ChunkCatalog,
|
|
83
|
+
artifact_store: ObjectStorePort,
|
|
84
|
+
params: ChunkerParams,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Bind the handler to its catalog, the artifacts bucket, and the params."""
|
|
87
|
+
self._catalog = catalog
|
|
88
|
+
self._artifact_store = artifact_store
|
|
89
|
+
self._params = params
|
|
90
|
+
self._chunker_version = chunker_version(params=params)
|
|
91
|
+
|
|
92
|
+
def handle(self, *, work: ClaimedWork, meter: CostMeterPort) -> HandlerOutcome:
|
|
93
|
+
"""Pack one representation into chunks and chain the embed stage.
|
|
94
|
+
|
|
95
|
+
Replay before regenerate (D7): rows this chunker generation already
|
|
96
|
+
packed for the version are kept as-is and the stage just re-chains.
|
|
97
|
+
"""
|
|
98
|
+
del meter
|
|
99
|
+
source = self._catalog.chunk_source(
|
|
100
|
+
representation_id=_payload_uuid(work=work, field="representation_id")
|
|
101
|
+
)
|
|
102
|
+
if self._catalog.existing_chunk_ids(
|
|
103
|
+
representation_id=source.representation_id,
|
|
104
|
+
chunker_version=self._chunker_version,
|
|
105
|
+
):
|
|
106
|
+
return _embed_follow_up(work=work, source=source)
|
|
107
|
+
document_md = self._artifact_store.read_bytes(
|
|
108
|
+
key=ObjectKey(source.markdown_uri)
|
|
109
|
+
).decode("utf-8")
|
|
110
|
+
blocks_doc = json.loads(
|
|
111
|
+
self._artifact_store.read_bytes(key=ObjectKey(source.blocks_uri))
|
|
112
|
+
)
|
|
113
|
+
blocks = tuple(Block.model_validate(block) for block in blocks_doc["blocks"])
|
|
114
|
+
packed = pack_blocks(
|
|
115
|
+
blocks=blocks,
|
|
116
|
+
sections=source.sections,
|
|
117
|
+
document_md=document_md,
|
|
118
|
+
params=self._params,
|
|
119
|
+
)
|
|
120
|
+
self._catalog.record_chunks(
|
|
121
|
+
records=tuple(
|
|
122
|
+
_chunk_record(
|
|
123
|
+
source=source,
|
|
124
|
+
packed=packed,
|
|
125
|
+
index=index,
|
|
126
|
+
chunker_version=self._chunker_version,
|
|
127
|
+
)
|
|
128
|
+
for index in range(len(packed))
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
return _embed_follow_up(work=work, source=source)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class EmbedChunksHandler:
|
|
135
|
+
"""The embed stage: context prefixes + one embedding batch per document.
|
|
136
|
+
|
|
137
|
+
The conventional-mode branch binds (D63): each chunk gets a generated
|
|
138
|
+
"where this sits" prefix, and prefix + verbatim text embed together. The
|
|
139
|
+
batch never crosses a document (D58's billing and lane rule).
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
*,
|
|
145
|
+
catalog: ChunkCatalog,
|
|
146
|
+
artifact_store: ObjectStorePort,
|
|
147
|
+
model_provider: ModelProviderPort,
|
|
148
|
+
chunk_index: ChunkIndexPort,
|
|
149
|
+
settings: E1Settings,
|
|
150
|
+
params: ChunkerParams,
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Bind the handler to its catalog, stores, provider, and P1 index.
|
|
153
|
+
|
|
154
|
+
`params` names the chunker generation whose rows this stage embeds —
|
|
155
|
+
the same parameters the composing profile gave the chunk stage.
|
|
156
|
+
"""
|
|
157
|
+
self._catalog = catalog
|
|
158
|
+
self._artifact_store = artifact_store
|
|
159
|
+
self._model_provider = model_provider
|
|
160
|
+
self._chunk_index = chunk_index
|
|
161
|
+
self._settings = settings
|
|
162
|
+
self._chunker_version = chunker_version(params=params)
|
|
163
|
+
|
|
164
|
+
def handle(self, *, work: ClaimedWork, meter: CostMeterPort) -> HandlerOutcome:
|
|
165
|
+
"""Prefix, embed, and index every chunk of one document version.
|
|
166
|
+
|
|
167
|
+
The D56/A3 carry-forward runs here: an unchanged chunk (same content
|
|
168
|
+
hash as a prior version's chunk in this lineage) keeps that chunk's
|
|
169
|
+
stored prefix and copies its vector — the model is called only for
|
|
170
|
+
chunks the edit actually touched. LLM output is never regenerated
|
|
171
|
+
for unchanged regions: it is the cost being avoided, and its
|
|
172
|
+
non-determinism would make every derived byte drift.
|
|
173
|
+
"""
|
|
174
|
+
source = self._catalog.chunk_source(
|
|
175
|
+
representation_id=_payload_uuid(work=work, field="representation_id")
|
|
176
|
+
)
|
|
177
|
+
chunks = self._catalog.chunks_for_embedding(
|
|
178
|
+
representation_id=source.representation_id,
|
|
179
|
+
chunker_version=self._chunker_version,
|
|
180
|
+
)
|
|
181
|
+
if not chunks:
|
|
182
|
+
return HandlerOutcome() # an empty document has nothing to index
|
|
183
|
+
document_md = self._artifact_store.read_bytes(
|
|
184
|
+
key=ObjectKey(source.markdown_uri)
|
|
185
|
+
).decode("utf-8")
|
|
186
|
+
carry = self._catalog.carry_forward_sources(
|
|
187
|
+
deployment_id=work.deployment_id,
|
|
188
|
+
doc_id=source.doc_id,
|
|
189
|
+
version_id=source.version_id,
|
|
190
|
+
prefixer_version=E1_PREFIXER_VERSION,
|
|
191
|
+
embedding_version=self._settings.embedding_model,
|
|
192
|
+
)
|
|
193
|
+
carried_vectors = self._carried_vectors(work=work, chunks=chunks, carry=carry)
|
|
194
|
+
prefixes = tuple(
|
|
195
|
+
self._resolve_prefix(
|
|
196
|
+
source=source,
|
|
197
|
+
chunk=chunk,
|
|
198
|
+
document_md=document_md,
|
|
199
|
+
carry=carry,
|
|
200
|
+
meter=meter,
|
|
201
|
+
)
|
|
202
|
+
for chunk in chunks
|
|
203
|
+
)
|
|
204
|
+
texts = tuple(
|
|
205
|
+
f"{prefix}\n\n{document_md[chunk.char_start : chunk.char_end]}"
|
|
206
|
+
for prefix, chunk in zip(prefixes, chunks, strict=True)
|
|
207
|
+
)
|
|
208
|
+
fresh = tuple(
|
|
209
|
+
index
|
|
210
|
+
for index in range(len(chunks))
|
|
211
|
+
if chunks[index].chunk_id not in carried_vectors
|
|
212
|
+
)
|
|
213
|
+
if fresh:
|
|
214
|
+
response = self._model_provider.embed(
|
|
215
|
+
request=EmbeddingRequest(
|
|
216
|
+
model=self._settings.embedding_model,
|
|
217
|
+
texts=tuple(texts[index] for index in fresh),
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
meter.record(
|
|
221
|
+
call_key="embed_chunks", tier="embedding", usage=response.usage
|
|
222
|
+
)
|
|
223
|
+
fresh_vectors = dict(
|
|
224
|
+
zip(
|
|
225
|
+
(chunks[index].chunk_id for index in fresh),
|
|
226
|
+
response.vectors,
|
|
227
|
+
strict=True,
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
fresh_vectors = {}
|
|
232
|
+
vectors = {**carried_vectors, **fresh_vectors}
|
|
233
|
+
self._chunk_index.upsert_chunks(
|
|
234
|
+
rows=tuple(
|
|
235
|
+
P1ChunkRow(
|
|
236
|
+
chunk_id=chunk.chunk_id,
|
|
237
|
+
deployment_id=work.deployment_id,
|
|
238
|
+
doc_id=chunk.doc_id,
|
|
239
|
+
version_id=chunk.version_id,
|
|
240
|
+
section_role=chunk.section_role,
|
|
241
|
+
text=text,
|
|
242
|
+
vector=vectors[chunk.chunk_id],
|
|
243
|
+
)
|
|
244
|
+
for chunk, text in zip(chunks, texts, strict=True)
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
self._catalog.record_embeddings(
|
|
248
|
+
updates=tuple(
|
|
249
|
+
EmbeddingUpdate(
|
|
250
|
+
chunk_id=chunk.chunk_id,
|
|
251
|
+
embedding_ref=str(chunk.chunk_id),
|
|
252
|
+
embedding_version=self._settings.embedding_model,
|
|
253
|
+
context_prefix=prefix,
|
|
254
|
+
prefixer_version=E1_PREFIXER_VERSION,
|
|
255
|
+
)
|
|
256
|
+
for chunk, prefix in zip(chunks, prefixes, strict=True)
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
return HandlerOutcome(
|
|
260
|
+
follow_up=(
|
|
261
|
+
EnqueueWork(
|
|
262
|
+
deployment_id=work.deployment_id,
|
|
263
|
+
target_kind=work.target_kind,
|
|
264
|
+
target_id=work.target_id,
|
|
265
|
+
stage=PipelineStage.EXTRACT_CLAIMS,
|
|
266
|
+
component_version=E2_EXTRACTOR_VERSION,
|
|
267
|
+
content_hash=work.content_hash,
|
|
268
|
+
lane=work.lane,
|
|
269
|
+
payload={
|
|
270
|
+
"version_id": str(source.version_id),
|
|
271
|
+
"representation_id": str(source.representation_id),
|
|
272
|
+
},
|
|
273
|
+
),
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def _carried_vectors(
|
|
278
|
+
self,
|
|
279
|
+
*,
|
|
280
|
+
work: ClaimedWork,
|
|
281
|
+
chunks: tuple[ChunkForEmbedding, ...],
|
|
282
|
+
carry: dict[str, CarryForwardSource],
|
|
283
|
+
) -> dict[UUID, tuple[float, ...]]:
|
|
284
|
+
"""Copy prior versions' vectors for unchanged chunks (D56).
|
|
285
|
+
|
|
286
|
+
A carried chunk whose vector is missing from the index (pruned or
|
|
287
|
+
never landed) simply falls back to the fresh-embed path — reuse is
|
|
288
|
+
an economy, never a correctness dependency.
|
|
289
|
+
"""
|
|
290
|
+
wanted = {
|
|
291
|
+
str(carry[chunk.chunk_content_hash].chunk_id): chunk.chunk_id
|
|
292
|
+
for chunk in chunks
|
|
293
|
+
if chunk.chunk_content_hash in carry
|
|
294
|
+
}
|
|
295
|
+
if not wanted:
|
|
296
|
+
return {}
|
|
297
|
+
stored = self._chunk_index.chunk_vectors(
|
|
298
|
+
deployment_id=str(work.deployment_id), chunk_ids=tuple(wanted)
|
|
299
|
+
)
|
|
300
|
+
return {
|
|
301
|
+
wanted[prior_id]: vector
|
|
302
|
+
for prior_id, vector in stored.items()
|
|
303
|
+
if prior_id in wanted
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
def _resolve_prefix(
|
|
307
|
+
self,
|
|
308
|
+
*,
|
|
309
|
+
source: ChunkSource,
|
|
310
|
+
chunk: ChunkForEmbedding,
|
|
311
|
+
document_md: str,
|
|
312
|
+
carry: dict[str, CarryForwardSource],
|
|
313
|
+
meter: CostMeterPort,
|
|
314
|
+
) -> str:
|
|
315
|
+
"""One chunk's "where this sits" sentence: replayed if already stored.
|
|
316
|
+
|
|
317
|
+
Resolution order (D7 replay, then D56 carry-forward, then the model):
|
|
318
|
+
the row's own stored prefix of this generation; a prior version's
|
|
319
|
+
stored prefix for the same content hash; only then a model call.
|
|
320
|
+
"""
|
|
321
|
+
if (
|
|
322
|
+
chunk.context_prefix is not None
|
|
323
|
+
and chunk.prefixer_version == E1_PREFIXER_VERSION
|
|
324
|
+
):
|
|
325
|
+
return chunk.context_prefix
|
|
326
|
+
carried = carry.get(chunk.chunk_content_hash)
|
|
327
|
+
if carried is not None:
|
|
328
|
+
return carried.context_prefix
|
|
329
|
+
head = document_md[chunk.char_start : chunk.char_end][:400]
|
|
330
|
+
prompt = _PREFIX_PROMPT_TEMPLATE.format(
|
|
331
|
+
title=source.title or "untitled",
|
|
332
|
+
section_path=chunk.section_path,
|
|
333
|
+
ordinal=chunk.ordinal,
|
|
334
|
+
head=head,
|
|
335
|
+
)
|
|
336
|
+
response = self._model_provider.generate(
|
|
337
|
+
request=ModelRequest(model=self._settings.prefix_model, prompt=prompt),
|
|
338
|
+
response_type=ContextPrefix,
|
|
339
|
+
)
|
|
340
|
+
meter.record(
|
|
341
|
+
call_key=f"prefix:{chunk.chunk_id}", tier="prefix", usage=response.usage
|
|
342
|
+
)
|
|
343
|
+
return response.output.prefix
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _chunk_record(
|
|
347
|
+
*,
|
|
348
|
+
source: ChunkSource,
|
|
349
|
+
packed: tuple[PackedChunk, ...],
|
|
350
|
+
index: int,
|
|
351
|
+
chunker_version: str,
|
|
352
|
+
) -> ChunkRecord:
|
|
353
|
+
"""Build one chunk row, deriving its D56 reuse key from stable inputs only."""
|
|
354
|
+
chunk = packed[index]
|
|
355
|
+
neighbor_hashes = tuple(
|
|
356
|
+
packed[neighbor].chunk_content_hash
|
|
357
|
+
for neighbor in (index - 1, index + 1)
|
|
358
|
+
if 0 <= neighbor < len(packed)
|
|
359
|
+
)
|
|
360
|
+
header_facts = (
|
|
361
|
+
source.title or "",
|
|
362
|
+
source.source_kind,
|
|
363
|
+
_isoformat_or_empty(value=source.source_modified_at),
|
|
364
|
+
_isoformat_or_empty(value=source.published_at),
|
|
365
|
+
source.language or "",
|
|
366
|
+
)
|
|
367
|
+
return ChunkRecord(
|
|
368
|
+
chunk_id=uuid4(),
|
|
369
|
+
deployment_id=source.deployment_id,
|
|
370
|
+
doc_id=source.doc_id,
|
|
371
|
+
version_id=source.version_id,
|
|
372
|
+
representation_id=source.representation_id,
|
|
373
|
+
section_id=chunk.section_id,
|
|
374
|
+
ordinal=chunk.ordinal,
|
|
375
|
+
block_start=chunk.block_start,
|
|
376
|
+
block_end=chunk.block_end,
|
|
377
|
+
chunk_content_hash=chunk.chunk_content_hash,
|
|
378
|
+
extraction_input_hash=extraction_input_hash(
|
|
379
|
+
own_block_hashes=(chunk.chunk_content_hash,),
|
|
380
|
+
neighbor_block_hashes=neighbor_hashes,
|
|
381
|
+
header_facts=header_facts,
|
|
382
|
+
extractor_version=E2_EXTRACTOR_VERSION,
|
|
383
|
+
structurer_version=source.structurer_version,
|
|
384
|
+
),
|
|
385
|
+
char_start=chunk.char_start,
|
|
386
|
+
char_end=chunk.char_end,
|
|
387
|
+
token_count=chunk.token_count,
|
|
388
|
+
chunker_version=chunker_version,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _embed_follow_up(*, work: ClaimedWork, source: ChunkSource) -> HandlerOutcome:
|
|
393
|
+
"""Chain the embed stage for one (version, representation)."""
|
|
394
|
+
return HandlerOutcome(
|
|
395
|
+
follow_up=(
|
|
396
|
+
EnqueueWork(
|
|
397
|
+
deployment_id=work.deployment_id,
|
|
398
|
+
target_kind=work.target_kind,
|
|
399
|
+
target_id=work.target_id,
|
|
400
|
+
stage=PipelineStage.EMBED_CHUNK,
|
|
401
|
+
component_version=E1_EMBED_VERSION,
|
|
402
|
+
content_hash=work.content_hash,
|
|
403
|
+
lane=work.lane,
|
|
404
|
+
payload={
|
|
405
|
+
"version_id": str(source.version_id),
|
|
406
|
+
"representation_id": str(source.representation_id),
|
|
407
|
+
},
|
|
408
|
+
),
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _isoformat_or_empty(*, value: datetime | None) -> str:
|
|
414
|
+
"""Render an optional datetime deterministically for the reuse key."""
|
|
415
|
+
return "" if value is None else value.isoformat()
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _payload_uuid(*, work: ClaimedWork, field: str) -> UUID:
|
|
419
|
+
"""Read a required UUID from the claimed payload; absence is non-retryable."""
|
|
420
|
+
value = (work.payload or {}).get(field)
|
|
421
|
+
if not isinstance(value, str):
|
|
422
|
+
raise NonRetryableHandlerError(
|
|
423
|
+
f"stage {work.stage} work {work.processing_id} carries no {field!r} payload"
|
|
424
|
+
)
|
|
425
|
+
return UUID(value)
|