WEBSITE ĐANG PHÁT TRIỂN

RAG pipeline architecture cho hệ thống chính phủ - những ràng buộc đặc thù

RAG pipeline trên cloud thì nhiều tutorial. Nhưng khi bạn build cho hệ thống chính phủ - không được phép gửi data ra ngoài, network isolated, model phải run on-premise - mọi thứ khác hoàn toàn. Đây là những gì tôi học được từ dự án thực tế.

Vấn đề

Cuối năm 2024, tôi tham gia một dự án AI cho cơ quan nhà nước. Yêu cầu: "Hệ thống hỏi đáp trên văn bản pháp luật - nhân viên có thể hỏi bằng ngôn ngữ tự nhiên, hệ thống trả lời dựa trên văn bản luật."

Nghe như RAG tutorial chuẩn. Rồi họ nói thêm:

  • "Data không được rời khỏi data center của chúng tôi"
  • "Không dùng cloud AI API - không dùng OpenAI, không dùng Claude API"
  • "Network isolated - không có internet access"
  • "Phải audit log mọi query và response"
  • "Model phải có khả năng explain tại sao đưa ra câu trả lời đó"

Đây là lúc tôi biết: Đây không phải tutorial RAG. Đây là enterprise RAG với constraints cực kỳ khắt khe.


Giải thích đơn giản: RAG là gì và tại sao nó phù hợp

RAG (Retrieval-Augmented Generation) là pattern: thay vì "train" LLM với data của bạn (đắt, chậm, không flexible), bạn:

  1. Lưu documents vào vector database
  2. Khi user hỏi, tìm documents liên quan (retrieval)
  3. Đưa documents + câu hỏi vào LLM → LLM trả lời dựa trên context đó

Nó như là: "Này LLM, đây là 5 trang văn bản luật liên quan. Dựa vào đó, trả lời câu hỏi này."

Lợi thế lớn nhất: Data không cần "train vào model". Bạn cập nhật vector database là xong - không cần retrain model.

Nhưng constraint của dự án này làm mọi thứ phức tạp hơn nhiều.


Kiến trúc on-premise RAG pipeline

Lớp 1: Document Processing Pipeline

Văn bản pháp luật (PDF, DOCX)
    ↓
Text Extraction (Apache Tika)
    ↓
Chunking Strategy
    ↓
Embedding Generation (on-premise model)
    ↓
Vector Database (Qdrant on-premise)
public class DocumentProcessor
{
    private readonly IEmbeddingModel _embeddingModel; // On-premise
    private readonly IVectorStore _vectorStore;       // Qdrant local
    private readonly ITextChunker _chunker;

    public async Task ProcessDocumentAsync(LegalDocument document)
    {
        // Extract text
        var rawText = await _textExtractor.ExtractAsync(document.FilePath);

        // Chunk với overlap để không mất context ở ranh giới chunk
        var chunks = _chunker.Chunk(rawText, new ChunkOptions
        {
            MaxTokens = 512,
            OverlapTokens = 50,
            SplitStrategy = SplitStrategy.SemanticBoundary // Ưu tiên split ở ranh giới câu/đoạn
        });

        // Generate embeddings - on-premise model
        foreach (var (chunk, index) in chunks.WithIndex())
        {
            var embedding = await _embeddingModel.GetEmbeddingAsync(chunk.Text);

            await _vectorStore.UpsertAsync(new VectorDocument
            {
                Id = $"{document.Id}_chunk_{index}",
                Vector = embedding,
                Metadata = new Dictionary<string, object>
                {
                    ["documentId"] = document.Id,
                    ["documentName"] = document.Name,
                    ["chunkIndex"] = index,
                    ["text"] = chunk.Text,
                    ["pageNumber"] = chunk.PageNumber,
                    ["effectiveDate"] = document.EffectiveDate
                }
            });
        }
    }
}

Lớp 2: On-premise LLM Setup

Đây là phần khác biệt nhất so với cloud setup.

Model choice: Qwen2.5-7B-Instruct - lý do:

  • Hỗ trợ tiếng Việt tốt
  • 7B parameters - chạy được trên server 4x RTX 3090 (48GB VRAM total)
  • License cho phép commercial use
  • Performance acceptable cho legal Q&A
# docker-compose.yml cho Ollama on-premise
version: '3.8'
services:
  ollama:
    image: ollama/ollama:latest
    volumes:
      - ollama_data:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 4
              capabilities: [gpu]
    environment:
      - OLLAMA_NUM_PARALLEL=4
      - OLLAMA_MAX_LOADED_MODELS=2
public class OnPremiseLLMClient : ILLMClient
{
    private readonly HttpClient _httpClient;
    private readonly string _modelName = "qwen2.5:7b-instruct";

    public async Task<string> CompleteAsync(string prompt)
    {
        var request = new
        {
            model = _modelName,
            prompt = prompt,
            stream = false,
            options = new
            {
                temperature = 0.1,  // Low temperature cho legal context - cần chính xác
                num_ctx = 4096,
                top_p = 0.9
            }
        };

        var response = await _httpClient.PostAsJsonAsync("/api/generate", request);
        var result = await response.Content.ReadFromJsonAsync<OllamaResponse>();

        return result?.Response ?? string.Empty;
    }
}

Lớp 3: Query Pipeline với Audit Logging

public class LegalRAGService
{
    private readonly IVectorStore _vectorStore;
    private readonly ILLMClient _llmClient;
    private readonly IAuditLogger _auditLogger;
    private readonly IEmbeddingModel _embeddingModel;

    public async Task<RAGResponse> QueryAsync(string userId, string userQuery)
    {
        var auditEntry = new AuditEntry
        {
            UserId = userId,
            Query = userQuery,
            Timestamp = DateTime.UtcNow
        };

        try
        {
            // 1. Convert query thành vector
            var queryVector = await _embeddingModel.GetEmbeddingAsync(userQuery);

            // 2. Retrieve relevant chunks
            var relevantChunks = await _vectorStore.SearchAsync(queryVector, topK: 5,
                minScore: 0.7); // Threshold để filter noise

            if (!relevantChunks.Any())
            {
                return new RAGResponse
                {
                    Answer = "Không tìm thấy văn bản pháp luật liên quan đến câu hỏi này.",
                    Sources = Array.Empty<SourceReference>(),
                    Confidence = 0
                };
            }

            // 3. Build prompt với retrieved context
            var context = BuildContext(relevantChunks);
            var prompt = BuildLegalPrompt(userQuery, context);

            // 4. Generate answer
            var answer = await _llmClient.CompleteAsync(prompt);

            // 5. Extract source references for explainability
            var sources = relevantChunks.Select(c => new SourceReference
            {
                DocumentName = c.Metadata["documentName"].ToString(),
                PageNumber = (int)c.Metadata["pageNumber"],
                RelevanceScore = c.Score,
                ExcerptText = c.Metadata["text"].ToString()[..Math.Min(200, c.Metadata["text"].ToString().Length)]
            }).ToList();

            auditEntry.Response = answer;
            auditEntry.Sources = sources;
            auditEntry.Success = true;

            return new RAGResponse { Answer = answer, Sources = sources, Confidence = relevantChunks.Average(c => c.Score) };
        }
        catch (Exception ex)
        {
            auditEntry.Error = ex.Message;
            throw;
        }
        finally
        {
            await _auditLogger.LogAsync(auditEntry); // LUÔN log, dù success hay fail
        }
    }

    private string BuildLegalPrompt(string query, string context)
    {
        return $"""
            Bạn là trợ lý pháp luật. Chỉ trả lời dựa trên văn bản pháp luật được cung cấp.
            Nếu thông tin không có trong văn bản, hãy nói rõ "Tôi không tìm thấy thông tin này trong văn bản được cung cấp."
            KHÔNG suy đoán hoặc thêm thông tin ngoài văn bản.

            VĂN BẢN PHÁP LUẬT THAM KHẢO:
            {context}

            CÂU HỎI: {query}

            TRẢ LỜI:
            """;
    }
}

So sánh: Cloud RAG vs On-premise Government RAG

Khía cạnh Cloud RAG Government On-premise RAG
Model chất lượng GPT-4o, Claude Qwen2.5-7B (lower quality)
Setup complexity Thấp Cao (GPU infra, networking)
Latency ~2-3s ~3-8s (phụ thuộc hardware)
Cost model Pay-per-token CapEx (hardware upfront)
Data security Trust provider Hoàn toàn kiểm soát
Updates Automatic Manual, cần process
Scalability Unlimited Giới hạn bởi hardware

Best practices từ kinh nghiệm thực

