# 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-машине дать неправильный образ для отправки. ### 11. Есть неоднозначность между README и PDF по sparse в `search` - `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