brief-rags-bench/DEVELOPMENT_PLAN.md

683 lines
22 KiB
Markdown
Raw Normal View History

2025-12-17 15:37:32 +01:00
# План развития 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 для анализа ответов
**Файлы для переноса:**
```bash
# Скопировать из 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`
**Полная реализация:**
```javascript
/**
* 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`:**
```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):**
```javascript
// Было
const response = await fetch('https://rag-backend/api/bench', {
method: 'POST',
headers: { ... },
body: JSON.stringify(questions)
})
```
**Новый код (через API client):**
```javascript
// Стало
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`:**
```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):**
```bash
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)
```
**Установка:**
```bash
pip install pytest pytest-asyncio httpx
```
**Запуск:**
```bash
pytest tests/ -v
```
---
## Этап 5: Production Deployment
### 5.1 Настройка .env для production
**Критичные изменения:**
```bash
# 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`:**
```python
# 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 сертификатов
```bash
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
**Запуск:**
```bash
# Создать .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 для логирования (опционально):**
```python
# 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`:**
```python
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
### 🟡 Важно (сделать после критичного)
6. Интеграция Settings UI
7. Environment selector
8. Сохранение и загрузка сессий анализа
9. Ручное тестирование всех сценариев
### 🟢 Желательно (если есть время)
10. Автоматические тесты (pytest)
11. Production deployment настройка
12. Logging middleware
13. Rate limiting
14. 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
**Готовы начать с первого этапа?**