Kiến trúc và thiết lập môi trường Backend RAG
Chúng ta sẽ xây dựng một microservice backend sử dụng FastAPI để đóng vai trò là bộ điều phối (orchestrator). Service này sẽ nhận yêu cầu từ người dùng, gọi VectorDB để tìm ngữ cảnh (context), sau đó ghép ngữ cảnh đó vào prompt và gửi đến vLLM để tạo câu trả lời.
Đầu tiên, hãy tạo thư mục dự án và file cấu hình dependencies. Chúng ta cần `fastapi` để xử lý HTTP, `uvicorn` làm ASGI server, `langchain` để xử lý logic RAG, và `pymilvus` để kết nối với VectorDB (giả định bạn đã triển khai Milvus ở Phần 2).
Tạo file requirements.txt tại đường dẫn /app/backend/requirements.txt với nội dung:
fastapi==0.109.0
uvicorn[standard]==0.27.0
langchain==0.1.0
langchain-community==0.0.10
pymilvus==2.4.0
python-dotenv==1.0.0
pydantic==2.5.3
File này định nghĩa các thư viện cần thiết. Sau đó, tạo file Makefile tại /app/backend/Makefile để tiện quản lý build và chạy.
install:
pip install -r requirements.txt
run:
uvicicorn main:app --host 0.0.0.0 --port 8000 --reload
build:
docker build -t rag-backend:latest .
Thực thi lệnh cài đặt dependencies vào environment ảo hoặc container của bạn. Kết quả mong đợi là không có lỗi báo thiếu thư viện và quá trình cài đặt hoàn tất.
Triển khai logic kết nối VectorDB và Retrieval
Bước tiếp theo là xây dựng module kết nối với VectorDB. Chúng ta cần một class để thiết lập connection pool và hàm để thực hiện tìm kiếm vector (similarity search) dựa trên embedding của câu hỏi người dùng.
Tạo file /app/backend/db/milvus_client.py. File này chứa logic kết nối và hàm `retrieve_context`. Giả sử Milvus đang chạy trên host `milvus-standalone` trong Kubernetes namespace `rag-system` với port 19530.
from pymilvus import MilvusClient, DataType
from typing import List, Dict
import os
MILVUS_URI = os.getenv("MILVUS_URI", "http://milvus-standalone.rag-system.svc.cluster.local:19530")
COLLECTION_NAME = "documents"
DIMENSION = 1536
def get_client():
"""Khởi tạo client kết nối Milvus."""
return MilvusClient(uri=MILVUS_URI)
def retrieve_context(query_embedding: List[float], top_k: int = 3) -> List[Dict]:
"""
Tìm kiếm ngữ cảnh từ VectorDB dựa trên embedding.
Trả về danh sách các đoạn văn bản có độ tương đồng cao nhất.
"""
client = get_client()
try:
search_params = {
"metric_type": "L2",
"params": {"radius": 0.8},
"limit": top_k
}
results = client.search(
collection_name=COLLECTION_NAME,
data=[query_embedding],
output_fields=["text", "source"],
search_params=search_params
)
# Parse kết quả từ Milvus về dạng dict đơn giản
context_chunks = []
if results:
for hit in results[0]:
context_chunks.append({
"text": hit['entity']['text'],
"source": hit['entity'].get('source', 'unknown')
})
return context_chunks
except Exception as e:
print(f"Lỗi khi truy vấn VectorDB: {e}")
return []
File này đóng vai trò là adapter giữa ứng dụng và VectorDB. Hàm `retrieve_context` nhận vector embedding, tìm kiếm trong collection `documents` và trả về các đoạn văn bản kèm nguồn gốc. Kết quả mong đợi là hàm này có thể thực thi mà không bị lỗi connection (nếu Milvus đã sẵn sàng).
Xây dựng Prompt Template động và Logic RAG
Đến đây chúng ta cần module xử lý Prompt. Logic RAG cốt lõi là: [System Instruction] + [Context từ VectorDB] + [User Question]. Chúng ta sẽ tạo một hàm động để chèn context vào template trước khi gửi đi.
Tạo file /app/backend/core/prompt_engine.py. File này định nghĩa cấu trúc prompt và hàm ghép nối.
from typing import List, Dict
SYSTEM_TEMPLATE = """Bạn là một trợ lý AI chuyên nghiệp, chỉ được trả lời dựa trên các thông tin ngữ cảnh (Context) được cung cấp bên dưới.
Nếu ngữ cảnh không chứa câu trả lời cho câu hỏi, hãy nói "Tôi không có đủ thông tin trong ngữ cảnh hiện tại để trả lời câu hỏi này."
Dưới đây là ngữ cảnh tìm kiếm:
{context}
Hãy trả lời câu hỏi sau một cách ngắn gọn, chính xác và hữu ích."""
def build_prompt(context_list: List[Dict], user_question: str) -> str:
"""
Ghép ngữ cảnh tìm được vào prompt template.
context_list: Danh sách dict chứa 'text' và 'source'.
"""
# Ghép các đoạn text lại thành một chuỗi có dấu phân cách
context_text = "\n\n".join([
f"--- Nguồn: {chunk.get('source', 'unknown')} ---\n{chunk['text']}"
for chunk in context_list
])
if not context_text:
context_text = "Không tìm thấy ngữ cảnh liên quan."
return SYSTEM_TEMPLATE.format(context=context_text) + f"\n\nCâu hỏi: {user_question}"
Hàm `build_prompt` sẽ biến danh sách các đoạn văn bản rời rạc thành một khối ngữ cảnh có cấu trúc, giúp LLM hiểu rõ nguồn gốc thông tin. Kết quả mong đợi là một chuỗi string hoàn chỉnh chứa system instruction, context đã được format, và câu hỏi của người dùng.
Tích hợp vLLM Client và tạo API Endpoint
Bây giờ chúng ta kết nối mọi thứ lại. Tạo file chính /app/backend/main.py. File này sẽ chứa FastAPI app, endpoint POST `/query`, và logic gọi vLLM inference.
Giả định vLLM server chạy trên host `vllm-server` port 8000 trong cluster. Chúng ta dùng `httpx` để gọi API stream của vLLM.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import httpx
import os
from db.milvus_client import retrieve_context
from core.prompt_engine import build_prompt
app = FastAPI(title="RAG Backend API")
# Cấu hình từ biến môi trường
VLLM_ENDPOINT = os.getenv("VLLM_ENDPOINT", "http://vllm-server.rag-system.svc.cluster.local:8000/v1/chat/completions")
MODEL_NAME = os.getenv("MODEL_NAME", "mistral-7b-instruct")
class QueryRequest(BaseModel):
question: str
top_k: int = 3
temperature: float = 0.7
class QueryResponse(BaseModel):
answer: str
sources: List[str]
@app.post("/query", response_model=QueryResponse)
async def query_rag(request: QueryRequest):
"""
Endpoint chính xử lý yêu cầu RAG:
1. Tính toán embedding cho câu hỏi (giả sử có sẵn hàm compute_embedding)
2. Tìm kiếm context từ VectorDB
3. Xây dựng prompt
4. Gọi vLLM
"""
try:
# Bước 1: Giả lập tính toán embedding (Trong thực tế cần gọi model embedding)
# Ở đây dùng dummy vector để demo, bạn cần thay bằng hàm gọi embedding model thật
dummy_embedding = [0.01] * 1536
# Bước 2: Retrieve context từ VectorDB
context_chunks = retrieve_context(dummy_embedding, request.top_k)
if not context_chunks:
return QueryResponse(answer="Không tìm thấy thông tin liên quan trong cơ sở dữ liệu.", sources=[])
# Bước 3: Build prompt động
final_prompt = build_prompt(context_chunks, request.question)
# Bước 4: Gọi vLLM API
async with httpx.AsyncClient() as client:
response = await client.post(
VLLM_ENDPOINT,
json={
"model": MODEL_NAME,
"messages": [
{"role": "user", "content": final_prompt}
],
"temperature": request.temperature,
"stream": False
},
timeout=60.0
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Lỗi từ vLLM server")
result_data = response.json()
answer_text = result_data['choices'][0]['message']['content']
sources = list(set([chunk.get('source', 'unknown') for chunk in context_chunks]))
return QueryResponse(answer=answer_text, sources=sources)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Lỗi nội bộ: {str(e)}")
# Endpoint health check
@app.get("/health")
async def health_check():
return {"status": "healthy", "service": "rag-backend"}
Endpoint `/query` thực hiện toàn bộ luồng RAG. Nó nhận câu hỏi, gọi VectorDB (với embedding giả lập cho phần demo này), ghép prompt, và gọi vLLM. Kết quả mong đợi là một JSON trả về chứa câu trả lời và danh sách nguồn tài liệu.
Cấu hình Docker và Deploy lên Kubernetes
Để chạy service này trên Kubernetes, chúng ta cần Dockerfile và Kubernetes Manifest. Dockerfile sẽ đóng gói FastAPI app.
Tạo file /app/backend/Dockerfile với nội dung:
FROM python:3.10-slim
WORKDIR /app
# Cài đặt dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy source code
COPY . .
# Chạy ứng dụng
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
File này tạo một image nhẹ dựa trên Python slim, cài đặt thư viện và chạy uvicorn. Kết quả mong đợi là build thành công image docker.
Tiếp theo, tạo file Kubernetes Deployment /app/backend/k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: rag-backend
namespace: rag-system
spec:
replicas: 2
selector:
matchLabels:
app: rag-backend
template:
metadata:
labels:
app: rag-backend
spec:
containers:
- name: backend
image: rag-backend:latest
ports:
- containerPort: 8000
env:
- name: MILVUS_URI
value: "http://milvus-standalone.rag-system.svc.cluster.local:19530"
- name: VLLM_ENDPOINT
value: "http://vllm-server.rag-system.svc.cluster.local:8000/v1/chat/completions"
- name: MODEL_NAME
value: "mistral-7b-instruct"
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "200m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: rag-backend-service
namespace: rag-system
spec:
selector:
app: rag-backend
ports:
- port: 8000
targetPort: 8000
type: ClusterIP
File deployment.yaml triển khai 2 replica của backend, inject các biến môi trường để connect tới Milvus và vLLM qua internal DNS của K8s, và expose qua Service ClusterIP. Kết quả mong đợi là khi apply file này, pod sẽ ở trạng thái Running và có thể truy cập từ các service khác trong cluster.
Verify kết quả hoạt động
Để xác nhận backend đã hoạt động đúng, hãy thực hiện các bước kiểm tra sau từ trong container hoặc từ node master.
Trước tiên, kiểm tra trạng thái pod và service:
kubectl get pods -n rag-system | grep rag-backend
kubectl get svc -n rag-system | grep rag-backend
Kết quả mong đợi là trạng thái pod là `Running` với `2/2` containers sẵn sàng, và service đã được expose ở port 8000.
Tiếp theo, thực hiện một yêu cầu thử nghiệm (test query) vào endpoint `/query`. Lưu ý: Bạn cần thay thế `dummy_embedding` trong code bằng một vector thực tế hoặc sử dụng một model embedding đơn giản nếu muốn test toàn bộ luồng thật.
curl -X POST http://rag-backend-service.rag-system.svc.cluster.local:8000/query \
-H "Content-Type: application/json" \
-d '{
"question": "Làm thế nào để deploy vLLM trên Kubernetes?",
"top_k": 2,
"temperature": 0.7
}'
Kết quả mong đợi là một JSON trả về có cấu trúc:
{
"answer": "...Nội dung câu trả lời dựa trên ngữ cảnh...",
"sources": ["part5_vllm.md", "architecture.md"]
}
Nếu thấy lỗi connection, hãy kiểm tra logs của pod bằng lệnh kubectl logs -f deployment/rag-backend -n rag-system để xem lỗi cụ thể ở bước nào (connect Milvus hay connect vLLM).
Điều hướng series:
Mục lục: Series: Xây dựng nền tảng Private LLM với vLLM, RAG và VectorDB trên hạ tầng Kubernetes
« Phần 5: Triển khai vLLM server trên Kubernetes với GPU support
Phần 7: Tích hợp Frontend và API Gateway cho trải nghiệm người dùng »