LlamaIndex + Ollama: Xây dựng hệ thống hỏi đáp tài liệu nội bộ hoàn toàn offline

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

Vấn đề thực tế: Khi tài liệu nội bộ không được lên cloud

Công ty mình từng có một đống tài liệu kỹ thuật: hàng chục file PDF, Word về quy trình vận hành, hợp đồng, manual máy móc. Mỗi khi cần tra cứu, phải mở từng file, Ctrl+F, lật qua lật lại. Mất cả tiếng đồng hồ cho một câu hỏi đơn giản.

Giải pháp đầu tiên mình nghĩ đến: upload lên ChatGPT rồi hỏi. Nhưng ngay khi vừa drag file vào browser, trưởng bộ phận IT nhảy vào ngăn lại — tài liệu có thông tin khách hàng, hợp đồng bảo mật, không thể đẩy lên server của OpenAI.

Đó là lúc mình bắt đầu tìm hiểu LlamaIndex kết hợp Ollama — chạy AI hoàn toàn trên máy chủ nội bộ, không một byte dữ liệu nào rời khỏi công ty.

Ba cách phổ biến để “hỏi chuyện” với tài liệu của bạn

Trước khi đi vào kỹ thuật, mình muốn so sánh thẳng thắn ba hướng tiếp cận để bạn chọn đúng công cụ cho đúng bài toán.

Cách 1 — Upload thẳng lên ChatGPT/Claude

Đơn giản nhất: kéo file PDF vào ChatGPT rồi hỏi. Không cần cài đặt gì, dùng được ngay.

  • Ưu điểm: Không cần code, setup tức thì, chất lượng trả lời cao
  • Nhược điểm: Dữ liệu đi lên server của OpenAI/Anthropic. Giới hạn kích thước file. Mỗi phiên đều phải upload lại. Không phù hợp tài liệu bảo mật.

Cách 2 — LangChain RAG với cloud LLM

Build pipeline RAG bằng LangChain, dùng OpenAI API làm LLM. Đây là approach quen thuộc với nhiều developer.

  • Ưu điểm: Linh hoạt, ecosystem rộng, nhiều tài liệu hướng dẫn
  • Nhược điểm: Query (và đoạn tài liệu liên quan) vẫn gửi lên cloud. Tốn phí API theo token. Phụ thuộc kết nối internet.

Cách 3 — LlamaIndex + Ollama (offline hoàn toàn)

LlamaIndex chuyên biệt cho bài toán indexing và query tài liệu. Kết hợp với Ollama chạy LLM local, toàn bộ pipeline hoạt động 100% trên máy của bạn.

  • Ưu điểm: Không gửi dữ liệu ra ngoài. Không mất phí API. Hoạt động không cần internet sau khi setup.
  • Nhược điểm: Cần phần cứng đủ mạnh. Chất lượng model local thường thua GPT-4.

LlamaIndex khác LangChain ở điểm nào?

LangChain là framework đa năng — nó làm được nhiều thứ: agent, chain, tool calling, RAG… Nhưng chính vì cố làm tất cả nên API thay đổi liên tục và đôi khi phức tạp hơn mức cần thiết với bài toán thuần túy về tài liệu.

LlamaIndex ngay từ đầu được thiết kế chỉ để giải một bài toán: kết nối dữ liệu của bạn với LLM. Nó có các khái niệm rõ ràng hơn:

  • Document Loaders: Hàng chục connector sẵn có cho PDF, Word, CSV, Notion, Google Docs…
  • Node Parser: Chia nhỏ tài liệu thành chunk một cách có kiểm soát
  • Index: Nhiều loại index (VectorStoreIndex, SummaryIndex, KnowledgeGraphIndex…)
  • Query Engine: Xử lý câu hỏi và trả lời với ngữ cảnh từ tài liệu

Với bài toán hỏi đáp tài liệu thuần túy, LlamaIndex thường ít code hơn đáng kể. Code ở bước tiếp theo chỉ khoảng 15 dòng — viết tương đương với LangChain thường cần gấp đôi, chưa kể phải theo dõi xem API nào vừa bị deprecated.

Khi nào nên chọn LlamaIndex + Ollama?

Giải pháp này dành cho bạn khi:

  • Có tài liệu nội bộ nhạy cảm (hợp đồng, NDA, dữ liệu khách hàng, bí mật kinh doanh)
  • Làm việc trong môi trường không có internet hoặc intranet
  • Muốn tiết kiệm chi phí API khi có nhiều câu hỏi mỗi ngày
  • Cần hệ thống chạy trên server nội bộ (on-premise)

Nếu tài liệu không bảo mật và bạn chỉ muốn test nhanh — ChatGPT sẽ nhanh hơn nhiều. Đừng over-engineer.

Hướng dẫn triển khai thực tế

Bước 1 — Cài Ollama và pull model

# Cài Ollama (Linux/Mac)
curl -fsSL https://ollama.com/install.sh | sh

# Pull model nhẹ, phù hợp máy không có GPU mạnh
ollama pull llama3.2

# Pull embedding model riêng (chất lượng tốt hơn dùng chung)
ollama pull nomic-embed-text

# Kiểm tra model đã sẵn sàng
ollama list

Model chạy ở http://localhost:11434 — LlamaIndex sẽ kết nối vào đây.

Bước 2 — Cài LlamaIndex và các dependencies

pip install llama-index llama-index-llms-ollama llama-index-embeddings-ollama

# Parser cho PDF và Word
pip install llama-index-readers-file python-docx pypdf

