From 9db1d12192379598bb52badf809affd05624ed89 Mon Sep 17 00:00:00 2001 From: Hitoshi-Hub Date: Sat, 18 Apr 2026 11:24:11 +0300 Subject: [PATCH] todo list update and add filter in search/main.py (def qdrant_search) --- doc/todo.md | 35 ++++++++++++++++++++++++++--------- search/main.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/doc/todo.md b/doc/todo.md index 3d60579..0b4a107 100644 --- a/doc/todo.md +++ b/doc/todo.md @@ -1,5 +1,7 @@ # TODO +## `search/main.py` + - [ ] P0: Переключить основной query на `question.search_text` с fallback на `question.text` - [ ] P0: Подключить `question.variants` как дополнительные query-варианты - [ ] P0: Подключить `question.hyde` как дополнительные dense-запросы @@ -8,6 +10,16 @@ - [ ] P0: Дедуплицировать `message_ids` перед ответом - [ ] P0: Ограничить финальную выдачу до `top-50` - [ ] P0: Агрегировать score по `message_id` +- [ ] P2: Использовать `entities.people` и `entities.emails` для boost или фильтрации +- [ ] P2: Использовать `entities.documents`, `entities.names`, `entities.links` для lexical boost +- [ ] P2: Использовать `date_range` для фильтрации по `metadata.start` и `metadata.end` +- [ ] P2: Использовать `contains_quote` и `contains_forward` как сигналы ранжирования +- [ ] P2: Добавить multi-query fusion в `Qdrant` +- [ ] P2: Подобрать `prefetch`, `retrieve_k`, `rerank_limit` +- [ ] P3: Добавить retry и timeout политику для dense/rerank HTTP вызовов + +## `index/main.py` + - [ ] P1: Перейти с символьного chunking на chunking по сообщениям - [ ] P1: Учитывать time gap при сборке чанков - [ ] P1: Маркировать в тексте `quote`, `forward`, автора сообщения и автора цитаты @@ -15,18 +27,23 @@ - [ ] P1: Материализовать `sender_id` и `mentions` в индексируемый текст - [ ] P1: Разбирать `file_snippets` и вытаскивать имя файла, mime и url - [ ] P1: Разбирать `member_event` и превращать его в индексируемый текст -- [ ] P2: Использовать `entities.people` и `entities.emails` для boost или фильтрации -- [ ] P2: Использовать `entities.documents`, `entities.names`, `entities.links` для lexical boost -- [ ] P2: Использовать `date_range` для фильтрации по `metadata.start` и `metadata.end` -- [ ] P2: Использовать `contains_quote` и `contains_forward` как сигналы ранжирования -- [ ] P2: Добавить multi-query fusion в `Qdrant` -- [ ] P2: Подобрать `prefetch`, `retrieve_k`, `rerank_limit` + +## `docker-compose.yml` + - [ ] P3: Привести локальный `docker-compose.yml` к схеме с `API_KEY` - [ ] P3: Добавить `--platform linux/amd64` в сборку образов -- [ ] P3: Добавить retry и timeout политику для dense/rerank HTTP вызовов + +## `doc/` (новый файл с регрессионными вопросами) + - [ ] P3: Зафиксировать набор локальных тестовых вопросов для проверки регрессий + +## `search/requirements.txt` + +- [ ] P4: Добавить `python-dateutil` или `dateparser` +- [ ] P4: Добавить `tenacity` + +## `index/requirements.txt` и/или `search/requirements.txt` + - [ ] P4: Добавить `razdel` - [ ] P4: Добавить `pymorphy3` - [ ] P4: Добавить `rapidfuzz` -- [ ] P4: Добавить `python-dateutil` или `dateparser` -- [ ] P4: Добавить `tenacity` diff --git a/search/main.py b/search/main.py index f9efb24..c5063aa 100644 --- a/search/main.py +++ b/search/main.py @@ -206,12 +206,40 @@ async def embed_sparse(text: str) -> SparseVector: values=[float(value) for value in item.values.tolist()], ) +# ПЕРЕПИСАТЬ async def qdrant_search( client: AsyncQdrantClient, dense_vector: list[float], sparse_vector: SparseVector, + question_data: Question ) -> Any | None: + must_conditions: [] + + # Фильтр по диапазону дат (поле metadata.start в Qdrant) [cite: 147, 148, 175] + if question_data.date_range: + must_conditions.append( + models.FieldCondition( + key="metadata.start", + range=models.Range( + gte=question_data.date_range.from_, + lte=question_data.date_range.to_ + ) + ) + ) + + # Фильтр по автору вопроса (поле metadata.participants) [cite: 161, 163] + if question_data.asker: + must_conditions.append( + models.FieldCondition( + key="metadata.participants", + match=models.MatchValue(value=question_data.asker) + ) + ) + + # Создаем итоговый объект фильтра, если есть условия + search_filter = models.Filter(must=must_conditions) if must_conditions else None + response = await client.query_points( collection_name=QDRANT_COLLECTION_NAME, prefetch=[ @@ -219,6 +247,7 @@ async def qdrant_search( query=dense_vector, using=QDRANT_DENSE_VECTOR_NAME, limit=DENSE_PREFETCH_K, + filter=search_filter, ), models.Prefetch( query=models.SparseVector( @@ -227,6 +256,7 @@ async def qdrant_search( ), using=QDRANT_SPARSE_VECTOR_NAME, limit=SPRASE_PREFETCH_K, + filter=search_filter, ), ], query=models.FusionQuery(fusion=models.Fusion.RRF), @@ -313,7 +343,7 @@ async def search(payload: SearchAPIRequest) -> SearchAPIResponse: dense_vector = await embed_dense(client, query) sparse_vector = await embed_sparse(query) - best_points = await qdrant_search(qdrant, dense_vector, sparse_vector) + best_points = await qdrant_search(qdrant, dense_vector, sparse_vector, payload.question) if best_points is None: return SearchAPIResponse(results=[]) -- 2.45.2