Внутренний конвейер обработки: как сырьё из источника превращается в нормализованную сущность и попадает в хранилище. Три стадии — Extract → Transform → Load. Source-specific — только вход (extract) и первый шаг трансформации (normalize); всё, что дальше, — общее для любого источника и для любого режима синхронизации.
fetch(since) → RawItem и выдают
поток RawItem — это и есть вход в Transform.
Саму механику выборки — подключение, аутентификацию, пагинацию
и инкремент по since — держит
sources.
Entity. Единственный шаг трансформации,
который пишется под конкретный источник.
draft / final /
archived.
fetch
может вернуть сущность дважды. Идемпотентность
между прогонами — не здесь, её даёт Load через
upsert по source_id + source_type + source_entity_id. Глубокий
entity resolution между источниками держит
data-model.
enrich: ссылки и упоминания в разметке
нужны для связей, а заголовки — для резки на фрагменты.
Чистится только копия под эмбеддинг; оригинал в
Entity остаётся нетронутым.
Entity
→ много фрагментов, у каждого
свой вектор. Целевой размер — 512 токенов
(рабочий диапазон 256–1024), тюнируемый
параметр. Окно модели — не цель, а потолок:
effective = min(target_chunk_tokens, model_max_tokens).
Большое окно эмбеддера (у text-embedding-3 —
8192) даёт запас от обрезки длинных секций, а не
разрешение делать фрагмент на всё окно: чем длиннее текст под
одним вектором, тем сильнее он «усредняется» и тем хуже
попадания поиска.
normalize: заголовки, секции, сообщение
или тред. Граница естественная — нахлёст не нужен.
overlap = 0.
chunk и
embed на два дома. Интринсики модели — макс. токенов
окна, обязательный префикс
(query:/passage:), размерность вектора
— живут в
реестре AI-моделей;
чанкер читает потолок оттуда.
Тюнируемые ручки — target_chunk_tokens,
overlap_pct, размеры по типам контента — в конфиге.
На стыке стоит валидатор:
target_chunk_tokens ≤ model.max_input_tokens.
source_id + source_type + source_entity_id: каждая сущность пишется
ровно в свою строку, повторный прогон обновляет её, а не
плодит дубли. Идемпотентность распространяется и на фрагменты:
их вектора — дочерние строки сущности, и тем же прогоном их
набор сверяется целиком — устаревшие удаляются, новые
добавляются, осиротевших векторов от прежней версии не остаётся.
Неизменные фрагменты при этом не переэмбеддятся: вектор
переиспользуется по контент-хешу, инференс бьёт только по тому,
что реально изменилось. Главная гарантия модуля: повторная
синхронизация и подключение нового источника спустя время не
ломают уже собранные данные. Одна сущность при этом ложится
сразу в несколько приёмников хранения:
RawItem →
Entity → сохранено
Конвейер держится на двух контрактах. Каждая стадия знает только их — поэтому всё после normalize остаётся общим.
RawItemEntityEntity и проходят
один и тот же конвейер. Полную модель держит
data-model.