Bước 3 — Load PDF và Word, tạo index

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.core import Settings

# Cấu hình dùng Ollama thay vì OpenAI
Settings.llm = Ollama(model="llama3.2", request_timeout=120.0)
Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")

# Đặt tất cả PDF, Word vào thư mục ./documents/
# SimpleDirectoryReader tự nhận dạng định dạng file
documents = SimpleDirectoryReader("./documents").load_data()

# Tạo vector index từ tài liệu
index = VectorStoreIndex.from_documents(documents)

# Hỏi thử
query_engine = index.as_query_engine()
response = query_engine.query("Điều khoản bảo hành trong hợp đồng số 2024-001 là gì?")
print(response)

Bước 4 — Lưu index để không phải build lại mỗi lần

Build index lần đầu có thể mất vài phút — 50 file PDF thường tốn 3-5 phút trên CPU. Lưu lại, lần sau load tức thì:

from llama_index.core import StorageContext, load_index_from_storage
import os

PERSIST_DIR = "./storage"

if not os.path.exists(PERSIST_DIR):
    # Lần đầu: build và lưu vào disk
    documents = SimpleDirectoryReader("./documents").load_data()
    index = VectorStoreIndex.from_documents(documents)
    index.storage_context.persist(persist_dir=PERSIST_DIR)
else:
    # Các lần sau: load từ disk, không cần xử lý lại
    storage_context = StorageContext.from_defaults(persist_dir=PERSIST_DIR)
    index = load_index_from_storage(storage_context)

query_engine = index.as_query_engine()

Bước 5 — Load từ website nội bộ (intranet)

Nếu bạn có wiki nội bộ, tài liệu trên Confluence, hoặc website chỉ truy cập được trong mạng công ty:

from llama_index.readers.web import SimpleWebPageReader
from llama_index.core import SimpleDirectoryReader

# Load từ URL nội bộ — chạy từ trong mạng nội bộ
loader = SimpleWebPageReader(html_to_text=True)
web_docs = loader.load_data(urls=[
    "http://wiki.internal/sop/quy-trinh-xuat-hang",
    "http://wiki.internal/sop/kiem-tra-chat-luong",
])

# Kết hợp với tài liệu file
file_docs = SimpleDirectoryReader("./documents").load_data()
all_docs = web_docs + file_docs

index = VectorStoreIndex.from_documents(all_docs)

Bước 6 — Interface đơn giản để cả team dùng được

# simple_qa.py
import sys
from llama_index.core import StorageContext, load_index_from_storage
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.core import Settings

Settings.llm = Ollama(model="llama3.2", request_timeout=120.0)
Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")

query = " ".join(sys.argv[1:])
if not query:
    print("Dùng: python simple_qa.py <câu hỏi của bạn>")
    sys.exit(1)

storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)
query_engine = index.as_query_engine(similarity_top_k=3)

response = query_engine.query(query)
print(f"\nTrả lời:\n{response}\n")
print("Nguồn:", [n.metadata.get("file_name", "web") for n in response.source_nodes])
python simple_qa.py "Hợp đồng với khách hàng ABC hết hạn ngày nào?"

Phần source_nodes cho biết AI lấy thông tin từ tài liệu nào, trang nào. Đây là điểm mạnh của RAG: bạn verify được ngay nguồn gốc, không phải tin mù vào AI. LLM thuần túy không làm được điều này.

Những điểm hay bị bỏ qua khi đưa vào production

Hai điểm dưới đây hay bị bỏ qua nhất — nhưng lại ảnh hưởng nhiều nhất đến chất lượng kết quả:

Chunk size quyết định độ chính xác. Mặc định LlamaIndex dùng 1024 tokens/chunk. Với tài liệu kỹ thuật dày đặc — manual máy móc, spec sheet, quy trình vận hành — chunk 512 tokens với overlap 50 thường cải thiện độ chính xác rõ rệt:

from llama_index.core.node_parser import SentenceSplitter
from llama_index.core import Settings

Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=50)

Tài liệu scan (PDF ảnh) sẽ không đọc được nếu không có OCR. LlamaIndex chỉ đọc text layer. PDF dạng ảnh scan — phổ biến với hóa đơn, hợp đồng cũ, tài liệu in rồi scan lại — cần qua OCR trước. Tesseract (open source, miễn phí) hoặc Docling của IBM đều xử lý được.

So sánh nhanh ba lựa chọn

Tiêu chí ChatGPT Upload LangChain + Cloud LLM LlamaIndex + Ollama
Bảo mật dữ liệu Gửi lên cloud Query gửi lên cloud 100% local
Chi phí vận hành Subscription Trả theo token Miễn phí (phần cứng)
Độ khó setup Không cần Trung bình Trung bình
Chất lượng trả lời Cao nhất Cao Phụ thuộc model
Hoạt động offline Không Không

Hệ thống mình đang dùng xử lý khoảng 200 file — PDF manual, hợp đồng, SOP nội bộ. Response time với llama3.2 chạy thuần CPU vào khoảng 8-15 giây mỗi câu hỏi. Chậm hơn ChatGPT, nhưng không ai trong team phải lo dữ liệu công ty đang ngồi ở server của ai đó bên ngoài.

Bài toán tài liệu nội bộ xuất hiện ở hầu hết công ty — từ nhà máy cần tra cứu manual máy móc theo ca, đến văn phòng cần lọc điều khoản trong hàng trăm hợp đồng. Nếu bảo mật là ràng buộc không thể thỏa hiệp, LlamaIndex + Ollama là stack đáng thử đầu tiên.

Share: