edit phase 7 version 1
This commit is contained in:
155
api/main.py
155
api/main.py
@@ -1,93 +1,138 @@
|
|||||||
import logging
|
import logging
|
||||||
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional, Dict, Any
|
|
||||||
import uvicorn
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
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__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from chat.rag_engine import RAGEngine
|
from chat.rag_engine import RAGEngine
|
||||||
from core.config import settings
|
from core.config import settings
|
||||||
|
|
||||||
# Cấu hình Logging
|
# --- Cấu hình Logging chuyên nghiệp ---
|
||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
logging.basicConfig(
|
||||||
logger = logging.getLogger("API")
|
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(
|
app = FastAPI(
|
||||||
title="PoC SharePoint RAG API",
|
title="Enterprise SharePoint RAG API",
|
||||||
description="Hệ thống hỏi đáp nội bộ dựa trên SharePoint và Distributed VLM",
|
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.0.0"
|
version="1.1.0",
|
||||||
|
docs_url="/docs",
|
||||||
|
redoc_url="/redoc"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Khởi tạo RAG Engine (Singleton)
|
# --- Singleton Engine Instance ---
|
||||||
# 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
|
rag_engine = None
|
||||||
|
|
||||||
# --- MODELS ---
|
@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):
|
class ChatRequest(BaseModel):
|
||||||
query: str
|
query: str = Field(
|
||||||
history: Optional[List[Dict[str, str]]] = []
|
...,
|
||||||
|
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):
|
class ChatResponse(BaseModel):
|
||||||
answer: str
|
answer: str = Field(..., description="Câu trả lời từ AI")
|
||||||
sources: List[Dict[str, Any]] = []
|
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 ---
|
# --- ENDPOINTS ---
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health", tags=["System"])
|
||||||
def health_check():
|
async def health_check():
|
||||||
"""Kiểm tra sức khỏe hệ thống"""
|
"""Kiểm tra trạng thái kết nối tới OpenSearch và LLM."""
|
||||||
return {
|
return {
|
||||||
"status": "healthy",
|
"status": "online" if rag_engine else "offline",
|
||||||
"llm_provider": settings.llm_provider,
|
"engine_ready": rag_engine is not None,
|
||||||
|
"config": {
|
||||||
|
"provider": settings.llm_provider,
|
||||||
"opensearch_host": settings.opensearch_host
|
"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):
|
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:
|
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:
|
try:
|
||||||
logger.info(f"Nhận câu hỏi: {request.query}")
|
# Chuyển đổi ChatHistoryItem sang format dict cho RAGEngine
|
||||||
result = rag_engine.chat(request.query, history=request.history)
|
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(
|
return ChatResponse(
|
||||||
answer=result["answer"],
|
answer=result["answer"],
|
||||||
sources=result["sources"]
|
sources=result["sources"],
|
||||||
|
context_used=result.get("context_used")
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Lỗi xử lý chat: {e}")
|
logger.error(f"Lỗi thực thi RAG: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
@app.post("/ingest")
|
detail="Đã xảy ra lỗi nội bộ trong quá trình xử lý ngôn ngữ."
|
||||||
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
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Chạy server tại cổng 8000
|
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user