feat: add submitted collection, /similar and /submitted endpoints (Stage 4)
Made-with: Cursor
This commit is contained in:
66
app/main.py
66
app/main.py
@@ -7,8 +7,8 @@ FastAPI приложение Embedding сервиса.
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@@ -18,11 +18,12 @@ from app.api.routes import router
|
||||
from app.config import get_settings
|
||||
from app.services.rag_service import RAGService, get_rag_service
|
||||
|
||||
|
||||
# Настройка логирования
|
||||
def setup_logging() -> None:
|
||||
"""Настраивает логирование для приложения."""
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, settings.log_level.upper()),
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
@@ -30,7 +31,7 @@ def setup_logging() -> None:
|
||||
logging.StreamHandler(sys.stdout),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Уменьшаем логи от библиотек
|
||||
logging.getLogger("transformers").setLevel(logging.WARNING)
|
||||
logging.getLogger("torch").setLevel(logging.WARNING)
|
||||
@@ -40,33 +41,40 @@ def setup_logging() -> None:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Глобальная задача автосохранения
|
||||
_autosave_task: Optional[asyncio.Task] = None
|
||||
_autosave_task: asyncio.Task | None = None
|
||||
|
||||
|
||||
async def autosave_loop(service: RAGService, interval: int) -> None:
|
||||
"""
|
||||
Фоновая задача для периодического сохранения векторов.
|
||||
|
||||
|
||||
Args:
|
||||
service: RAG сервис
|
||||
interval: Интервал сохранения в секундах
|
||||
"""
|
||||
logger.info(f"Автосохранение запущено (интервал: {interval} сек)")
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
|
||||
# Сохраняем только если есть данные
|
||||
if service.vector_store.total_count > 0:
|
||||
has_examples = service.vector_store.total_count > 0
|
||||
has_submitted = service.vector_store.submitted_count > 0
|
||||
if has_examples or has_submitted:
|
||||
service.save_vectors()
|
||||
logger.info(
|
||||
f"Автосохранение: сохранено {service.vector_store.positive_count} pos, "
|
||||
f"{service.vector_store.negative_count} neg"
|
||||
)
|
||||
parts = []
|
||||
if has_examples:
|
||||
parts.append(
|
||||
f"{service.vector_store.positive_count} pos, "
|
||||
f"{service.vector_store.negative_count} neg"
|
||||
)
|
||||
if has_submitted:
|
||||
parts.append(f"{service.vector_store.submitted_count} submitted")
|
||||
logger.info(f"Автосохранение: сохранено {', '.join(parts)}")
|
||||
else:
|
||||
logger.debug("Автосохранение: нет данных для сохранения")
|
||||
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info("Автосохранение остановлено")
|
||||
break
|
||||
@@ -79,43 +87,41 @@ async def autosave_loop(service: RAGService, interval: int) -> None:
|
||||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
"""
|
||||
Lifespan контекст для FastAPI.
|
||||
|
||||
|
||||
При запуске:
|
||||
- Настраивает логирование
|
||||
- Прогревает модель (опционально)
|
||||
|
||||
|
||||
При остановке:
|
||||
- Сохраняет векторы на диск
|
||||
"""
|
||||
global _autosave_task
|
||||
|
||||
|
||||
setup_logging()
|
||||
logger.info(f"Embedding Service v{__version__} запускается...")
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
logger.info(f"Настройки: model={settings.model_name}, vectors_path={settings.vectors_path}")
|
||||
|
||||
|
||||
# Получаем сервис (создается singleton)
|
||||
service = get_rag_service()
|
||||
|
||||
|
||||
# Запускаем автосохранение если включено
|
||||
if settings.autosave_interval > 0:
|
||||
_autosave_task = asyncio.create_task(
|
||||
autosave_loop(service, settings.autosave_interval)
|
||||
)
|
||||
_autosave_task = asyncio.create_task(autosave_loop(service, settings.autosave_interval))
|
||||
logger.info(f"Автосохранение включено: каждые {settings.autosave_interval} сек")
|
||||
else:
|
||||
logger.info("Автосохранение отключено")
|
||||
|
||||
|
||||
# Прогреваем модель при запуске (опционально)
|
||||
# Можно раскомментировать если нужен автопрогрев
|
||||
# logger.info("Прогрев модели при запуске...")
|
||||
# await service.warmup()
|
||||
|
||||
|
||||
logger.info("Embedding Service готов к работе")
|
||||
|
||||
|
||||
yield
|
||||
|
||||
|
||||
# Останавливаем автосохранение
|
||||
if _autosave_task and not _autosave_task.done():
|
||||
_autosave_task.cancel()
|
||||
@@ -123,7 +129,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
await _autosave_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
|
||||
# При остановке сохраняем векторы
|
||||
logger.info("Embedding Service останавливается, финальное сохранение векторов...")
|
||||
try:
|
||||
@@ -131,7 +137,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
logger.info("Векторы сохранены")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка сохранения векторов: {e}")
|
||||
|
||||
|
||||
logger.info("Embedding Service остановлен")
|
||||
|
||||
|
||||
@@ -176,19 +182,21 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
# Простой healthcheck endpoint без авторизации (для Docker healthcheck)
|
||||
@app.get("/health")
|
||||
async def simple_health_check():
|
||||
"""Простая проверка здоровья без авторизации (для Docker healthcheck)."""
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
# Подключение роутов
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
|
||||
Reference in New Issue
Block a user