vk_hackathon/README.md
2026-04-18 10:51:43 +03:00

515 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Пример решения
Этот репозиторий содержит минимальный шаблон решения, совместимый с нашей проверяющей системой.
В шаблоне есть два сервиса:
- `index` — получает сообщения и строит чанки для индексации;
- `search` — получает вопрос и возвращает `message_ids` найденных сообщений.
Это не эталонное решение по качеству поиска. Его задача — показать корректный контракт и базовый pipeline.
## Состав репозитория
- `index/main.py` — сервис индексации;
- `search/main.py` — сервис поиска;
- `data/Go Nova.json` — анонимизированный пример реального чата;
- `index/Makefile`, `search/Makefile` — локальная сборка и публикация образов.
## Что можно менять
Вы можете менять:
- внутреннюю логику chunking;
- формирование текста для dense;
- формирование sparse векторов;
- retrieval и rerank pipeline;
- любые эвристики и фильтры.
Вы не должны менять:
- контракт `POST /index`;
- контракт `POST /sparse_embedding`;
- контракт `POST /search`.
Если вы меняете request/response этих endpoint'ов, решение перестанет проходить проверку.
## Формат данных
Для разбора входных данных используйте `data/Go Nova.json`.
Особенно важно посмотреть:
- `messages`;
- поля `text`, `parts`, `mentions`;
- metadata чанков в payload Qdrant (можете увидеть её формат в коде сервиса `search`).
Во многих сообщениях значимая часть текста находится не в корневом `text`, а в `parts[*].text`.
Поле `parts` может содержать:
- обычный текст;
- цитату;
- пересланное сообщение.
Если вы только начинаете работу с шаблоном, сначала откройте `data/Go Nova.json`, затем сравните его со схемами в `index/main.py` и `search/main.py`.
При индексации в metadata чанка мы также сохраняем полезные поля:
- `participants` — уникальные `sender_id` сообщений, попавших в чанк;
- `mentions` — уникальные упоминания из сообщений чанка;
- `contains_forward` — есть ли в чанке пересланные сообщения;
- `contains_quote` — есть ли в чанке цитаты других сообщений.
Этими полями можно пользоваться в запросе к `Qdrant`.
## Сервис `index`
`index` должен реализовать:
- `GET /health`
- `POST /index`
- `POST /sparse_embedding`
>ВАЖНО! У `index`-контейнера нет доступа в интернет. Поэтому sparse-модель должна быть доступна локально внутри образа.
### `GET /health`
Endpoint для health check контейнера.
Мы используем его, чтобы проверить, что контейнер успешно запустился и сервис готов принимать запросы.
На выходе endpoint должен вернуть `200 OK`.
### `POST /index`
На вход приходят:
- `chat`;
- `overlap_messages`;
- `new_messages`.
Endpoint должен:
- принять новую порцию сообщений для индексации;
- построить чанки;
- вернуть текст чанков и связанные `message_ids`.
На выходе сервис должен вернуть `results[]`, где каждый элемент содержит:
- `page_content` — текст чанка, который сохраняется в payload;
- `dense_content` — текст для dense embedding;
- `sparse_content` — текст для sparse embedding;
- `message_ids` — id сообщений, связанных с чанком.
Пример запроса:
```bash
curl -X POST "http://localhost:8001/index" \
-H "Content-Type: application/json" \
-d '{
"data": {
"chat": {
"id": "chat-1",
"name": "Go Nova",
"sn": "chat-1@chat.agent",
"type": "channel",
"is_public": true
},
"overlap_messages": [
{
"id": "1",
"time": 1710000000,
"text": "Обсуждаем релиз Go",
"sender_id": "u1",
"file_snippets": "",
"parts": [],
"mentions": [],
"is_system": false,
"is_hidden": false,
"is_forward": false,
"is_quote": false
}
],
"new_messages": [
{
"id": "2",
"time": 1710000060,
"text": "Релиз Go перенесли на следующую неделю",
"sender_id": "u2",
"file_snippets": "",
"parts": [],
"mentions": [],
"is_system": false,
"is_hidden": false,
"is_forward": false,
"is_quote": false
}
]
}
}'
```
Пример ответа:
```json
{
"results": [
{
"page_content": "Обсуждаем релиз Go\nРелиз Go перенесли на следующую неделю",
"dense_content": "Обсуждаем релиз Go\nРелиз Go перенесли на следующую неделю",
"sparse_content": "Обсуждаем релиз Go\nРелиз Go перенесли на следующую неделю",
"message_ids": ["1", "2"]
}
]
}
```
### `POST /sparse_embedding`
На вход endpoint получает:
- `texts: string[]`
Endpoint должен:
- принять batch текстов;
- посчитать sparse-вектора;
- вернуть их в формате, совместимом с Qdrant.
На выходе должен вернуть:
- `vectors[].indices`
- `vectors[].values`
Этот endpoint мы используем для вычисления sparse векторов.
Пример запроса:
```bash
curl -X POST "http://localhost:8001/sparse_embedding" \
-H "Content-Type: application/json" \
-d '{
"texts": [
"Релиз Go перенесли на следующую неделю",
"VK GPT обсуждали в отдельном чате"
]
}'
```
Пример ответа:
```json
{
"vectors": [
{
"indices": [1452, 9081, 12044],
"values": [0.6931, 1.0986, 0.6931]
},
{
"indices": [317, 2801, 9102],
"values": [0.6931, 0.6931, 1.3863]
}
]
}
```
## Сервис `search`
`search` должен реализовать:
- `GET /health`
- `POST /search`
>ВАЖНО! У `search`-контейнера тоже нет доступа в интернет. Поэтому sparse-модель должна быть доступна локально внутри образа.
### `GET /health`
Endpoint для health check контейнера.
Мы используем его, чтобы проверить, что контейнер успешно запустился и сервис готов принимать запросы.
На выходе endpoint должен вернуть `200 OK`.
### `POST /search`
На вход `POST /search` приходит вопрос пользователя с некоторой метадатой (можете изучить её в коде сервиса `search`).
Endpoint должен:
1. получить вектора для поискового запроса;
2. выполнить retrieval по коллекции в Qdrant;
3. при необходимости выполнить rerank;
4. вернуть `message_ids` найденных сообщений.
На выходе `POST /search` должен вернуть `results[]`, где каждый элемент содержит:
- `message_ids`
Пример запроса:
```bash
curl -X POST "http://localhost:8002/search" \
-H "Content-Type: application/json" \
-d '{
"question": {
"text": "Что писали про релиз Go?"
}
}'
```
Пример ответа:
```json
{
"results": [
{
"message_ids": ["2", "1"]
}
]
}
```
## Доступ к нашим сервисам из ваших контейнеров
Во время проверки мы передаем адреса наших сервисов через env. Внутри ваших контейнеров нужно использовать именно эти переменные, а не хардкодить адреса.
### `index`
`index` должен использовать:
- `HOST`
- `PORT`
### `search`
`search` должен использовать:
- `HOST`
- `PORT`
- `API_KEY`
- `EMBEDDINGS_DENSE_URL`
- `RERANKER_URL`
- `QDRANT_URL`
- `QDRANT_COLLECTION_NAME`
- `QDRANT_DENSE_VECTOR_NAME`
- `QDRANT_SPARSE_VECTOR_NAME`
## Открытый API dense и rerank
Для dense embeddings и rerank мы предоставляем внешний HTTP API с Basic Auth.
Будьте осторожны при использовании этих URL'ов, для каждой команды есть ограничение для использования (rate limit) в символах в минуту.
Базовый URL:
- `http://83.166.249.64:18001`
Доступные endpoint'ы:
- `POST /embeddings` — dense embeddings;
- `GET /embeddings/models` — список dense embedding моделей;
- `POST /score` — rerank;
- `GET /score/models` — список rerank моделей.
Примеры запросов:
Dense embeddings:
```bash
curl -u "$LOGIN:$PASSWORD" \
-X POST "http://83.166.249.64:18001/embeddings" \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-Embedding-0.6B",
"input": ["Пример поискового запроса"],
"encoding_format": "base64"
}'
```
Список dense embedding моделей:
```bash
curl -u "$LOGIN:$PASSWORD" \
"http://83.166.249.64:18001/embeddings/models"
```
Rerank:
```bash
curl -u "$LOGIN:$PASSWORD" \
-X POST "http://83.166.249.64:18001/score" \
-H "Content-Type: application/json" \
-d '{
"model": "nvidia/llama-nemotron-rerank-1b-v2",
"text_1": "Что обсуждали про релиз Go?",
"text_2": [
"Первый кандидат для реранка",
"Второй кандидат для реранка"
]
}'
```
Список rerank моделей:
```bash
curl -u "$LOGIN:$PASSWORD" \
"http://83.166.249.64:18001/score/models"
```
## Примеры вопросов
Примеры вопросов из test датасета, на котором оцениваются ваши решения.
Примеры:
- `В какой чат можно написать вопрос про VK GPT?`
- `Кто сейчас является руководителем команды коробочных продуктов Tarantool?`
- `Какие проблемы были с сервисом oauth-antibot в августе 2020?`
## Оценивание
При оценивании мы считаем две метрики:
- `Recall@K`
- `nDCG@K`
Где `K` = 50. Если ваше решение возвращает больше 50-и `message_id`, то все после первых 50-и отбрасываются и не учитываются.
Для каждой посылки мы усредняем значения этих метрик по всем вопросам датасета и получаем:
- `recall_avg`
- `ndcg_avg`
Итоговый `score` рассчитывается по формуле:
```text
score = recall_avg * 0.8 + ndcg_avg * 0.2
```
## Время выполнения
Время выполнения почти полностью состоит из вычисления векторов и индексации и занимает приблизительно `15 минут`
Вы должны уложиться в `20 минут`
## Локальный запуск
Для локального запуска используйте `docker compose`.
Перед запуском укажите учетные данные для внешнего dense/rerank API:
```bash
export OPEN_API_LOGIN=...
export OPEN_API_PASSWORD=...
```
Если эти переменные не заданы, `docker compose up` завершится с ошибкой.
Запуск:
```bash
docker compose up --build
```
Будут подняты:
- `qdrant` на `localhost:6333`
- `index` на `localhost:8001`
- `search` на `localhost:8002`
`docker compose` также создаст локальную коллекцию `evaluation` в Qdrant.
>ВАЖНО! В текущем примере INDEX не вставляет чанки в Qdrant, поэтому `search` не вернет чанков
Сборка и запуск `index`:
```bash
cd index
make build
make run
```
Сборка и запуск `search`:
```bash
cd search
make build
make run
```
## Сдача решения
### Шаг 1. Получение логина и пароля
В личном кабинете получите логин и пароль для входа в Docker registry
### Шаг 2. Настройка Docker
Registry для хранения образов будет доступен по адресу `83.166.249.64:5000`.
Поскольку он работает без TLS, необходимо добавить его в список insecure registries в настройках Docker.
#### Docker Desktop (macOS / Windows)
1. Откройте Docker Desktop -> **Settings** -> **Docker Engine**.
2. Добавьте в JSON-конфиг поле `insecure-registries`:
```json
{
"insecure-registries": ["83.166.249.64:5000"]
}
```
3. Нажмите **Apply & Restart**.
#### CLI — Linux
1. Откройте файл с конфигурацией докер демона в режиме редактирования
```bash
sudo nano /etc/docker/daemon.json
```
2. Добавьте в JSON-конфиг поле `insecure-registries`:
```json
{
"insecure-registries": ["83.166.249.64:5000"]
}
```
3. Перезапустите docker
```bash
sudo systemctl restart docker
```
### Шаг 3. Логин в registry
Необходимо пройти аутентификацию в docker registry, используя логин и пароль, полученные на шаге 1.
```bash
docker login 83.166.249.64:5000 -u <login> -p <password>
```
Ожидаемый вывод: `Login Succeeded`.
### Шаг 4. Сборка образов
Соберите образы своих Index Service и Search Service.
Образы должны иметь тег, который имеет строгий формат:
- Для Index Service - 83.166.249.64:5000/{team_id}/index-service:latest
- Для Search Service - 83.166.249.64:5000/{team_id}/search-service:latest
Образы должны быть собраны под платформу linux/amd64
```bash
docker build --platform linux/amd64 -t 83.166.249.64:5000/{team_id}/index-service:latest {path_to_index_service_dir}
docker build --platform linux/amd64 -t 83.166.249.64:5000/{team_id}/search-service:latest {path_to_search_service_dir}
```
### Шаг 5. Публикация образов в registry
```bash
docker push 83.166.249.64:5000/{team_id}/index-service:latest
docker push 83.166.249.64:5000/{team_id}/search-service:latest
```
### Шаг 6. Запуск процесса оценивания
На страничке оценивания нажать кнопку "Запустить/Оценить"
### Примечание
Для удобства сборки и публикации образов в шаблоне решения уже есть `Makefile`с нужными командами
Примеры:
```bash
cd index
export LOGIN=... PASSWORD=... TEAM_ID=...
make push
```
```bash
cd search
export LOGIN=... PASSWORD=... TEAM_ID=...
make push
```