Compare commits

..

No commits in common. "3ea1cd30668a559c35540f6918880a372182302f" and "20b401cf017cca126eec292591066449c2f96697" have entirely different histories.

4 changed files with 10 additions and 122 deletions

View file

@ -1,7 +1,5 @@
# TODO # TODO
## `search/main.py`
- [ ] P0: Переключить основной query на `question.search_text` с fallback на `question.text` - [ ] P0: Переключить основной query на `question.search_text` с fallback на `question.text`
- [ ] P0: Подключить `question.variants` как дополнительные query-варианты - [ ] P0: Подключить `question.variants` как дополнительные query-варианты
- [ ] P0: Подключить `question.hyde` как дополнительные dense-запросы - [ ] P0: Подключить `question.hyde` как дополнительные dense-запросы
@ -10,16 +8,6 @@
- [ ] P0: Дедуплицировать `message_ids` перед ответом - [ ] P0: Дедуплицировать `message_ids` перед ответом
- [ ] P0: Ограничить финальную выдачу до `top-50` - [ ] P0: Ограничить финальную выдачу до `top-50`
- [ ] P0: Агрегировать score по `message_id` - [ ] 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: Перейти с символьного chunking на chunking по сообщениям
- [ ] P1: Учитывать time gap при сборке чанков - [ ] P1: Учитывать time gap при сборке чанков
- [ ] P1: Маркировать в тексте `quote`, `forward`, автора сообщения и автора цитаты - [ ] P1: Маркировать в тексте `quote`, `forward`, автора сообщения и автора цитаты
@ -27,23 +15,18 @@
- [ ] P1: Материализовать `sender_id` и `mentions` в индексируемый текст - [ ] P1: Материализовать `sender_id` и `mentions` в индексируемый текст
- [ ] P1: Разбирать `file_snippets` и вытаскивать имя файла, mime и url - [ ] P1: Разбирать `file_snippets` и вытаскивать имя файла, mime и url
- [ ] P1: Разбирать `member_event` и превращать его в индексируемый текст - [ ] P1: Разбирать `member_event` и превращать его в индексируемый текст
- [ ] P2: Использовать `entities.people` и `entities.emails` для boost или фильтрации
## `docker-compose.yml` - [ ] 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: Привести локальный `docker-compose.yml` к схеме с `API_KEY` - [ ] P3: Привести локальный `docker-compose.yml` к схеме с `API_KEY`
- [ ] P3: Добавить `--platform linux/amd64` в сборку образов - [ ] P3: Добавить `--platform linux/amd64` в сборку образов
- [ ] P3: Добавить retry и timeout политику для dense/rerank HTTP вызовов
## `doc/` (новый файл с регрессионными вопросами)
- [ ] P3: Зафиксировать набор локальных тестовых вопросов для проверки регрессий - [ ] P3: Зафиксировать набор локальных тестовых вопросов для проверки регрессий
## `search/requirements.txt`
- [ ] P4: Добавить `python-dateutil` или `dateparser`
- [ ] P4: Добавить `tenacity`
## `index/requirements.txt` и/или `search/requirements.txt`
- [ ] P4: Добавить `razdel` - [ ] P4: Добавить `razdel`
- [ ] P4: Добавить `pymorphy3` - [ ] P4: Добавить `pymorphy3`
- [ ] P4: Добавить `rapidfuzz` - [ ] P4: Добавить `rapidfuzz`
- [ ] P4: Добавить `python-dateutil` или `dateparser`
- [ ] P4: Добавить `tenacity`

View file

