RAG - Dạy Jarvis đọc tài liệu mật (PDF/Docx) của bạn

AI Hunter

Member
RAG là kỹ thuật giúp AI trả lời câu hỏi dựa trên dữ liệu bên ngoài mà nó chưa từng được học.
Quy trình đơn giản như sau:
  1. Upload: Bạn gửi file PDF cho Jarvis.
  2. Chunking: Jarvis cắt nhỏ file thành các đoạn văn ngắn.
  3. Embedding: Chuyển các đoạn văn đó thành Vector (dãy số) bằng một model chuyên dụng.
  4. Storage: Lưu vào Qdrant.
  5. Retrieval: Khi bạn hỏi, Jarvis tìm đoạn văn liên quan nhất trong Qdrant và dùng nó để trả lời.
RAG - Dạy Jarvis đọc tài liệu mật (PDFDocx) của bạn.jpg

1. Chuẩn bị nguyên liệu​


Chúng ta cần cài thêm vài thư viện để xử lý file và Vector.
Mở file backend/requirements.txt và thêm:

Mã:
langchain
langchain-community
langchain-ollama
langchain-qdrant
pypdf
qdrant-client

Chúng ta cũng cần một model Embedding nhẹ và nhanh. Hãy mở Terminal và kéo model nomic-embed-text về Ollama (model này cực ngon cho RAG):
Mã:
ollama pull nomic-embed-text

2. Nâng cấp Backend (FastAPI)​


Mở file backend/server.py. Code sẽ dài hơn một chút vì phải xử lý file.

Python:
import os
import shutil
from fastapi import FastAPI, UploadFile, File, HTTPException
from pydantic import BaseModel
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
from langchain_qdrant import QdrantVectorStore
from qdrant_client import QdrantClient
from openai import OpenAI

app = FastAPI()

# 1. Cấu hình Qdrant & Embedding
# Kết nối Qdrant (chạy trong Docker thì dùng 'http://qdrant:6333')
qdrant_url = os.getenv("QDRANT_URL", "http://localhost:6333")
client = QdrantClient(url=qdrant_url)

# Model Embedding chạy Local qua Ollama
embeddings = OllamaEmbeddings(
    model="nomic-embed-text",
    base_url=os.getenv("OLLAMA_URL", "http://localhost:11434")
)

# Vector Store (Nơi lưu kiến thức)
vector_store = QdrantVectorStore(
    client=client,
    collection_name="my_knowledge",
    embedding=embeddings,
)

# Client Chat (Ollama/OpenAI)
chat_client = OpenAI(
    base_url=os.getenv("OLLAMA_URL", "http://localhost:11434/v1"),
    api_key="ollama"
)

# --- API UPLOAD TÀI LIỆU ---
@app.post("/upload")
async def upload_document(file: UploadFile = File(...)):
    temp_file = f"temp_{file.filename}"
   
    try:
        # Lưu file tạm để thư viện đọc
        with open(temp_file, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)
           
        # 1. Load PDF
        loader = PyPDFLoader(temp_file)
        docs = loader.load()
       
        # 2. Cắt nhỏ văn bản (Chunking)
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000, # Mỗi đoạn 1000 ký tự
            chunk_overlap=200 # Gối đầu 200 ký tự để không mất ngữ cảnh
        )
        splits = text_splitter.split_documents(docs)
       
        # 3. Mã hóa và lưu vào Qdrant
        vector_store.add_documents(documents=splits)
       
        return {"status": "success", "chunks": len(splits)}
       
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
    finally:
        # Dọn dẹp file tạm
        if os.path.exists(temp_file):
            os.remove(temp_file)

# --- API CHAT (CÓ RAG) ---
class ChatRequest(BaseModel):
    message: str

