brief-rags-bench/DEVELOPMENT_PLAN.md

22 KiB
Raw Permalink Blame History

План развития Brief Bench FastAPI

Дата создания: 17 декабря 2025 Статус: Backend готов, требуется frontend интеграция и тестирование


Текущее состояние

Готово (Backend)

  • Структура FastAPI приложения
  • JWT авторизация (8-значный логин)
  • TgBackendInterface (полная реализация с httpx)
  • DBApiClient (интеграция с DB API)
  • RagService (интеграция с RAG backends, mTLS)
  • API endpoints:
    • POST /api/v1/auth/login - авторизация
    • GET/PUT /api/v1/settings - настройки пользователя
    • POST /api/v1/query/bench - batch запросы
    • POST /api/v1/query/backend - последовательные запросы
    • POST/GET/DELETE /api/v1/analysis/sessions - сессии анализа
  • Docker setup (Dockerfile, docker-compose.yml)
  • Документация (README.md, DB_API_CONTRACT.md)

Требуется доделать

  • Frontend файлы (перенос из rag-bench-old-version)
  • API client для frontend
  • Интеграция frontend с новым API
  • Тестирование
  • Production deployment конфигурация

Этап 1: Подготовка Frontend

1.1 Перенос статических файлов из rag-bench-old-version

Старая архитектура:

  • Статический SPA напрямую делал fetch запросы к RAG backends
  • Настройки хранились в localStorage (отдельно для каждого окружения)
  • Встроенная поддержка 3 окружений (IFT, PSI, PROD)
  • Два режима: Bench (batch) и Backend (sequential)
  • Аннотации, экспорт/импорт, детальный UI для анализа ответов

Файлы для переноса:

# Скопировать из rag-bench-old-version/ в static/
static/
├── index.html          # Основная страница (24KB, 495 строк)
├── styles.css          # Material Design стили (23KB)
├── app.js              # Основная логика (61KB, ~1500+ строк)
├── settings.js         # Дефолтные настройки (3KB)
└── api-client.js       # НОВЫЙ ФАЙЛ - будет создан

Ключевые изменения в архитектуре:

  1. Было: Browser → RAG Backend (прямой fetch)
  2. Стало: Browser → FastAPI → RAG Backend
  3. Было: Настройки в localStorage
  4. Стало: Настройки в DB API (персональные для каждого пользователя)
  5. Добавлено: JWT авторизация с 8-значным логином

1.2 Создать API client для frontend

Файл: static/api-client.js

Полная реализация:

/**
 * Brief Bench API Client
 * Взаимодействие с FastAPI backend
 */
class BriefBenchAPI {
  constructor() {
    this.baseURL = '/api/v1'
  }

  // ============================================
  // Internal Helpers
  // ============================================

  _getToken() {
    return localStorage.getItem('access_token')
  }

  _setToken(token) {
    localStorage.setItem('access_token', token)
  }

  _clearToken() {
    localStorage.removeItem('access_token')
  }

  _getHeaders(includeAuth = true) {
    const headers = {
      'Content-Type': 'application/json'
    }

    if (includeAuth) {
      const token = this._getToken()
      if (token) {
        headers['Authorization'] = `Bearer ${token}`
      }
    }

    return headers
  }