@ -1,64 +0,0 @@
Для того чтобы раскидать задачи между тремя людьми, я распределю их по сложности и приоритету.
### Человек 1 (Основной фокус на поиске):
#### `search/main.py`
* **P0**: Переключить основной query на `question.search_text` с fallback на `question.text`
* **P0**: Подключить `question.variants` как дополнительные query-варианты
* **P0**: Подключить `question.hyde` как дополнительные dense-запросы
* **P0**: Подключить `question.keywords` как основу для sparse-запросов
* **P0**: Перестать терять retrieval-кандидатов после rerank
* **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 вызовов
---
### Человек 2 (Основной фокус на индексации и разметке):
#### `index/main.py`
* **P1**: Перейти с символьного chunking на chunking по сообщениям
* **P1**: Учитывать time gap при сборке чанков
* **P1**: Маркировать в тексте `quote`, `forward`, автора сообщения и автора цитаты
* **P1**: Развести `page_content`, `dense_content`, `sparse_content`
* **P1**: Материализовать `sender_id` и `mentions` в индексируемый текст
* **P1**: Разбирать `file_snippets` и вытаскивать имя файла, mime и url
* **P1**: Разбирать `member_event` и превращать его в индексируемый текст
#### `index/requirements.txt` и/или `search/requirements.txt`
* **P4**: Добавить `razdel`
* **P4**: Добавить `pymorphy3`
* **P4**: Добавить `rapidfuzz`
---
### Человек 3 (Основной фокус на Docker и зависимостях):
#### `docker-compose.yml`
* **P3**: Привести локальный `docker-compose.yml` к схеме с `API_KEY`
* **P3**: Добавить `--platform linux/amd64` в сборку образов
#### `doc/` (новый файл с регрессионными вопросами)
* **P3**: Зафиксировать набор локальных тестовых вопросов для проверки регрессий
#### `search/requirements.txt`
* **P4**: Добавить `python-dateutil` или `dateparser`
* **P4**: Добавить `tenacity`
---
Таким образом, задачи равномерно распределены по 3 участникам с учётом сложности и области фокуса.

View file

@ -60,4 +60,3 @@ services:
OPEN_API_PASSWORD: ${OPEN_API_PASSWORD:?set OPEN_API_PASSWORD before docker compose up} OPEN_API_PASSWORD: ${OPEN_API_PASSWORD:?set OPEN_API_PASSWORD before docker compose up}
ports: ports:
- "8002:8000" - "8002:8000"

View file

@ -206,40 +206,12 @@ async def embed_sparse(text: str) -> SparseVector:
values=[float(value) for value in item.values.tolist()], values=[float(value) for value in item.values.tolist()],
) )
# ПЕРЕПИСАТЬ
async def qdrant_search( async def qdrant_search(
client: AsyncQdrantClient, client: AsyncQdrantClient,
dense_vector: list[float], dense_vector: list[float],
sparse_vector: SparseVector, sparse_vector: SparseVector,
question_data: Question
) -> Any | None: ) -> 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( response = await client.query_points(
collection_name=QDRANT_COLLECTION_NAME, collection_name=QDRANT_COLLECTION_NAME,
prefetch=[ prefetch=[
@ -247,7 +219,6 @@ async def qdrant_search(
query=dense_vector, query=dense_vector,
using=QDRANT_DENSE_VECTOR_NAME, using=QDRANT_DENSE_VECTOR_NAME,
limit=DENSE_PREFETCH_K, limit=DENSE_PREFETCH_K,
filter=search_filter,
), ),
models.Prefetch( models.Prefetch(
query=models.SparseVector( query=models.SparseVector(
@ -256,7 +227,6 @@ async def qdrant_search(
), ),
using=QDRANT_SPARSE_VECTOR_NAME, using=QDRANT_SPARSE_VECTOR_NAME,
limit=SPRASE_PREFETCH_K, limit=SPRASE_PREFETCH_K,
filter=search_filter,
), ),
], ],
query=models.FusionQuery(fusion=models.Fusion.RRF), query=models.FusionQuery(fusion=models.Fusion.RRF),
@ -343,7 +313,7 @@ async def search(payload: SearchAPIRequest) -> SearchAPIResponse:
dense_vector = await embed_dense(client, query) dense_vector = await embed_dense(client, query)
sparse_vector = await embed_sparse(query) sparse_vector = await embed_sparse(query)
best_points = await qdrant_search(qdrant, dense_vector, sparse_vector, payload.question) best_points = await qdrant_search(qdrant, dense_vector, sparse_vector)
if best_points is None: if best_points is None:
return SearchAPIResponse(results=[]) return SearchAPIResponse(results=[])