16 KiB
Challenge Service API Documentation
Сервис для проверки заданий в реальном времени через LLM (GigaChat).
Описание
Система позволяет:
- Создавать задания с описанием в Markdown
- Объединять задания в цепочки
- Пользователям проходить задания и отправлять результаты
- Автоматически проверять результаты через GigaChat с ограничением потоков
- Отслеживать статистику по пользователям и системе
Конфигурация
В .env файле добавьте:
CHALLENGE_LLM_THREADS=2 # Количество одновременных проверок через LLM
API Endpoints
Все endpoints начинаются с префикса /api/challenge
Аутентификация
POST /auth
Регистрация/авторизация пользователя по nickname.
Request:
{
"nickname": "user123"
}
Валидация:
nickname: обязательное поле, от 3 до 50 символов
Response:
{
"error": null,
"data": {
"ok": true,
"userId": "507f1f77bcf86cd799439011"
}
}
Управление заданиями
Важно: Все операции создания/обновления/удаления заданий требуют авторизации через Keycloak с ролью teacher или challenge-author.
POST /task
Создание нового задания (требует роль teacher или challenge-author).
Headers:
Authorization: Bearer <keycloak_token>
Request:
{
"title": "Написать функцию сортировки",
"description": "# Задание\n\nНапишите функцию сортировки массива чисел...",
"hiddenInstructions": "Проверь сложность алгоритма. Должна быть O(n log n), не принимай bubble sort"
}
Валидация:
title: обязательное поле, максимум 255 символовdescription: обязательное полеhiddenInstructions: опциональное поле
Response:
{
"error": null,
"data": {
"_id": "507f1f77bcf86cd799439011",
"title": "Написать функцию сортировки",
"description": "# Задание\n\n...",
"hiddenInstructions": "Проверь сложность алгоритма...",
"creator": { "sub": "...", "preferred_username": "teacher1" },
"createdAt": "2023-10-29T12:00:00.000Z",
"updatedAt": "2023-10-29T12:00:00.000Z"
}
}
Примечание: Поле hiddenInstructions опционально. Эти инструкции будут переданы в LLM при проверке, но скрыты от студентов.
GET /task/:taskId
Получение задания по ID.
Важно: Поля hiddenInstructions и creator возвращаются только для пользователей с ролью teacher или challenge-author. Обычные студенты их не увидят.
GET /tasks
Получение всех заданий.
Примечание: Для не-преподавателей поля hiddenInstructions и creator скрыты.
PUT /task/:taskId
Обновление задания (требует роль teacher или challenge-author).
Headers:
Authorization: Bearer <keycloak_token>
Request:
{
"title": "Новый заголовок",
"description": "Новое описание",
"hiddenInstructions": "Обновленные инструкции для LLM"
}
Валидация:
title: опциональное поле, максимум 255 символовdescription: опциональное полеhiddenInstructions: опциональное поле
DELETE /task/:taskId
Удаление задания (требует роль teacher или challenge-author). Также удаляется из всех цепочек.
Headers:
Authorization: Bearer <keycloak_token>
Управление цепочками заданий
Важно: Все операции создания/обновления/удаления цепочек требуют авторизации через Keycloak с ролью teacher или challenge-author.
POST /chain
Создание цепочки заданий (требует роль teacher или challenge-author).
Request:
{
"name": "Основы программирования",
"taskIds": ["507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012"]
}
Валидация:
name: обязательное поле, максимум 255 символовtaskIds: обязательное поле, массив строк
GET /chains
Получение всех цепочек с заданиями.
Примечание: Для не-преподавателей поля hiddenInstructions и creator скрыты в заданиях внутри цепочек.
GET /chain/:chainId
Получение цепочки по ID.
Примечание: Для не-преподавателей поля hiddenInstructions и creator скрыты в заданиях внутри цепочки.
PUT /chain/:chainId
Обновление цепочки (требует роль teacher или challenge-author).
Headers:
Authorization: Bearer <keycloak_token>
Request:
{
"name": "Новое название цепочки",
"taskIds": ["507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012"]
}
Валидация:
name: опциональное поле, максимум 255 символовtaskIds: опциональное поле, массив строк
DELETE /chain/:chainId
Удаление цепочки (требует роль teacher или challenge-author).
Headers:
Authorization: Bearer <keycloak_token>
Отправка и проверка заданий
POST /submit
Отправка результата выполнения задания на проверку.
Request:
{
"userId": "507f1f77bcf86cd799439011",
"taskId": "507f1f77bcf86cd799439012",
"result": "function sort(arr) { return arr.sort((a, b) => a - b); }"
}
Response:
{
"error": null,
"data": {
"queueId": "550e8400-e29b-41d4-a716-446655440000",
"submissionId": "507f1f77bcf86cd799439013"
}
}
Валидация:
userId: обязательное полеtaskId: обязательное полеresult: обязательное поле
GET /check-status/:queueId
Polling endpoint для проверки статуса проверки.
Response (в процессе):
{
"error": null,
"data": {
"status": "waiting",
"position": 3
}
}
Response (завершено):
{
"error": null,
"data": {
"status": "completed",
"submission": {
"_id": "507f1f77bcf86cd799439013",
"user": {...},
"task": {...},
"result": "...",
"status": "accepted",
"feedback": "Отлично! Задание выполнено правильно.",
"attemptNumber": 1,
"submittedAt": "2023-10-29T12:00:00.000Z",
"checkedAt": "2023-10-29T12:00:05.000Z"
}
}
}
Статусы проверки:
waiting- ожидает в очередиin_progress- проверяетсяcompleted- проверка завершенаerror- ошибка при проверкеnot_found- не найдено
Статусы submission:
pending- ожидает проверкиin_progress- проверяетсяaccepted- принятоneeds_revision- требует доработки
Просмотр попыток
GET /user/:userId/submissions?taskId=...
Получение всех попыток пользователя (опционально для конкретного задания).
Response:
{
"error": null,
"data": [
{
"_id": "507f1f77bcf86cd799439013",
"task": {...},
"result": "...",
"status": "accepted",
"feedback": "...",
"attemptNumber": 2,
"submittedAt": "2023-10-29T12:00:00.000Z",
"checkedAt": "2023-10-29T12:00:05.000Z"
}
]
}
Статистика
GET /user/:userId/stats
Детальная статистика пользователя.
Response:
{
"error": null,
"data": {
"totalTasksAttempted": 5,
"completedTasks": 3,
"inProgressTasks": 1,
"needsRevisionTasks": 1,
"totalSubmissions": 8,
"averageCheckTimeMs": 5234,
"taskStats": [
{
"taskId": "507f1f77bcf86cd799439012",
"taskTitle": "Написать функцию сортировки",
"attempts": [...],
"totalAttempts": 2,
"status": "completed",
"lastAttemptAt": "2023-10-29T12:00:00.000Z"
}
],
"chainStats": [
{
"chainId": "507f1f77bcf86cd799439014",
"chainName": "Основы программирования",
"totalTasks": 5,
"completedTasks": 3,
"progress": 60
}
]
}
}
Примечание: Статусы заданий в taskStats.status:
not_attempted- задание не начатоpending- ожидает проверкиin_progress- проверяетсяneeds_revision- требует доработкиcompleted- выполнено
GET /stats
Общая статистика системы.
Response:
{
"error": null,
"data": {
"users": 150,
"tasks": 25,
"chains": 5,
"submissions": {
"total": 850,
"accepted": 420,
"rejected": 380,
"pending": 30,
"inProgress": 20
},
"averageCheckTimeMs": 5500,
"queue": {
"queueLength": 12,
"waiting": 10,
"inProgress": 2,
"maxConcurrency": 2,
"currentlyProcessing": 2
}
}
}
GET /stats/v2
Расширенная статистика системы с детальными данными для таблиц и прогресс-баров.
Query параметры:
chainId(опционально): фильтрация статистики по конкретной цепочке
Response:
{
"error": null,
"data": {
"users": 150,
"tasks": 25,
"chains": 5,
"submissions": {
"total": 850,
"accepted": 420,
"rejected": 380,
"pending": 30,
"inProgress": 20
},
"averageCheckTimeMs": 5500,
"queue": {
"queueLength": 12,
"waiting": 10,
"inProgress": 2,
"maxConcurrency": 2,
"currentlyProcessing": 2
},
"tasksTable": [
{
"taskId": "507f1f77bcf86cd799439012",
"title": "Написать функцию сортировки",
"totalAttempts": 45,
"uniqueUsers": 20,
"acceptedCount": 18,
"successRate": 90,
"averageAttemptsToSuccess": 1.5
}
],
"activeParticipants": [
{
"userId": "507f1f77bcf86cd799439011",
"nickname": "user123",
"totalSubmissions": 10,
"completedTasks": 5,
"chainProgress": [
{
"chainId": "507f1f77bcf86cd799439014",
"chainName": "Основы программирования",
"totalTasks": 10,
"completedTasks": 5,
"progressPercent": 50
}
]
}
],
"chainsDetailed": [
{
"chainId": "507f1f77bcf86cd799439014",
"name": "Основы программирования",
"totalTasks": 10,
"tasks": [
{
"taskId": "507f1f77bcf86cd799439012",
"title": "Написать функцию сортировки",
"description": "# Задание\n\n..."
}
],
"participantProgress": [
{
"userId": "507f1f77bcf86cd799439011",
"nickname": "user123",
"taskProgress": [
{
"taskId": "507f1f77bcf86cd799439012",
"taskTitle": "Написать функцию сортировки",
"status": "completed"
}
],
"completedCount": 5,
"progressPercent": 50
}
]
}
]
}
}
Примечание: Статусы задач в taskProgress:
not_started- задание не начатоpending- ожидает проверкиin_progress- проверяетсяneeds_revision- требует доработкиcompleted- выполнено
Архитектура
Модели данных
- ChallengeUser - пользователи сервиса
- ChallengeTask - отдельные задания
- ChallengeChain - цепочки заданий
- ChallengeSubmission - результаты выполнения заданий
Сервисы
- challenge-checker.ts - проверка заданий через GigaChat
- ChallengeCheckQueue.ts - управление очередью проверок
- challengeQueueInstance.ts - singleton экземпляр очереди
Очередь проверки
Очередь работает in-memory с ограничением на количество одновременных проверок.
- Элементы добавляются в очередь при отправке задания
- Обработка происходит автоматически каждую секунду
- Количество параллельных проверок ограничено
CHALLENGE_LLM_THREADS - После завершения проверки результаты сохраняются в БД
- Записи в очереди удаляются через 5 минут после завершения
Проверка через LLM
Для проверки используется GigaChat с промптом, который:
- Получает описание задания
- Получает результат пользователя
- Возвращает статус (ПРИНЯТО/ДОРАБОТКА) и feedback
Ответ парсится и сохраняется в submission.
Примеры использования
Типичный flow пользователя
- Авторизация:
POST /api/challenge/auth - Получение цепочек:
GET /api/challenge/chains - Получение заданий цепочки:
GET /api/challenge/chain/:chainId - Отправка результата:
POST /api/challenge/submit - Polling проверки:
GET /api/challenge/check-status/:queueId(каждые 2-3 секунды) - Просмотр статистики:
GET /api/challenge/user/:userId/stats
Типичный flow администратора
- Создание заданий:
POST /api/challenge/task - Создание цепочки:
POST /api/challenge/chain - Просмотр общей статистики:
GET /api/challenge/stats - Просмотр расширенной статистики:
GET /api/challenge/stats/v2(опционально с?chainId=...)
Ограничения и особенности
- Очередь работает in-memory, при перезапуске сервера незавершенные проверки вернутся в статус
pending - История всех попыток сохраняется полностью
- Markdown описания заданий хранятся как простой текст в БД
- Аутентификация простая, без паролей (только nickname)
- Nickname должен быть уникальным в системе