edit phase 7 version 1
This commit is contained in:
159
api/main.py
159
api/main.py
@@ -1,93 +1,138 @@
|
||||
import logging
|
||||
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional, Dict, Any
|
||||
import uvicorn
|
||||
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
|
||||
|
||||
# Thêm thư mục gốc vào PYTHONPATH để tìm thấy các module chat, search, ingestion...
|
||||
# Đả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
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
logger = logging.getLogger("API")
|
||||
# --- 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="PoC SharePoint RAG API",
|
||||
description="Hệ thống hỏi đáp nội bộ dựa trên SharePoint và Distributed VLM",
|
||||
version="1.0.0"
|
||||
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"
|
||||
)
|
||||
|
||||
# Khởi tạo RAG Engine (Singleton)
|
||||
# Khi API khởi động, nó sẽ nạp sẵn Embedding Model vào RAM để phản hồi cực nhanh
|
||||
try:
|
||||
if settings.opensearch_host == "opensearch":
|
||||
settings.opensearch_host = "localhost" # Fallback cho Local Dev
|
||||
rag_engine = RAGEngine()
|
||||
except Exception as e:
|
||||
logger.error(f"Không thể khởi động RAG Engine: {e}")
|
||||
rag_engine = None
|
||||
# --- 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")
|
||||
|
||||
# --- MODELS ---
|
||||
class ChatRequest(BaseModel):
|
||||
query: str
|
||||
history: Optional[List[Dict[str, str]]] = []
|
||||
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
|
||||
sources: List[Dict[str, Any]] = []
|
||||
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")
|
||||
def health_check():
|
||||
"""Kiểm tra sức khỏe hệ thống"""
|
||||
@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": "healthy",
|
||||
"llm_provider": settings.llm_provider,
|
||||
"opensearch_host": settings.opensearch_host
|
||||
"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)
|
||||
@app.post("/chat", response_model=ChatResponse, tags=["RAG"], status_code=status.HTTP_200_OK)
|
||||
async def chat_endpoint(request: ChatRequest):
|
||||
"""
|
||||
Điểm cuối để thực hiện hỏi đáp (RAG).
|
||||
Đ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=503, detail="RAG Engine chưa được khởi tạo thành công.")
|
||||
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:
|
||||
logger.info(f"Nhận câu hỏi: {request.query}")
|
||||
result = rag_engine.chat(request.query, history=request.history)
|
||||
# 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"]
|
||||
sources=result["sources"],
|
||||
context_used=result.get("context_used")
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Lỗi xử lý chat: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@app.post("/ingest")
|
||||
async def start_ingestion(background_tasks: BackgroundTasks):
|
||||
"""
|
||||
Kích hoạt quá trình quét SharePoint và nạp dữ liệu vào OpenSearch.
|
||||
Chạy dưới dạng Background Task để không làm treo API.
|
||||
"""
|
||||
# TODO: Kết nối với script sync.py và quy trình extraction
|
||||
# Ở đây chúng ta sẽ gọi một function xử lý bất đồng bộ
|
||||
background_tasks.add_task(dummy_ingest_task)
|
||||
return {"message": "Quá trình đồng bộ dữ liệu đã bắt đầu chạy ngầm."}
|
||||
|
||||
async def dummy_ingest_task():
|
||||
logger.info("Bắt đầu Ingestion task...")
|
||||
# Sẽ tích hợp logic từ test_rag_pipeline.py vào đây
|
||||
pass
|
||||
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__":
|
||||
# Chạy server tại cổng 8000
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
||||
|
||||
Reference in New Issue
Block a user