  async _handleResponse(response) {
    // Handle 401 Unauthorized
    if (response.status === 401) {
      this._clearToken()
      throw new Error('Сессия истекла. Пожалуйста, войдите снова.')
    }

    // Handle 502 Bad Gateway (RAG backend error)
    if (response.status === 502) {
      throw new Error('RAG backend недоступен или вернул ошибку')
    }

    // Handle other errors
    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}))
      throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`)
    }

    // Handle 204 No Content
    if (response.status === 204) {
      return null
    }

    return await response.json()
  }

  async _request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`

    try {
      const response = await fetch(url, options)
      return await this._handleResponse(response)
    } catch (error) {
      console.error(`API request failed: ${endpoint}`, error)
      throw error
    }
  }

  // ============================================
  // Auth API
  // ============================================

  /**
   * Авторизация с 8-значным логином
   * @param {string} login - 8-значный логин
   * @returns {Promise<{access_token: string, user: object}>}
   */
  async login(login) {
    const response = await this._request(`/auth/login?login=${login}`, {
      method: 'POST',
      headers: this._getHeaders(false)
    })

    // Сохранить токен
    this._setToken(response.access_token)

    return response
  }

  /**
   * Выход (очистка токена)
   */
  logout() {
    this._clearToken()
    window.location.reload()
  }

  /**
   * Проверка авторизации
   * @returns {boolean}
   */
  isAuthenticated() {
    return !!this._getToken()
  }

  // ============================================
  // Settings API
  // ============================================

  /**
   * Получить настройки пользователя для всех окружений
   * @returns {Promise<{user_id: string, settings: object, updated_at: string}>}
   */
  async getSettings() {
    return await this._request('/settings', {
      method: 'GET',
      headers: this._getHeaders()
    })
  }

  /**
   * Обновить настройки пользователя
   * @param {object} settings - Объект с настройками для окружений
   * @returns {Promise<{user_id: string, settings: object, updated_at: string}>}
   */
  async updateSettings(settings) {
    return await this._request('/settings', {
      method: 'PUT',
      headers: this._getHeaders(),
      body: JSON.stringify({ settings })
    })
  }

  // ============================================
  // Query API
  // ============================================

  /**
   * Отправить batch запрос (Bench mode)
   * @param {string} environment - Окружение (ift/psi/prod)
   * @param {Array<{body: string, with_docs: boolean}>} questions - Массив вопросов
   * @returns {Promise<{request_id: string, timestamp: string, environment: string, response: object}>}
   */
  async benchQuery(environment, questions) {
    return await this._request('/query/bench', {
      method: 'POST',
      headers: this._getHeaders(),
      body: JSON.stringify({
        environment,
        questions
      })
    })
  }

  /**
   * Отправить последовательные запросы (Backend mode)
   * @param {string} environment - Окружение (ift/psi/prod)
   * @param {Array<{body: string, with_docs: boolean}>} questions - Массив вопросов
   * @param {boolean} resetSession - Сбрасывать ли сессию после каждого вопроса
   * @returns {Promise<{request_id: string, timestamp: string, environment: string, response: object}>}
   */
  async backendQuery(environment, questions, resetSession = true) {
    return await this._request('/query/backend', {
      method: 'POST',
      headers: this._getHeaders(),
      body: JSON.stringify({
        environment,
        questions,
        reset_session: resetSession
      })
    })
  }

  // ============================================
  // Analysis Sessions API
  // ============================================

  /**
   * Сохранить сессию анализа
   * @param {object} sessionData - Данные сессии
   * @returns {Promise<object>}
   */
  async saveSession(sessionData) {
    return await this._request('/analysis/sessions', {
      method: 'POST',
      headers: this._getHeaders(),
      body: JSON.stringify(sessionData)
    })
  }

  /**
   * Получить список сессий
   * @param {string|null} environment - Фильтр по окружению (опционально)
   * @param {number} limit - Лимит результатов
   * @param {number} offset - Смещение для пагинации
   * @returns {Promise<{sessions: Array, total: number}>}
   */
  async getSessions(environment = null, limit = 50, offset = 0) {
    const params = new URLSearchParams({ limit, offset })
    if (environment) {
      params.append('environment', environment)
    }

    return await this._request(`/analysis/sessions?${params}`, {
      method: 'GET',
      headers: this._getHeaders()
    })
  }

  /**
   * Получить конкретную сессию
   * @param {string} sessionId - ID сессии
   * @returns {Promise<object>}
   */
  async getSession(sessionId) {
    return await this._request(`/analysis/sessions/${sessionId}`, {
      method: 'GET',
      headers: this._getHeaders()
    })
  }

  /**
   * Удалить сессию
   * @param {string} sessionId - ID сессии
   * @returns {Promise<null>}
   */
  async deleteSession(sessionId) {
    return await this._request(`/analysis/sessions/${sessionId}`, {
      method: 'DELETE',
      headers: this._getHeaders()
    })
  }
}

// Export API client instance
const api = new BriefBenchAPI()

Особенности реализации:

  • Автоматическое добавление Authorization: Bearer {token} из localStorage
  • Обработка всех HTTP ошибок (401 → logout + reload, 502 → RAG error)
  • Singleton instance (const api = new BriefBenchAPI())
  • Методы соответствуют FastAPI endpoints один-к-одному

Этап 2: Адаптация Frontend под новую архитектуру

2.1 Добавить Login Screen

Изменения в index.html:

<!-- Login screen (показывается если нет токена) -->
<div id="login-screen">
  <h1>Brief Bench</h1>
  <input type="text" id="login-input" placeholder="8-значный логин" maxlength="8">
  <button id="login-btn">Войти</button>
  <div id="login-error"></div>
