Перейти к содержанию

Indexer

Indexer переводит сохранённую закупку и её документы в поисковые chunks. Он принимает at-least-once события, поэтому каждый шаг спроектирован как повторяемый и version-safe.

Вход
tender.changed.v1, PostgreSQL rows, attachment files
Выход
chunks + VECTOR(1024), indexed_hash, index_status
AI
FakeAIProvider в CI или Ollama embeddings в live
Точка запуска
python -m tender_lens.indexer

Карта файлов

Файл Назначение
extract.py метаданные + безопасное извлечение PDF/XML/HTML/JSON/TXT
chunk.py paragraph-first разбиение с overlap
service.py stale checks, batches и атомарная замена
__main__.py durable consumer, ACK/NAK/TERM policy

Извлечение

Каждый parser возвращает список TextUnit: attachment_id, человекочитаемая section, text.

Метаданные индексируются тремя units: название отдельно, описание отдельно и агрегированная карточка. Это не даёт длинному description полностью размыть embedding title. Для PDF unit соответствует странице; для остальных поддержанных форматов — документу.

Unknown binary возвращает пустой список, а ошибка отдельного supported attachment становится warning. Поэтому доступные metadata всё равно индексируются.

Chunking

chunk_units() сначала уважает абзацы, затем режет слишком длинный абзац по ближайшему пробелу. Максимум — 1500 символов, overlap — 150. Position монотонно растёт через все units одной закупки.

TextUnit A: [chunk 0] [overlap → chunk 1]
TextUnit B: [chunk 2] [overlap → chunk 3]

Overlap сохраняет контекст возле границы, но увеличивает объём индекса; параметры выбраны как MVP-компромисс.

Version-safe process

process() имеет три быстрых результата:

  • missing — tender удалён/не существует;
  • stale — event hash не равен текущему;
  • unchanged — этот hash уже готов.

Для новой версии сервис ставит processing, извлекает и chunk-ит вне длинной DB-транзакции, вызывает embeddings batch-ами, затем снова блокирует tender и сверяет hash. Только после этого одна транзакция удаляет старые chunks, вставляет новые и ставит ready.

Если embedding или insert падает, старая версия chunks не была удалена. _mark_failed() меняет status только если tender всё ещё имеет hash падающего event.

AI batches

EMBEDDING_BATCH_SIZE ограничивает число текстов в одном /api/embed. Это критично для CPU Ollama: один большой вызов избегает overhead отдельного HTTP request на chunk, а небольшие batches ограничивают память и latency.

Проверяются:

  • число vectors равно числу drafts;
  • каждый vector имеет 1024 компоненты;
  • результаты собираются в исходном порядке;
  • zip(..., strict=True) не скрывает рассинхронизацию.

Политика сообщения

Событие/ошибка Действие
невалидный JSON/Pydantic TERM
missing/stale/unchanged/ready ACK
Ollama/PostgreSQL/OSError NAK(delay=10)
неожиданная постоянная ошибка TERM

JetStream всё равно ограничивает доставку max_deliver=5, а ack_wait=300s должен быть больше нормальной индексации одного tender.