@app.post("/chat")
async def chat_endpoint(req: ChatRequest):
    # 1. Tìm kiếm thông tin liên quan trong Qdrant
    results = vector_store.similarity_search(req.message, k=3) # Lấy 3 đoạn giống nhất
   
    # Ghép context
    context_text = "\n\n".join([doc.page_content for doc in results])
   
    if not context_text:
        context_text = "Không có thông tin trong tài liệu."

    # 2. Tạo Prompt RAG
    prompt = f"""
    Sử dụng thông tin sau để trả lời câu hỏi. Nếu không biết, hãy nói không biết.
   
    [THÔNG TIN TÀI LIỆU]:
    {context_text}
   
    [CÂU HỎI]:
    {req.message}
    """

    # 3. Gửi cho LLM trả lời
    response = chat_client.chat.completions.create(
        model="llama3.1",
        messages=[{"role": "user", "content": prompt}]
    )
   
    return {
        "answer": response.choices[0].message.content,
        "sources": [doc.metadata.get("source", "unknown") for doc in results]
    }

3. Nâng cấp Frontend (Chainlit)​


Mở file frontend/app.py. Chúng ta cần thêm nút Upload file.

Python:
import chainlit as cl
import requests

API_URL = "http://backend:8000" # Gọi nội bộ Docker

@cl.on_chat_start
async def start():
    files = None
   
    # Chờ User upload file
    while files == None:
        files = await cl.AskFileMessage(
            content="Xin chào! Vui lòng upload file PDF để tôi học (hoặc gõ 'skip' để bỏ qua).",
            accept=["application/pdf"],
            max_size_mb=20,
            timeout=180
        ).send()

    text_file = files[0]
   
    # Gửi file sang Backend
    msg = cl.Message(content=f"Đang đọc file {text_file.name}...")
    await msg.send()
   
    with open(text_file.path, "rb") as f:
        files = {"file": (text_file.name, f, "application/pdf")}
        response = requests.post(f"{API_URL}/upload", files=files)
       
    if response.status_code == 200:
        await msg.update(content=f"✅ Đã học xong {text_file.name}! Giờ bạn có thể hỏi tôi về nó.")
    else:
        await msg.update(content=f"❌ Lỗi: {response.text}")

@cl.on_message
async def main(message: cl.Message):
    # Chat bình thường (Backend tự lo phần RAG)
    payload = {"message": message.content}
    response = requests.post(f"{API_URL}/chat", json=payload)
   
    if response.status_code == 200:
        data = response.json()
        answer = data["answer"]
        sources = list(set(data.get("sources", []))) # Lọc trùng
       
        # Hiển thị nguồn tham khảo
        if sources:
            answer += "\n\n📚 *Nguồn:* " + ", ".join(sources)
           
        await cl.Message(content=answer).send()
    else:
        await cl.Message(content="Lỗi Server").send()

4. Chạy lại Docker​


Do chúng ta thêm thư viện mới vào requirements.txt, cần phải build lại ảnh Docker:

Mã:
docker-compose up --build

5. Test thực tế​


  1. Truy cập http://localhost:8501.
  2. Jarvis sẽ hiện nút upload. Hãy ném vào file "HopDongLaoDong.pdf" của bạn.
  3. Đợi vài giây để nó học.
  4. Hỏi: "Lương cơ bản trong hợp đồng là bao nhiêu?".
  5. Jarvis sẽ trích xuất đúng con số trong file PDF để trả lời bạn.

Tổng kết​


Chúc mừng! Bạn đã sở hữu một hệ thống Private RAG hoàn chỉnh.
Dữ liệu của bạn nằm trên máy bạn, xử lý bằng CPU của bạn, không ai đọc trộm được.

Hệ thống Jarvis của chúng ta giờ đã quá mạnh mẽ. Nhưng để hoàn thiện trải nghiệm "Iron Man", vẫn còn thiếu một thứ: Giọng nói.
Sẽ thật tuyệt nếu bạn chỉ cần hô: "Hey Jarvis!" là nó bật dậy nghe lệnh, thay vì phải bấm nút.
 
Back
Top