1. Chunking strategy là critical. Tôi đã thử 3 strategies:

  • Fixed-size chunking: đơn giản nhưng cắt đứt context pháp luật
  • Sentence-based: tốt hơn nhưng câu luật thường dài
  • Paragraph-based với overlap: tốt nhất cho văn bản pháp luật

2. Hybrid search luôn tốt hơn pure vector search. Kết hợp BM25 + vector search:

// Reciprocal Rank Fusion
var hybridResults = RRFMerge(keywordResults, vectorResults, k: 60);

3. Confidence threshold là quan trọng. Đặt minimum similarity score. Nếu không có chunk nào score đủ cao, trả về "không tìm thấy" thay vì hallucinate.

4. Audit log không thể thiếu. Không chỉ để compliance - còn để debug khi model trả lời sai.


Kết

On-premise RAG cho government systems phức tạp hơn nhiều so với cloud demo. Nhưng khi constraints là absolute (data sovereignty, network isolation), đây là con đường duy nhất.

Bài học lớn nhất: Đừng pick model trước, pick constraints trước. Khi constraints rõ ràng, model và architecture sẽ tự nhiên follow.


Tham khảo

  • Qdrant documentation: qdrant.tech/documentation
  • Ollama on-premise deployment: ollama.ai
  • LangChain RAG patterns: python.langchain.com

/Son Do - believe in basic

#1percentbetter #AIArchitecture #RAG #LLM #Enterprise #OnPremise


Bài viết liên quan

Xem thêm
AI Integration — Góc nhìn Architect

LLM Wiki: Khi AI Không Còn Phải Đọc Codebase Từ Đầu Mỗi Ngày

LLM Wiki là pattern biến codebase thành "living knowledge base" – AI đọc một lần, nhớ mãi, kiến thức tích lũy theo thời gian. Khác với RAG truyền thống, LLM Wiki compound knowledge: mỗi lần hỏi đều thông minh hơn lần trước. Tôi nhớ lần đầu tiên tiếp cận một codebase e-commerce 5 năm tuổi. Hệ thống đang chạy ổn định, team cũ đã nghỉ gần hết, và tôi được giao nhiệm vụ thêm một tính năng tưởng đơn giản: hiển thị trạng thái tồn kho real-time trên trang sản phẩm. Ba ngày sau, tôi vẫn còn ngồi đọc code. Không phải vì code xấu. Mà vì không ai – kể cả những dev còn lại trong team – có thể giải thích được tại sao InventoryService lại gọi thẳng vào một endpoint của OrderService thay vì dùng shared database. Confluence có một trang wiki viết năm 2021 với nội dung "TODO: update this section." File README chỉ có hướng dẫn setup môi trường local. Đó là lần đầu tôi thực sự hiểu cái gọi là tribal knowledge – kiến thức nằm trong đầu người, không phải trong code.

AI Integration — Góc nhìn Architect

Prompt Engineering 2026: Không phải 1 góc nhìn — mà 40 góc nhìn song song

Năm 2026, AI agents đang bùng nổ và nhiều người vội vã tuyên bố "prompt engineering đã chết". Sai hoàn toàn. Chain of Thought, flipping roles — những kỹ thuật cơ bản đó vẫn còn sống, chỉ là giờ chúng ta không chạy 1 luồng nữa, mà chạy song song 40 luồng cùng lúc. Bài này là câu chuyện về sự tiến hóa đó. Hôm rồi tôi ngồi cà phê với một ông bạn — tôi gọi anh là Khoa cho tiện — senior developer 6 năm, đang chuyển sang làm AI engineer ở một startup khá hot trong nước. Anh mở màn bằng một câu khiến tôi suýt đổ cà phê: "Anh ơi, prompt engineering giờ chết rồi. Giờ phải học AI agents mới là đúng hướng." Tôi nhìn anh, hỏi: "Chết theo nghĩa nào?" Anh giải thích: "Thì giờ người ta build hệ thống multi-agent rồi, AI tự lo với nhau, mình chỉ cần thiết kế workflow là xong. Ai còn ngồi viết prompt thủ công nữa?" Tôi im lặng một lúc, rồi hỏi ngược: "Thế mấy cái agent đó nó tự nhiên biết làm việc không? Hay vẫn cần ai đó chỉ cho nó cách nghĩ?" Anh Khoa dừng lại. Ừ nhỉ.