import logging import sys import os from enum import Enum from typing import List, Optional, Dict, Any from fastapi import FastAPI, HTTPException, BackgroundTasks, status from pydantic import BaseModel, Field, validator import uvicorn # Đảm bảo đường dẫn module sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from chat.rag_engine import RAGEngine from core.config import settings # --- Cấu hình Logging chuyên nghiệp --- logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) logger = logging.getLogger("RAG_API") app = FastAPI( title="Enterprise SharePoint RAG API", description="Hệ thống hỏi đáp AI nội bộ sử dụng kiến trúc Modular Providers và Distributed VLM.", version="1.1.0", docs_url="/docs", redoc_url="/redoc" ) # --- Singleton Engine Instance --- rag_engine = None @app.on_event("startup") async def startup_event(): global rag_engine try: logger.info(f"Đang khởi tạo RAG Engine với Provider: {settings.llm_provider}") # Đảm bảo host OpenSearch đúng trong môi trường dev if settings.opensearch_host == "opensearch" and os.environ.get("ENV") != "docker": settings.opensearch_host = "localhost" rag_engine = RAGEngine() # Thông báo sẵn sàng kèm địa chỉ truy cập host = "0.0.0.0" port = 8000 logger.info("="*60) logger.info("🚀 RAG ENGINE ĐÃ SẴN SÀNG PHỤC VỤ!") logger.info(f"🔗 API Endpoint: http://localhost:{port}") logger.info(f"📖 Swagger UI: http://localhost:{port}/docs") logger.info(f"📊 Health Check: http://localhost:{port}/health") logger.info("="*60) except Exception as e: logger.critical(f"❌ THẤT BẠI khi khởi động RAG Engine: {e}") # --- SCHEMAS --- class ChatRole(str, Enum): user = "user" assistant = "assistant" system = "system" class ChatHistoryItem(BaseModel): role: ChatRole = Field(..., description="Vai trò của người gửi (user/assistant)") content: str = Field(..., min_length=1, description="Nội dung tin nhắn") class ChatRequest(BaseModel): query: str = Field( ..., min_length=2, max_length=1000, example="Quy trình bảo trì thiết bị là gì?", description="Câu hỏi của người dùng" ) history: List[ChatHistoryItem] = Field( default_factory=list, description="Lịch sử cuộc trò chuyện để duy trì ngữ cảnh" ) class SourceCitation(BaseModel): file_name: str page: int url: Optional[str] = None class ChatResponse(BaseModel): answer: str = Field(..., description="Câu trả lời từ AI") sources: List[SourceCitation] = Field(default_factory=list, description="Danh sách các nguồn trích dẫn từ tài liệu") context_used: Optional[str] = Field(None, description="Ngữ cảnh thực tế đã được trích xuất từ VectorDB (Dùng cho Debug/UI)") # --- ENDPOINTS --- @app.get("/health", tags=["System"]) async def health_check(): """Kiểm tra trạng thái kết nối tới OpenSearch và LLM.""" return { "status": "online" if rag_engine else "offline", "engine_ready": rag_engine is not None, "config": { "provider": settings.llm_provider, "opensearch_host": settings.opensearch_host } } @app.post("/chat", response_model=ChatResponse, tags=["RAG"], status_code=status.HTTP_200_OK) async def chat_endpoint(request: ChatRequest): """ Điểm cuối xử lý hội thoại RAG. Hệ thống sẽ tự động trích xuất ngữ cảnh từ OpenSearch và sử dụng Provider đã cấu hình để trả lời. """ if not rag_engine: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Hệ thống RAG đang khởi động hoặc gặp sự cố kết nối Database." ) try: # Chuyển đổi ChatHistoryItem sang format dict cho RAGEngine history_data = [item.dict() for item in request.history] logger.info(f"Xử lý truy vấn: {request.query[:50]}...") result = rag_engine.chat(request.query, history=history_data) return ChatResponse( answer=result["answer"], sources=result["sources"], context_used=result.get("context_used") ) except Exception as e: logger.error(f"Lỗi thực thi RAG: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Đã xảy ra lỗi nội bộ trong quá trình xử lý ngôn ngữ." ) if __name__ == "__main__": uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)