</div>

<!-- Main app (показывается после авторизации) -->
<div id="app" style="display: none;">
  <!-- Существующий интерфейс -->
</div>

Логика в app.js:

  • При загрузке проверить наличие токена в localStorage
  • Если нет → показать login screen
  • Если есть → валидировать токен (попробовать загрузить настройки)
  • Если токен невалидный → показать login screen

2.2 Переписать вызовы API

Старый код (прямые fetch):

// Было
const response = await fetch('https://rag-backend/api/bench', {
  method: 'POST',
  headers: { ... },
  body: JSON.stringify(questions)
})

Новый код (через API client):

// Стало
const api = new BriefBenchAPI()
const response = await api.benchQuery('ift', questions)

Что нужно переписать:

  • Загрузка настроек пользователя (при старте приложения)
  • Отправка bench/backend запросов
  • Сохранение сессий анализа
  • Загрузка истории сессий
  • Экспорт данных

2.3 Управление Settings через UI

Новая логика:

  1. При загрузке приложения: GET /api/v1/settings → загрузить настройки для всех 3 окружений
  2. Отобразить в UI (вкладки для IFT/PSI/PROD)
  3. При изменении: PUT /api/v1/settings → сохранить в DB API

Поля настроек (для каждого окружения):

  • API Mode: bench / backend (radio buttons)
  • Bearer Token (input, password type)
  • System Platform (input)
  • System Platform User (input)
  • Platform User ID (input)
  • Platform ID (input)
  • With Classify (checkbox, только для backend mode)
  • Reset Session Mode (checkbox, только для backend mode)

2.4 Интеграция History (сессии анализа)

Функционал:

  1. После выполнения анализа → кнопка "Сохранить сессию"
  2. При сохранении: POST /api/v1/analysis/sessions
    • Отправить environment, api_mode, request, response, annotations
  3. Вкладка "История":
    • Загрузить список: GET /api/v1/analysis/sessions?environment=ift
    • Отобразить в виде таблицы (дата, окружение, режим, кол-во вопросов)
    • При клике → загрузить полную сессию: GET /api/v1/analysis/sessions/{id}
    • Кнопка удаления: DELETE /api/v1/analysis/sessions/{id}

Этап 3: Environment Selector

3.1 UI для выбора окружения

Добавить в index.html:

<div id="environment-selector">
  <label>Окружение:</label>
  <select id="env-select">
    <option value="ift">ИФТ</option>
    <option value="psi">ПСИ</option>
    <option value="prod">ПРОД</option>
  </select>
</div>

Логика:

  • При выборе окружения → загрузить настройки для этого окружения
  • Отобразить текущий apiMode (bench/backend)
  • При отправке запроса → использовать выбранное окружение

3.2 Валидация перед отправкой

Проверки:

  1. Выбрано окружение
  2. Настройки для окружения существуют
  3. apiMode соответствует выбранному режиму (bench/backend)
  4. Если требуется bearerToken → он заполнен

Показывать ошибки:

  • "Настройки для окружения ИФТ не найдены. Перейдите в Settings."
  • "Окружение ПСИ настроено на Backend mode, но вы выбрали Bench режим."

Этап 4: Тестирование

4.1 Ручное тестирование

Сценарии:

  1. Авторизация:

    • Успешный вход (8 цифр)
    • Ошибка (невалидный логин)
    • Истечение токена (через 30 дней или вручную испортить токен)
  2. Настройки:

    • Загрузка настроек для всех окружений
    • Изменение настроек для одного окружения
    • Сохранение → перезагрузка страницы → настройки сохранились
  3. Bench Query:

    • Выбрать IFT, bench mode
    • Отправить несколько вопросов
    • Проверить что ответ отображается корректно
    • Проверить headers в Network tab (Request-Id, System-Id, Bearer token)
  4. Backend Query:

    • Выбрать PSI, backend mode
    • Отправить вопросы последовательно
    • Проверить что между вопросами происходит reset session
  5. Сессии анализа:

    • Сохранить сессию после анализа
    • Открыть историю → найти сессию
    • Загрузить сессию → проверить что данные корректны
    • Удалить сессию
  6. Ошибки:

    • DB API недоступен → показать ошибку
    • RAG backend недоступен → показать ошибку 502
    • Невалидный токен → редирект на login

4.2 Автоматическое тестирование (опционально)

Backend tests (pytest):

