vk_hackathon/doc/ai_update.md
2026-04-18 11:13:56 +03:00

14 KiB
Raw Permalink Blame History

AI Update по ТЗ

Дата: 2026-04-18

Что просмотрено

  • doc/ТЗа_хакатон_Индексация_и_поиск_по_сообщениям.pdf
  • README.md
  • docker-compose.yml
  • index/main.py, search/main.py
  • index/Dockerfile, search/Dockerfile
  • index/Makefile, search/Makefile
  • data/Go Nova.json

По примеру данных:

  • всего сообщений: 25
  • с parts: 14
  • с цитатами: 5
  • с пересланными сообщениями: 2
  • с mentions: 4
  • с file_snippets: 1
  • системных сообщений: 1

Это важно, потому что в текущем коде часть этих сигналов либо не используется вообще, либо теряет смысл при индексации.

Что уже соответствует ТЗ

  1. В репозитории есть оба требуемых сервиса: index и search.
  2. Обязательные endpoints реализованы:
    • GET /health, POST /index, POST /sparse_embedding в index/main.py
    • GET /health, POST /search в search/main.py
  3. Контракты request/response по основным endpoint'ам не менялись и в целом совпадают с шаблоном и ТЗ.
  4. search использует Qdrant, dense endpoint и reranker через HTTP.
  5. В обоих Dockerfile sparse-модель предзагружается внутрь образа, что соответствует оффлайн-ограничению контейнеров.
  6. HOST и PORT читаются из env, как требует ТЗ.

Что отсутствует или реализовано частично

1. Обогащенный вопрос из ТЗ почти не используется

В search/main.py:90-100 описаны поля:

  • search_text
  • variants
  • hyde
  • keywords
  • entities
  • date_mentions
  • date_range
  • asker

Но в реальном поиске используется только question.text:

  • search/main.py:307-316

Это главный недобор относительно ТЗ. Само ТЗ явно дает эти поля как сигналы для retrieval, а код их сейчас просто игнорирует.

2. Метаданные чанков объявлены, но не участвуют в поиске

В README.md:52-58 отдельно сказано, что в metadata чанка сохраняются:

  • participants
  • mentions
  • contains_forward
  • contains_quote

В search/main.py:132-145 есть модель ChunkMetadata, но дальше она никак не используется в query_points. Поиск не делает:

  • фильтрацию по mentions
  • фильтрацию по participants
  • учет thread_sn
  • учет временного диапазона через start/end
  • отдельную обработку quote/forward чанков

То есть сильный канал улучшения качества уже предусмотрен схемой, но сейчас не задействован.

3. Реранк отбрасывает часть кандидатов

Сейчас:

  • retrieval берет до 20 чанков: search/main.py:174-177
  • rerank берет только первые 10: search/main.py:278-296
  • после rerank возвращаются только эти 10, а хвост 11-20 теряется: search/main.py:321-328

Это не нарушение контракта, но это реальная потеря recall.

4. Выдача не дедуплицируется и не ограничивается по полезному top-K

Сейчас message_ids просто конкатенируются:

  • search/main.py:323-328

Проблемы:

  • дубликаты message id не удаляются
  • результаты не агрегируются по лучшему score сообщения
  • нет явного ограничения на топ полезных 50, хотя именно K=50 участвует в метрике из ТЗ

Если один и тот же message_id попал в несколько чанков, он тратит место в выдаче.

5. Индексация пока очень базовая: фиксированные символьные чанки

В index/main.py:118-190 чанки строятся просто по длине строки:

  • CHUNK_SIZE = 512
  • OVERLAP_SIZE = 256
  • разбиение идет по символам, а не по сообщениям, тайм-гепам, тредам или смысловым блокам

Из-за этого:

  • длинные пересланные сообщения и цитаты могут резаться в неудобных местах
  • один и тот же смысловой блок может быть разнесен по чанкам неестественно
  • overlap строится по хвосту текста, а не по границе сообщений

6. page_content, dense_content и sparse_content сейчас одинаковые

См. index/main.py:180-186.

ТЗ прямо оставляет это место как точку оптимизации качества, но пока этот резерв не используется.

7. Индексация берет только text и parts[*].text, остальное почти теряется

См. index/main.py:99-115.

Сейчас не используются как поисковые сигналы:

  • sender_id
  • mentions
  • file_snippets
  • member_event
  • thread_sn
  • is_hidden
  • is_system
  • явное различение quote и forward

Особенно важные пробелы:

  • member_event у системных сообщений сейчас фактически пропадает, если обычного текста нет
  • file_snippets не разбирается, хотя там могут быть имена файлов, URL и служебные поля
  • запросы вида "кто писал", "кого упоминали", "какой файл/документ кидали" сейчас поддержаны слабо

8. Смысл quote и forward не маркируется

В index/main.py:105-113 текст из parts просто подшивается в общий текст без явных маркеров вида:

  • "цитата:"
  • "пересланное сообщение:"
  • "автор цитаты:"

