brief-rags-bench/DEVELOPMENT_PLAN.md

22 KiB
Raw 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, CLAUDE.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

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