Skip to content

vktechdev/it-academy-hackathon-solution-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Пример решения

Этот репозиторий содержит минимальный шаблон решения, совместимый с нашей проверяющей системой.

В шаблоне есть два сервиса:

  • 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 сообщений, связанных с чанком.

Пример запроса:

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
        }
      ]
    }
  }'

Пример ответа:

{
  "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 векторов.

Пример запроса:

curl -X POST "http://localhost:8001/sparse_embedding" \
  -H "Content-Type: application/json" \
  -d '{
    "texts": [
      "Релиз Go перенесли на следующую неделю",
      "VK GPT обсуждали в отдельном чате"
    ]
  }'

Пример ответа:

{
  "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

Пример запроса:

curl -X POST "http://localhost:8002/search" \
  -H "Content-Type: application/json" \
  -d '{
    "question": {
      "text": "Что писали про релиз Go?"
    }
  }'

Пример ответа:

{
  "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:

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 моделей:

curl -u "$LOGIN:$PASSWORD" \
  "http://83.166.249.64:18001/embeddings/models"

Rerank:

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 моделей:

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 рассчитывается по формуле:

score = recall_avg * 0.8 + ndcg_avg * 0.2

Время выполнения

Время выполнения почти полностью состоит из вычисления векторов и индексации и занимает приблизительно 15 минут Вы должны уложиться в 20 минут

Локальный запуск

Для локального запуска используйте docker compose.

Перед запуском укажите учетные данные для внешнего dense/rerank API:

export OPEN_API_LOGIN=...
export OPEN_API_PASSWORD=...

Если эти переменные не заданы, docker compose up завершится с ошибкой.

Запуск:

docker compose up --build

Будут подняты:

  • qdrant на localhost:6333
  • index на localhost:8001
  • search на localhost:8002

docker compose также создаст локальную коллекцию evaluation в Qdrant.

ВАЖНО! В текущем примере INDEX не вставляет чанки в Qdrant, поэтому search не вернет чанков

Сборка и запуск index:

cd index
make build
make run

Сборка и запуск search:

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:
{
  "insecure-registries": ["83.166.249.64:5000"]
}
  1. Нажмите Apply & Restart.

CLI — Linux

  1. Откройте файл с конфигурацией докер демона в режиме редактирования
sudo nano /etc/docker/daemon.json
  1. Добавьте в JSON-конфиг поле insecure-registries:
{
  "insecure-registries": ["83.166.249.64:5000"]
}
  1. Перезапустите docker
sudo systemctl restart docker

Шаг 3. Логин в registry

Необходимо пройти аутентификацию в docker registry, используя логин и пароль, полученные на шаге 1.

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

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

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'ы с нужными командами

Примеры:

cd index
export LOGIN=... PASSWORD=... TEAM_ID=...
make push
cd search
export LOGIN=... PASSWORD=... TEAM_ID=...
make push

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors