Files
challenge-admin-pl/docs/CHALLENGE_API_README.md
2025-12-09 12:25:29 +03:00

545 lines
16 KiB
Markdown
Raw 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.
# Challenge Service API Documentation
Сервис для проверки заданий в реальном времени через LLM (GigaChat).
## Описание
Система позволяет:
- Создавать задания с описанием в Markdown
- Объединять задания в цепочки
- Пользователям проходить задания и отправлять результаты
- Автоматически проверять результаты через GigaChat с ограничением потоков
- Отслеживать статистику по пользователям и системе
## Конфигурация
В `.env` файле добавьте:
```env
CHALLENGE_LLM_THREADS=2 # Количество одновременных проверок через LLM
```
## API Endpoints
Все endpoints начинаются с префикса `/api/challenge`
### Аутентификация
#### POST `/auth`
Регистрация/авторизация пользователя по nickname.
**Request:**
```json
{
"nickname": "user123"
}
```
**Валидация:**
- `nickname`: обязательное поле, от 3 до 50 символов
**Response:**
```json
{
"error": null,
"data": {
"ok": true,
"userId": "507f1f77bcf86cd799439011"
}
}
```
### Управление заданиями
**Важно:** Все операции создания/обновления/удаления заданий требуют авторизации через Keycloak с ролью `teacher` или `challenge-author`.
#### POST `/task`
Создание нового задания (требует роль `teacher` или `challenge-author`).
**Headers:**
```
Authorization: Bearer <keycloak_token>
```
**Request:**
```json
{
"title": "Написать функцию сортировки",
"description": "# Задание\n\nНапишите функцию сортировки массива чисел...",
"hiddenInstructions": "Проверь сложность алгоритма. Должна быть O(n log n), не принимай bubble sort"
}
```
**Валидация:**
- `title`: обязательное поле, максимум 255 символов
- `description`: обязательное поле
- `hiddenInstructions`: опциональное поле
**Response:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"name": "Новое название цепочки",
"taskIds": ["507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012"]
}
```
**Валидация:**
- `name`: опциональное поле, максимум 255 символов
- `taskIds`: опциональное поле, массив строк
#### DELETE `/chain/:chainId`
Удаление цепочки (требует роль `teacher` или `challenge-author`).
**Headers:**
```
Authorization: Bearer <keycloak_token>
```
### Отправка и проверка заданий
#### POST `/submit`
Отправка результата выполнения задания на проверку.
**Request:**
```json
{
"userId": "507f1f77bcf86cd799439011",
"taskId": "507f1f77bcf86cd799439012",
"result": "function sort(arr) { return arr.sort((a, b) => a - b); }"
}
```
**Response:**
```json
{
"error": null,
"data": {
"queueId": "550e8400-e29b-41d4-a716-446655440000",
"submissionId": "507f1f77bcf86cd799439013"
}
}
```
**Валидация:**
- `userId`: обязательное поле
- `taskId`: обязательное поле
- `result`: обязательное поле
#### GET `/check-status/:queueId`
Polling endpoint для проверки статуса проверки.
**Response (в процессе):**
```json
{
"error": null,
"data": {
"status": "waiting",
"position": 3
}
}
```
**Response (завершено):**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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 пользователя
1. Авторизация: `POST /api/challenge/auth`
2. Получение цепочек: `GET /api/challenge/chains`
3. Получение заданий цепочки: `GET /api/challenge/chain/:chainId`
4. Отправка результата: `POST /api/challenge/submit`
5. Polling проверки: `GET /api/challenge/check-status/:queueId` (каждые 2-3 секунды)
6. Просмотр статистики: `GET /api/challenge/user/:userId/stats`
### Типичный flow администратора
1. Создание заданий: `POST /api/challenge/task`
2. Создание цепочки: `POST /api/challenge/chain`
3. Просмотр общей статистики: `GET /api/challenge/stats`
4. Просмотр расширенной статистики: `GET /api/challenge/stats/v2` (опционально с `?chainId=...`)
## Ограничения и особенности
- Очередь работает in-memory, при перезапуске сервера незавершенные проверки вернутся в статус `pending`
- История всех попыток сохраняется полностью
- Markdown описания заданий хранятся как простой текст в БД
- Аутентификация простая, без паролей (только nickname)
- Nickname должен быть уникальным в системе