В итоге dense/sparse видят просто общий текстовый комок. Для поиска по обсуждениям это ощутимая потеря контекста.

9. Есть расхождение между локальной инфраструктурой и ТЗ

По ТЗ для search ожидается API_KEY.

В коде это поддержано:

  • search/main.py:21-30
  • search/main.py:42-47
  • search/Makefile:10-15

Но локальный docker-compose.yml:57-60 требует OPEN_API_LOGIN и OPEN_API_PASSWORD.

Итог:

  • сам сервис гибче ТЗ
  • локальный compose не повторяет боевую схему из ТЗ один в один

Это не ломает контракт, но может запутать при локальной отладке.

10. Есть еще одна инфраструктурная несостыковка со сдачей

В doc/upload_to_docker.md:45-48 явно сказано собирать образы с --platform linux/amd64.

Но index/Makefile:16-18 и search/Makefile:26-28 собирают без --platform linux/amd64.

На x86 это может пройти незаметно, а на ARM-машине дать неправильный образ для отправки.

  • README.md:211 говорит, что sparse-модель для search должна быть локально внутри образа
  • PDF в формулировке требований к Search Service делает акцент, что обращения к dense/sparse/rerank идут через проверяющую систему

Текущий код следует логике README/example: sparse считается локально в search/main.py:148-151 и search/main.py:198-207.

Я бы это не считал блокером, но как минимум это место стоит держать в голове как неоднозначное требование.

Что улучшать в первую очередь

Приоритет 1. Начать использовать все поля question

Минимально стоит задействовать:

  • search_text как основной нормализованный запрос
  • variants как дополнительные формулировки
  • hyde как дополнительные dense-запросы
  • keywords как основу для sparse
  • entities для фильтров и lexical boost
  • date_range и date_mentions для ограничения по времени
  • asker как сигнал по людям и email

Самый логичный путь без смены стэка: несколько dense/sparse запросов + fusion в Qdrant.

Приоритет 2. Перестроить chunking под структуру чата, а не под символы

Нужны чанки по:

  • окнам сообщений
  • временным разрывам
  • границам thread/forward/quote
  • ограничению на размер по сообщениям, а не только по символам

Для чатов это обычно дает больше пользы, чем любые косметические тюнинги rerank.

Приоритет 3. Развести page_content, dense_content, sparse_content

Хорошая схема:

  • page_content: читабельный исходный текст чанка
  • dense_content: нормализованный текст с ролями, автором, маркерами quote/forward
  • sparse_content: keyword-heavy версия с email, mentions, именами файлов, ссылками, документами, леммами

Сейчас эта возможность не используется вообще.

Приоритет 4. Нормально собирать финальную выдачу

Нужно:

  • не терять кандидатов после rerank
  • удалять дубликаты message_id
  • агрегировать по лучшему score сообщения или чанка
  • отдавать осмысленный top-50

Это прямой выигрыш по Recall@50 и nDCG@50.

Приоритет 5. Начать использовать metadata в Qdrant

Особенно полезно для:

  • mentions
  • participants
  • contains_quote
  • contains_forward
  • thread_sn
  • start / end

Для многих вопросов это позволит не просто "лучше ранжировать", а сразу отрезать нерелевантный шум.

Приоритет 6. Превратить скрытые сигналы в индексируемый текст

Стоит отдельно материализовать:

  • member_event в текст вида "пользователь X добавил Y"
  • file_snippets в текст вида "файл: NAME, url: ..."
  • автора сообщения
  • список упомянутых пользователей

Сейчас эти сигналы либо не попадают в индекс, либо попадают слишком слабо.

Что можно добавить по Python-библиотекам, не меняя стек

Стек Qdrant менять не нужно. Самые полезные добавки я бы смотрел такие:

  • pymorphy3 для лемматизации русских слов при подготовке sparse_content
  • razdel для аккуратной токенизации русского текста
  • rapidfuzz для точного lexical match по именам, email, документам, ссылкам и названиям
  • python-dateutil или dateparser для нормализации дат, если захотите усиливать работу с date_mentions
  • tenacity для аккуратных retry/timeout-оберток вокруг dense/rerank HTTP вызовов

Что не нужно делать:

  • менять Qdrant
  • тащить внешние LLM/API
  • усложнять архитектуру ради "модности", пока не использованы базовые сигналы из самого ТЗ

Короткий вывод

Сейчас репозиторий соответствует ТЗ как рабочий базовый шаблон, но почти не использует те сигналы, ради которых это ТЗ вообще интересно:

  • обогащение вопроса
  • metadata чанков
  • структуру chat messages
  • сигналы автора, упоминаний, файлов, системных событий, цитат и пересылок

Самый большой потенциал улучшения здесь не в замене базы или модели, а в трех вещах:

  1. умный chunking
  2. multi-query hybrid retrieval
  3. использование metadata и нормальной сборки финального top-50