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

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ỉ.

AI Integration — Góc nhìn Architect

Ba cấp độ làm việc với AI: automation, augmentation, và agency - bạn đang ở đâu?

Hầu hết developer đang dùng AI chỉ một cách: giao việc và chờ kết quả. Nhưng có hai cách hiệu quả hơn nhiều - và một trong số đó có thể thay đổi hoàn toàn cách bạn làm việc. Bài này là framework tôi dùng để tự đánh giá mình đang khai thác AI đến đâu. Tuần trước tôi ngồi cà phê với một anh bạn - senior developer 8 năm kinh nghiệm, tôi gọi là anh Hưng cho dễ. Anh vừa xong một sprint khá nặng, và câu đầu tiên mở miệng là: "Mày ơi, giờ tao làm việc với AI nhiều lắm, năng suất tăng rõ rệt." Tôi gật đầu, hỏi tiếp: "Bạn đang dùng theo cách nào?" Anh Hưng giải thích: ChatGPT để viết unit test. GitHub Copilot để gợi ý code. Claude để explain stack trace. Ổn đấy. Tôi hỏi tiếp: "Còn ngoài ra?" Anh im lặng một lúc. "Ngoài ra là... mình hỏi nó trả lời, xong mình copy, sửa lại, done." Câu đó làm tôi nghĩ nhiều. Không phải vì anh Hưng dùng AI sai - mà vì câu đó mô tả chính xác cách phần lớn developer giỏi đang bỏ lỡ hai phần ba giá trị của AI.