import asyncio import logging import os import sys from contextlib import asynccontextmanager from alembic import command from alembic.config import Config as AlembicConfig from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from config import settings # Настройка логов до импорта приложения: uvicorn ставит свои handlers # на root-logger, поэтому basicConfig в lifespan уже не срабатывает # (handlers есть — basicConfig no-op). force=True перебивает. logging.basicConfig( level=getattr(logging, settings.log_level.upper(), logging.INFO), format="%(asctime)s %(levelname)-7s %(name)s: %(message)s", datefmt="%H:%M:%S", handlers=[logging.StreamHandler(sys.stderr)], force=True, ) from services.embeddings import EmbeddingService # noqa: E402 from services.llm_client import LLMClient # noqa: E402 from services.vectorstore import VectorStoreService # noqa: E402 logger = logging.getLogger(__name__) embedding_service: EmbeddingService | None = None vectorstore_service: VectorStoreService | None = None llm_client: LLMClient | None = None def _run_migrations() -> None: """Автоматически подтягиваем схему до последней ревизии при старте.""" os.makedirs(os.path.dirname(settings.sqlite_path), exist_ok=True) cfg = AlembicConfig("alembic.ini") command.upgrade(cfg, "head") @asynccontextmanager async def lifespan(app: FastAPI): global embedding_service, vectorstore_service, llm_client logger.info("Running DB migrations…") await asyncio.to_thread(_run_migrations) logger.info("Loading embedding model: %s", settings.embedding_model) embedding_service = EmbeddingService(settings.embedding_model) logger.info("Embedding model loaded") vectorstore_service = VectorStoreService( persist_dir=settings.chroma_persist_dir, embedding_service=embedding_service, ) logger.info("ChromaDB initialized at %s", settings.chroma_persist_dir) llm_client = LLMClient() logger.info("LLM client ready (model=%s)", llm_client.model) yield logger.info("Shutting down") app = FastAPI( title="Chat Agent for Patients — Tuning Tool", description="RAG-ядро и инструмент настройки пациентского чат-агента", version="0.1.0", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) from routers import chat, documents, health, query, threads # noqa: E402 app.include_router(health.router) app.include_router(documents.router) app.include_router(query.router) app.include_router(chat.router) app.include_router(threads.router) app.mount("/", StaticFiles(directory="static", html=True), name="static")