tests/
├── test_auth.py          # Тесты авторизации
├── test_settings.py      # Тесты settings endpoints
├── test_query.py         # Тесты query endpoints
├── test_analysis.py      # Тесты analysis endpoints
└── conftest.py           # Fixtures (mock DB API, mock RAG)

Установка:

pip install pytest pytest-asyncio httpx

Запуск:

pytest tests/ -v

Этап 5: Production Deployment

5.1 Настройка .env для production

Критичные изменения:

# Application
DEBUG=false

# JWT (сгенерировать новый секретный ключ!)
JWT_SECRET_KEY=<сгенерированный-секрет-256-бит>

# DB API (production URL)
DB_API_URL=https://db-api.production.com/api/v1

# RAG Backends (production hosts)
IFT_RAG_HOST=ift-rag.production.com
PSI_RAG_HOST=psi-rag.production.com
PROD_RAG_HOST=prod-rag.production.com

# mTLS сертификаты (положить в certs/)
IFT_RAG_CERT_CA=/app/certs/ift/ca.crt
IFT_RAG_CERT_KEY=/app/certs/ift/client.key
IFT_RAG_CERT_CERT=/app/certs/ift/client.crt
# (аналогично для PSI и PROD)

5.2 CORS configuration

Изменить app/main.py:

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://brief-bench.production.com",  # Production domain
        "http://localhost:8000"                 # Для локальной разработки
    ],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)

5.3 Размещение mTLS сертификатов

certs/
├── ift/
│   ├── ca.crt
│   ├── client.key
│   ├── client.crt
├── psi/
│   ├── ca.crt
│   ├── client.key
│   ├── client.crt
└── prod/
    ├── ca.crt
    ├── client.key
    └── client.crt

Важно: Убедиться что права доступа 600 или 400 (только чтение владельцем)

5.4 Docker deployment

Запуск:

# Создать .env
cp .env.example .env
nano .env  # Заполнить production значения

# Разместить сертификаты
mkdir -p certs/ift certs/psi certs/prod
# Скопировать сертификаты в соответствующие папки

# Запустить
docker-compose up -d

# Проверить логи
docker-compose logs -f fastapi

# Проверить здоровье
curl http://localhost:8000/health

5.5 Logging и Monitoring

Добавить middleware для логирования (опционально):

# app/middleware/logging_middleware.py
import logging
import time
from fastapi import Request

logger = logging.getLogger(__name__)

async def log_requests(request: Request, call_next):
    start_time = time.time()

    logger.info(f"Request: {request.method} {request.url}")

    response = await call_next(request)

    process_time = time.time() - start_time
    logger.info(
        f"Response: {response.status_code} "
        f"({process_time:.2f}s)"
    )

    return response

Зарегистрировать в app/main.py:

from app.middleware.logging_middleware import log_requests

app.middleware("http")(log_requests)

Этап 6: Дополнительные улучшения (опционально)

6.1 Rate Limiting

  • Ограничить количество запросов от одного пользователя
  • Использовать slowapi или redis-based rate limiter

6.2 WebSocket для real-time updates

  • Показывать прогресс для backend mode (вопрос N из M)
  • Использовать FastAPI WebSocket

6.3 Export функционал

  • CSV экспорт сессий анализа
  • JSON экспорт для интеграции с другими системами

6.4 Advanced Analytics

  • Dashboard с метриками (кол-во запросов, успешность, время ответа)
  • Графики по окружениям

Приоритезация задач

Критично (сделать в первую очередь)

  1. Перенос статических файлов из rag-bench-old-version → static/
  2. Создание api-client.js
  3. Добавление login screen в index.html
  4. Переписывание вызовов API в app.js
  5. Тестирование auth flow

Важно (сделать после критичного)

  1. Интеграция Settings UI
  2. Environment selector
  3. Сохранение и загрузка сессий анализа
  4. Ручное тестирование всех сценариев

Желательно (если есть время)

  1. Автоматические тесты (pytest)
  2. Production deployment настройка
  3. Logging middleware
  4. Rate limiting
  5. Export функционал

Следующие шаги

Рекомендуемый порядок:

  1. Посмотреть структуру старого проекта rag-bench-old-version
  2. Скопировать статические файлы в static/
  3. Создать api-client.js с методами для всех endpoints
  4. Модифицировать index.html (добавить login screen)
  5. Переписать app.js для работы с новым API
  6. Протестировать локально с uvicorn app.main:app --reload
  7. Исправить найденные проблемы
  8. Подготовить production deployment

Готовы начать с первого этапа?