№ 29
PolicyMate AI là trợ lý AI tra cứu quy chế nội bộ cho môi trường đại học, giúp tự động truy xuất chính xác từng Điều/Khoản gốc và cảnh báo văn bản hết hiệu lực. Hệ thống còn hỗ trợ rà soát dự thảo, soạn nháp văn bản hành chính và cam kết từ chối trả lời khi không đủ căn cứ.
Trong một trường đại học, khối lượng công việc gắn với văn bản nội bộ lớn hơn nhiều so với hình dung thông thường. Cán bộ hành chính và chuyên viên pháp chế phải tra cứu quy chế đào tạo, quy định coi thi, quy chế nghiên cứu khoa học, quy định chi tiêu nội bộ; phải đối chiếu một dự thảo mới với những văn bản đang có hiệu lực; phải soạn công văn, tờ trình, thông báo có dẫn đủ căn cứ. Đây là công việc lặp lại hằng ngày nhưng không cho phép sai, vì mỗi văn bản ban hành ra đều mang hệ quả hành chính.
Người dùng mục tiêu chính của MVP là cán bộ hành chính, chuyên viên pháp chế và thư ký các phòng ban — nhóm soạn và rà soát văn bản mỗi ngày. Nhóm người dùng thứ hai là giảng viên, khi cần tra quy chế đào tạo hay quy định coi thi, và lãnh đạo đơn vị, khi cần một căn cứ nhanh để ra quyết định. Với cả ba nhóm, vấn đề không nằm ở chỗ "tìm được thông tin" mà ở chỗ tìm đúng điều khoản, đúng văn bản còn hiệu lực, và chứng minh được câu trả lời dựa trên nguồn nào.
Thực tế dữ liệu tại trường khiến việc này tốn kém. Văn bản nội bộ nằm rải rác dưới dạng PDF có text layer, PDF scan, DOCX, ảnh chụp, phân tán trong thư mục chia sẻ và email; mức độ chuẩn hóa thấp và không có chỉ mục dùng chung. Mỗi lần tìm căn cứ cho một quyết định, người dùng mất từ mười lăm đến sáu mươi phút đọc thủ công. Công cụ sẵn có không giúp được nhiều: tìm kiếm full-text bằng Ctrl+F hay Google Drive không hiểu ngữ nghĩa câu hỏi, và quan trọng hơn, nó hoàn toàn không biết văn bản nào đã bị thay thế.
Rủi ro lớn nhất vì vậy không phải chậm mà là sai. Một quyết định ban hành trên căn cứ đã hết hiệu lực sẽ phải thu hồi, và chi phí thu hồi lớn hơn nhiều lần chi phí của một lần tra cứu chậm. Đây cũng là lý do các chatbot LLM thông thường không dùng được cho nghiệp vụ pháp chế: chúng phản hồi nhanh và trôi chảy, nhưng bịa số hiệu văn bản cùng nội dung điều khoản ở dạng khó phát hiện nhất — người đọc chỉ nhận ra khi đã trích dẫn vào văn bản chính thức.
Ngoài tra cứu, hai nhóm việc còn lại cũng tốn thời gian tương tự. Rà soát một dự thảo đòi hỏi đọc toàn văn, phát hiện chỗ thiếu căn cứ, kiểm tra số hiệu được dẫn có đúng hay không, rồi đề xuất chỉnh sửa. Soạn nháp đòi hỏi đúng thể thức, đúng bối cảnh và bám vào quy định nội bộ liên quan.
Từ những điểm nghẽn đó, nhóm xác định PolicyMate AI không nên được định vị như một chatbot pháp lý trả lời tự do, mà là một trợ lý có kiểm soát, thu hẹp vào ba luồng nghiệp vụ: tra cứu quy định nội bộ kèm trích dẫn Điều/Khoản mở được ra nguyên văn; rà soát dự thảo để phát hiện lỗi căn cứ; và soạn nháp văn bản dựa trên nguồn có trong kho. Bên cạnh đó là hai chức năng phụ trợ đã có trong MVP: nhắc việc với văn bản sắp hết hiệu lực, và khu quản trị để nạp văn bản, xem nhật ký, theo dõi chỉ số đánh giá.
Phạm vi MVP được giới hạn rõ. Hệ thống không thay thế chuyên gia pháp chế, không tự ra quyết định cuối cùng, không cam kết bao phủ toàn bộ hệ thống văn bản của trường, và cố tình không trả lời khi kho tài liệu không đủ căn cứ. Corpus pilot hiện là mười văn bản lõi, catalog đã lập danh mục năm mươi mốt văn bản. Con người vẫn giữ vai trò kiểm duyệt: mọi kết quả trích xuất đều đi qua bước xác nhận của người dùng trước khi vào kho, và mọi phát hiện khi rà soát chỉ là đề xuất.
PolicyMate AI được thiết kế như một web app trợ lý pháp chế cho môi trường đại học, gồm bốn lớp trải nghiệm.
Lớp thứ nhất là hỏi đáp và tra cứu. Người dùng đặt câu hỏi bằng ngôn ngữ tự nhiên và nhận lại câu trả lời được phát theo từng token, kèm danh sách nguồn trích dẫn. Điểm khác biệt so với một chatbot thông thường nằm ở chỗ mỗi mã trích dẫn [n] trong câu trả lời là một liên kết mở được: bấm vào đó, người dùng thấy nguyên văn Điều/Khoản được dẫn cùng các điều lân cận, đủ để tự kiểm chứng mà không cần rời
khỏi màn hình. Khi câu trả lời dẫn tới một văn bản đã hết hiệu lực hoặc bị thay thế, hệ thống gắn cảnh báo hiệu lực ngay trên câu trả lời. Khi kho không đủ căn cứ, hệ thống nói thẳng là không đủ căn cứ và giải thích lý do, thay vì suy đoán.
Lớp thứ hai là thư viện văn bản. Dữ liệu được chia theo ba mức hiển thị gồm public, internal và confidential, gắn với vai trò người dùng, nên phạm vi tra cứu luôn nằm trong quyền của người đang đăng nhập. Người dùng xem được danh sách văn bản, đọc nguyên văn theo từng Điều, và tra lịch sử phiên bản của một văn bản khi nó đã được sửa đổi hoặc thay thế.
Lớp thứ ba là công cụ rà soát và soạn thảo. Với rà soát, hệ thống đối chiếu dự thảo với kho quy định và trả về các phát hiện kèm điểm đánh giá, mỗi phát hiện gắn với căn cứ trích dẫn được khi có. Với soạn thảo, hệ thống sinh bản nháp có cấu trúc dựa trên nguồn trong kho, còn người dùng vẫn là người kiểm duyệt cuối cùng. Cả hai luồng đều theo nguyên tắc human-in-the-loop: AI đề xuất, người quyết định.
Lớp thứ tư là khu quản trị. Người quản trị nạp văn bản qua giao diện, xem trước kết quả trích xuất và duyệt từng chunk trước khi đưa vào kho, quản lý người dùng và vai trò, xem nhật ký hoạt động, đọc góp ý của người dùng và theo dõi bảng chỉ số đánh giá chất lượng hệ thống.
Một tính năng nhỏ nhưng đúng nghiệp vụ là nhắc việc: hệ thống tạo nhắc việc cho những văn bản sắp hết hiệu lực, và nhận diện được câu hỏi mang ý định nhắc việc để xử lý riêng thay vì đẩy vào luồng tra cứu. Ngoài ra, khi câu trả lời mô tả một quy trình có nhiều đơn vị và vai trò tham gia, hệ thống tự sinh kèm một sơ đồ luồng để người đọc thấy được ai làm gì, ai phối hợp với ai.
Về nguyên tắc thiết kế trải nghiệm, nhóm ưu tiên sự rõ ràng và khả năng kiểm chứng hơn cảm giác "AI thông minh". Trích dẫn mở được, đoạn trích nguyên văn, cảnh báo hiệu lực, trạng thái xử lý theo từng chặng khi đang trả lời, thông báo lý do khi từ chối — tất cả đều nhằm để người dùng luôn biết hệ thống đang làm gì và câu trả lời đến từ đâu.
Kiến trúc hệ thống tách lớp rõ giữa giao diện, backend nghiệp vụ, cơ sở dữ liệu quan hệ, vector store và các dịch vụ AI, triển khai hoàn toàn trên cloud và mở rộng được theo từng phần.
Frontend dùng Next.js 16 App Router với React 19 và TypeScript, triển khai trên Vercel. Giao diện phủ các luồng chính của sản phẩm gồm tra cứu, thư viện, rà soát, soạn thảo, lịch sử hội thoại, hỗ trợ và khu quản trị. State phía client được chia theo trách nhiệm: TanStack React Query giữ server state và lo phần cache, invalidate cùng việc poll trạng thái job trích xuất; zustand giữ state giao diện cần persist; React Context giữ phiên đăng nhập.
Backend dùng FastAPI trên Python 3.11, triển khai bằng Docker trên Render, đóng vai trò trung gian giữa frontend, cơ sở dữ liệu và các dịch vụ AI. Backend chịu trách nhiệm xác thực, phân quyền, nạp và trích xuất tài liệu, tra cứu, hỏi đáp và điều phối pipeline RAG. Code được tách theo service riêng cho từng mối quan tâm — chunking, trích xuất, guardrail, rerank, chính sách truy xuất, xác minh trích dẫn, kho tri thức, streaming — nên từng phần kiểm thử và thay thế được độc lập.
Cơ sở dữ liệu quan hệ dùng Supabase Postgres với mười bốn bảng nghiệp vụ, bật RLS trên toàn bộ, lưu văn bản, chunk, lịch sử phiên bản, người dùng, vai trò, hội thoại, nhật ký hoạt động, phản hồi và kết quả đánh giá. Migration là SQL thuần đánh theo timestamp, không dùng ORM và không dùng Alembic. Điểm đáng nói về phân quyền là RBAC được ép ở tầng cơ sở dữ liệu bằng hàm has_role chạy SECURITY DEFINER và được chính RLS policy gọi, nên quyền không phụ thuộc vào việc frontend có gửi đúng tham số hay không.
Vector store dùng Qdrant Cloud với collection policy_chunks, 768 chiều, độ đo cosine. Embedding dùng gemini-embedding-001. Một quyết định kiến trúc quan trọng là Qdrant chỉ giữ vector và lo phần ANN, còn Postgres là nguồn sự thật cho text và metadata; lớp truy cập kho được viết dưới dạng Protocol với ba hiện thực — Qdrant cho production, Supabase, và SQLite cho test hoặc chạy offline — nên đổi backend lưu trữ không phải sửa nghiệp vụ.
Tầng truy xuất đi theo hướng hybrid search. Hệ thống chạy song song một nhánh dense theo ANN và một nhánh lexical full-text trên payload của Qdrant, hợp nhất bằng Reciprocal Rank Fusion theo công thức 1/(60 + rank), cộng thêm bias theo tình trạng hiệu lực và boost khi câu hỏi khớp số hiệu Điều, rồi rerank bằng Jina jina-reranker-v2-base-multilingual để lấy top-k. Lý do chọn hybrid nằm ở đặc thù
tra cứu văn bản pháp quy: người dùng khi thì hỏi bằng ngôn ngữ tự nhiên, khi thì nhớ chính xác số hiệu hoặc "Điều 5" — vector đơn thuần trượt ở nhóm thứ hai.
Pipeline RAG được điều phối bằng LangGraph, cố tình không làm theo dạng ReAct với vòng lặp tool-calling mà đi thẳng và rẽ nhánh một lần: retrieve là node vào, nếu có căn cứ thì sang generate rồi verify, nếu không thì sang no_results. Trước khi vào graph, câu hỏi đi qua guardrail lọc prompt injection và bộ nhận diện ý định nhắc việc. Đi kèm là ba lớp kiểm soát an toàn nằm quanh graph, được mô tả ở phần 4.3. Ngoài luồng này còn một luồng streaming riêng phục vụ giao diện chính, phát SSE theo thứ tự chặng understanding, searching, citations, writing, token, verifying, done.
LLM dùng Google Gemini flash, có dự phòng tự động sang OpenRouter khi provider chính lỗi. Cả LLM và embedding đều nằm sau một lớp provider thay thế được gồm google, openrouter, gateway, openai và local, nên đổi nhà cung cấp là đổi biến môi trường chứ không phải sửa code. OCR cho PDF scan và ảnh dùng Gemini vision; nhóm đã bỏ hướng fallback sang Tesseract sau khi thử vì chất lượng trên văn bản
hành chính tiếng Việt quá thấp để tin được.
Hai luồng rà soát và soạn thảo được đặt ở Supabase Edge Functions viết bằng Deno thay vì trong FastAPI. Mỗi function tự kiểm JWT và tự đọc bảng vai trò để suy ra phạm vi được phép, tức không tin frontend; khi cần ngữ cảnh, function gọi API tra cứu của FastAPI, và nếu không có thì fallback sang truy vấn lexical ngay trong Postgres.
Về triển khai và vận hành, frontend ở Vercel, backend ở Render region Singapore với health check riêng, Postgres cùng Auth, Storage và Edge Functions ở Supabase Cloud, vector ở Qdrant Cloud. CI chạy trên GitHub Actions với ruff và pytest mỗi lần push vào main hoặc develop. Quan sát chất lượng AI dựa trên LangSmith, cùng một lớp log tự động ghi lại prompt và tool call của quá trình phát triển.
Khó khăn đầu tiên là văn bản pháp quy không giống tài liệu văn xuôi. Nội dung được tổ chức theo Chương, Mục, Điều, Khoản, Điểm, và các đơn vị này tham chiếu lẫn nhau. Nếu cắt theo số ký tự như mặc định của phần lớn pipeline RAG, chunk sẽ đứt giữa một khoản, mất phần định danh Điều ở đầu, và khi trích dẫn thì không chỉ được ra đơn vị pháp lý nào đang được dẫn — đúng thứ mà người dùng pháp chế cần nhất.
Hướng giải quyết là chunk theo cấu trúc pháp lý bằng một bộ regex tiếng Việt, cắt theo Điều và Khoản thay vì theo độ dài, và gắn cho mỗi chunk metadata gồm nhãn Điều/Khoản, số hiệu văn bản, trang bắt đầu và kết thúc, mức hiển thị cùng toàn bộ trường hiệu lực. ID của document và chunk sinh bằng uuid5 trên đường dẫn, nhờ vậy reindex là thao tác idempotent và rollback vẫn tham chiếu đúng.
Thực tế văn bản hành chính buộc phải xử lý thêm nhiều trường hợp lệch chuẩn mà bản thiết kế ban đầu không lường tới: phần mục lục ở đầu văn bản trông y như danh sách các Điều nên dễ bị nhận nhầm thành nội dung; số Chương viết bằng số La Mã; nhan đề Điều nằm vắt qua hai dòng; đường kẻ trang trí bị đọc thành một dòng nội dung; công thức tính toán bị trải phẳng sai; và bảng biểu bị cắt ngang giữa một hàng. Mỗi trường hợp này đều được xử lý riêng trong module chunking, và khi một khoản dài vượt ngưỡng thì hệ thống mới cắt cửa sổ có phần chồng lấp, tức coi cắt theo độ dài là phương án dự phòng chứ không phải mặc định. Riêng phần chunking hiện có bốn mươi bảy test tự động.
Về dữ liệu nội bộ, nhóm chọn hướng để tổ chức tự nạp tài liệu qua giao diện quản trị, thay vì cố thu thập trước toàn bộ kho văn bản của trường. Đây là quyết định làm sản phẩm khả thi trong giai đoạn đầu, vì tài liệu nội bộ không công khai, không đồng nhất về định dạng và có yêu cầu bảo mật.
Trong quá trình đánh giá, nhóm phát hiện một đặc điểm quan trọng của bộ số truy xuất: Recall@3 bằng đúng Recall@5 ở mọi tier câu hỏi. Nghĩa là cái gì tìm được thì đã nằm trong top-3, còn mở rộng lên top-5 không cứu thêm case nào. Đây là dấu hiệu vấn đề còn lại không nằm ở xếp hạng mà nằm ở truy xuất: chunk đúng không vào được danh sách ứng viên ngay từ đầu.
Hướng xử lý là bổ sung nhánh lexical bên cạnh nhánh vector và hợp nhất bằng RRF, cộng thêm cơ chế boost khi câu hỏi khớp số hiệu Điều. Cách này bám đúng hành vi tra cứu thật: có lúc người dùng nhớ số hiệu văn bản, có lúc chỉ nhớ nội dung, có lúc cần đối chiếu nhiều văn bản cùng lúc.
Vẫn còn một case trượt đã định vị được nguyên nhân. Với một câu hỏi nêu rõ số hiệu, hệ thống tìm đúng cả văn bản liên quan nhưng chọn sai đơn vị: nó lấy "Điều 1" của văn bản sửa đổi thay vì "Điều 1" của nghị quyết được sửa, do nhan đề Điều 1 của văn bản sửa đổi chứa nguyên tên văn bản kia nên trùng từ khóa cao hơn chính văn bản đích. Hướng sửa đã ghi vào danh sách nợ: ưu tiên văn bản đích khi câu hỏi nêu số hiệu, và hạ trọng số phần nhan đề Điều có chứa tên một văn bản khác.
Đây là bài toán trung tâm của dự án, và cũng là chỗ nhóm học được nhiều nhất. Ở lần chạy đánh giá hành vi đầu tiên, hệ thống đạt 67.5% điểm tổng — con số trông tạm ổn — nhưng Refusal Accuracy là 0/9. Nghĩa là với chín câu hỏi mà đáp án đúng phải là từ chối, hệ thống trả lời cả chín. Nó luôn cố trả lời, kể cả khi câu hỏi nêu một số hiệu văn bản không tồn tại trong kho. Bài học rút ra là một pipeline RAG "chạy được" hoàn toàn có thể sai một trăm phần trăm ở đúng nhóm câu hỏi nguy hiểm nhất mà mọi metric trung bình vẫn không phát hiện ra.
Hướng giải quyết là dựng bốn cơ chế độc lập chứ không chỉ chỉnh prompt. Thứ nhất là chính sách bỏ trả lời: một module riêng chạy ngay trong node retrieve, loại chunk ngoài phạm vi, bắt hệ thống abstain khi độ trùng từ khóa quá yếu, và abstain khi câu hỏi nêu một số hiệu không có trong kho hoặc mang ý định ngoài phạm vi — khi đó luồng rẽ sang no_results thay vì trả bừa. Thứ hai là bắt buộc trích dẫn: node verify bóc từng mã [n] khỏi câu trả lời và đối chiếu với các chunk thật, và nếu không còn trích dẫn hợp lệ nào thì câu trả lời của LLM bị thay hoàn toàn bằng bản trích nguyên văn các chunk tìm được — hệ thống chấp nhận trả lời khô khan để không bịa. Thứ ba là xác minh từng luận điểm: chỉ có mặt mã [n] chưa đủ, nên một module riêng soi từng câu và yêu cầu mọi số hiệu, năm ban hành, mốc số liệu phải xuất hiện trong chunk được dẫn, không khớp thì gắn cảnh báo. Thứ tư là kiểm tra tiền đề: khi câu hỏi tự nêu một tình trạng không thuộc diện áp dụng, gộp sai phạm vi hoặc giả định một quy định không tồn tại, LLM đặt một marker ở đầu câu trả lời, backend bóc marker ra và chuyển hệ thống sang chế độ nói rõ điều kiện áp dụng, thay vì trả lời như thể người hỏi đã đủ điều kiện.
Sau khi thêm chính sách abstain và áp dụng cho cả luồng thường lẫn luồng streaming, Refusal Accuracy đi từ 0/9 lên 9/9, điểm tổng lên 97.5%. Điều đáng nói là cả năm lần chạy trong cùng một ngày đều được lưu lại, nên có thể chỉ ra chính xác thay đổi nào tạo ra bước nhảy nào chứ không chỉ báo cáo con số cuối.
Một câu trả lời đúng chữ nhưng sai hiệu lực còn nguy hiểm hơn không trả lời. Bài toán kỹ thuật ở đây có một nghịch lý: nếu hệ thống dìm các chunk thuộc văn bản đã hết hiệu lực xuống để tránh trả nội dung cũ, thì khi người dùng hỏi thẳng về văn bản cũ đó, hệ thống sẽ im lặng trả bản mới mà không nói gì — tức mất luôn khả năng cảnh báo.
Hướng giải quyết là cơ chế bias theo hiệu lực: khi truy vấn nêu số hiệu của một văn bản đã hết hiệu lực, chunk của nó không bị dìm, nhờ vậy hệ thống còn căn cứ để phát cảnh báo thay vì thay thầm. Kết hợp với metadata hiệu lực gắn trên từng chunk, hệ thống sinh cảnh báo có cấu trúc dạng nêu rõ số hiệu và phiên bản đã hết hiệu lực. Cùng cơ chế này còn đỡ thêm chức năng nhắc việc với văn bản sắp hết hạn, và tra cứu lịch sử phiên bản kèm khả năng rollback.
Vẫn còn hai khoảng trống đã xác định. Thứ nhất, hệ thống chưa cảnh báo cho trường hợp văn bản được trích vẫn còn hiệu lực nhưng một văn bản liên quan của nó đã bị thay thế. Thứ hai, cảnh báo hiện được sinh cùng lượt với câu trả lời, nên khi LLM không khả dụng và hệ thống rơi sang nhánh trích xuất nguyên văn thì cảnh báo biến mất — lỗ hổng này chỉ lộ ra đúng lúc người dùng ít có khả năng tự kiểm chứng nhất, nên nhóm xếp nó ở vị trí ưu tiên số một trong danh sách nợ. Chính vì vậy, cổng kiểm tra trước mỗi lần demo được đặt thêm điều kiện: nếu có case nào rơi vào nhánh trích xuất thì số đo cảnh báo của lần chạy đó không được dùng để báo cáo.
Trích dẫn không phải chi tiết trang trí mà là cơ chế tạo niềm tin, nên nhóm xử lý nó như một phần của kiến trúc chứ không phải của giao diện. Yêu cầu đặt ra là người dùng phải mở được nguyên văn từ chính câu trả lời, và đoạn nguyên văn đó phải khớp với nhãn nguồn đang hiển thị.
Để làm được, backend có endpoint riêng trả về nguyên văn của một chunk kèm các điều lân cận, và một endpoint khác trả nguyên văn theo Điều của cả văn bản; frontend có lớp fallback khi endpoint chính không trả được. Nhờ chunk đã cắt theo Điều/Khoản, nhãn nguồn hiển thị được đúng đơn vị pháp lý thay vì chỉ ghi tên file và số trang. Câu trả lời chỉ được coi là grounded khi còn ít nhất một trích dẫn hợp lệ sau bước xác minh, và trạng thái này được trả về cho frontend để hiển thị trung thực chứ không ẩn đi.
Phần lớn văn bản nội bộ đến dưới dạng PDF scan hoặc ảnh chụp, nên OCR là điều kiện bắt buộc chứ không phải tính năng thêm. Nhóm đã thử hướng fallback bằng Tesseract nhưng bỏ, vì chất lượng trên văn bản hành chính tiếng Việt quá thấp để dựa vào; hướng chốt lại là dùng pypdf khi PDF có text layer và chỉ gọi Gemini vision khi phần text rỗng.
Việc này lập tức tạo ra một bài toán hạ tầng. Render ở gói miễn phí cắt request ở khoảng ba mươi giây, trong khi OCR một PDF scan nhiều trang luôn vượt ngưỡng đó, nên endpoint trích xuất đồng bộ ban đầu trả lỗi timeout với đúng loại tài liệu quan trọng nhất. Hướng giải quyết là chuyển sang mô hình job chạy nền: endpoint nhận file, ghi vào thư mục job, tạo record trạng thái rồi trả về mã job trong dưới một giây; một worker chạy nền thực hiện OCR và cập nhật tiến độ theo số trang; frontend poll cho tới khi job hoàn thành mới lấy kết quả để đưa vào luồng duyệt. Trạng thái job được lưu ở một SQLite cục bộ chứ không đưa vào Postgres, vì đây là state tạm của tiến trình, có thời hạn sống và bị dọn định kỳ. Việc trích xuất nhẹ như PDF có text layer và DOCX thì làm luôn ở phía client để không tốn băng thông cho những trường hợp chỉ cần lấy text.
Một giới hạn hạ tầng nữa là gói miễn phí của Render cho dyno ngủ sau mười lăm phút, cold start khoảng năm mươi giây. Hệ thống có một endpoint ping nhẹ để giữ dyno tỉnh, và quy trình demo có bước đánh thức API trước năm phút cùng bản video dự phòng.
Vì sản phẩm phục vụ dữ liệu nội bộ, phân quyền được ép ở nhiều tầng thay vì chỉ kiểm ở frontend. Vai trò trong MVP giữ đơn giản gồm staff và admin; staff thấy được mức public và internal, admin thêm confidential. Phạm vi người dùng chọn được lấy giao với quyền của vai trò và không bao giờ nới rộng. Một quyết định đáng nói là mọi lỗi khi tra vai trò — trả rỗng, lỗi 5xx, timeout, mạng chết — đều degrade về staff chứ không bao giờ về admin, tức fail-closed. Ở tầng dữ liệu, RLS bật trên mọi bảng nghiệp vụ và policy gọi hàm kiểm vai trò ngay trong Postgres. Ngoài ra, chunk mức confidential không bao giờ xuất hiện trong kết quả tra cứu và hỏi đáp, kể cả với admin, vì tham số phạm vi chỉ ánh xạ tới public và internal.
Về đầu vào, mọi request được validate bằng Pydantic với giới hạn cụ thể cho độ dài câu hỏi, số lượng kết quả và số message lịch sử; upload giới hạn định dạng và dung lượng, trả mã lỗi riêng cho từng trường hợp sai định dạng, quá cỡ hoặc file rỗng. Guardrail chống prompt injection chạy trước khi câu hỏi tới LLM, có mẫu cho cả tiếng Việt và tiếng Anh, và khi phát hiện thì vẫn trả cảnh báo cho người dùng thay vì âm thầm bỏ qua. CORS dùng allowlist.
Hai lỗ hổng còn lại được nhóm ghi rõ trong tài liệu kiến trúc thay vì để ngầm. Thứ nhất, cơ chế chặn admin hiện chỉ có hiệu lực khi biến môi trường đặt ở chế độ production, nên tuyệt đối không được chạy production ở chế độ development; hành vi này còn được đóng đinh bằng một test để bất kỳ ai muốn đổi cũng buộc phải sửa test và nhìn thấy nó. Thứ hai, hệ thống chưa có rate limiting, nghĩa là endpoint streaming đang mở với chi phí LLM không có chặn trên. Cả hai nằm trong danh mục production hardening ở phần 6.
Độ trễ trung bình của một câu trả lời đầy đủ vào khoảng ba mươi giây, chủ yếu do chuỗi embed, truy xuất hai nhánh, rerank rồi sinh. Con số này quá lâu để bắt người dùng chờ một màn hình trắng, nên giao diện chính buộc phải dùng streaming, phát theo từng chặng để người dùng thấy hệ thống đang tìm gì và đang viết gì.
Sơ đồ quy trình được xử lý theo hướng không thêm hạ tầng: thay vì gọi một model riêng để sinh sơ đồ, prompt yêu cầu LLM đính kèm một block JSON ở cuối câu trả lời khi nội dung mô tả quy trình có nhiều đơn vị tham gia; backend bóc block đó ra khỏi văn bản trước khi hiển thị, parse thành danh sách node và edge rồi trả về cho frontend vẽ. Ở luồng streaming, khi phát hiện dấu hiệu bắt đầu của block này trong dòng token, backend ngừng phát ra giao diện và tích lũy phần còn lại để parse ở cuối, nên người dùng không bao giờ thấy JSON thô chạy qua màn hình.
Cái giá của việc có hai luồng là hậu xử lý bị lặp. Prompt đã được tách ra một module dùng chung nên sửa prompt chỉ còn một chỗ, nhưng phần bóc block sơ đồ, phần sinh câu trả lời trích xuất nguyên văn, phần xử lý marker tiền đề và phần xác minh trích dẫn hiện vẫn tồn tại hai bản giữa luồng LangGraph và luồng streaming. Nhóm ghi nhận đây là nợ kỹ thuật cần trả, vì mọi sửa đổi về an toàn đều phải nhớ áp dụng cho cả hai chỗ — đúng dạng lỗi dễ bỏ sót nhất.
Về mặt kỹ thuật, sản phẩm khả thi vì mọi thành phần cốt lõi đều dựng được bằng công nghệ hiện có: web app, API backend, Postgres, vector store, embedding, hybrid retrieval và LLM. Hệ thống đã được chia thành các module rõ ràng, mỗi module có test riêng, nên phát triển và thay thế từng phần không phải viết lại toàn bộ. Quan trọng hơn, sản phẩm không dừng ở notebook hay demo localhost: nó đang chạy end-to-end trên hạ tầng thật với API
Hiệu quả kỹ thuật đã được đo bằng một bộ đánh giá tái lập được, gồm năm bộ eval và toàn bộ script tái lập nằm trong repo. Về truy xuất, trên 31 case có gán nhãn, Article Recall@3 đạt 96.8% so với mục tiêu 85%, Doc Recall@3 đạt 100%, MRR đạt 0.8817, không có lần chạy nào trả rỗng và không có lỗi. Về hành vi, trên 40 case, Behavior Accuracy tổng đạt 97.5%, trong đó Refusal Accuracy đạt 100% trên 9 case và Answer Accuracy đạt 100% trên 25 case; cổng phát hành được chọn là Refusal + Warning Accuracy và đạt 93.3% so với ngưỡng 90%. Về chất lượng sinh, RAGAS cho faithfulness 0.933 so với ngưỡng 0.85 và context precision 0.956 so với ngưỡng 0.70, không có case nào rơi xuống dưới 0.70 faithfulness. Về luồng rà soát, trên 3 case, hệ thống bắt đúng cả ba loại lỗi kỳ vọng, kể cả case mà đáp án đúng là "không có phát
hiện nào". Về kỹ thuật, 305 test backend và 60 case frontend đều pass, ruff sạch.
Một điểm nhóm muốn nhấn là cách chọn cổng phát hành. Cổng go/no-go không đặt ở điểm tổng và cũng không đặt ở điểm RAGAS, mà đặt ở nhóm câu hỏi cần từ chối hoặc cần cảnh báo, vì rủi ro lớn nhất của sản phẩm không phải trả lời kém mà là trả lời trôi chảy dựa trên căn cứ không tồn tại hoặc đã hết hiệu lực. Bằng chứng cho lựa chọn này chính là tiến trình năm lần chạy trong cùng một ngày: chỉ số cổng đi từ 13.3% ở baseline lên 33.3%, 60%, rồi 93.3%, và mỗi bước nhảy gắn với một thay đổi cụ thể trong code chứ không phải một lần chỉnh prompt may mắn.
Về mặt dữ liệu, MVP không phụ thuộc vào việc phải sở hữu trước toàn bộ kho văn bản của một trường. Sản phẩm bắt đầu từ corpus pilot mười văn bản lõi đã index và catalog năm mươi mốt văn bản đã lập danh mục, rồi cho tổ chức tự nạp thêm qua giao diện quản trị với đầy đủ bước duyệt của con người. Hướng này khả thi hơn nhiều so với việc cố thu thập trước, vì tài liệu nội bộ không công khai, định dạng không đồng nhất và có yêu cầu bảo mật riêng.
Về mặt nghiệp vụ, sản phẩm chọn đúng phạm vi hỗ trợ thay vì thay thế con người. Quyết định pháp chế cuối cùng vẫn thuộc về cán bộ; AI đóng vai trò tăng tốc tra cứu, chỉ ra rủi ro hiệu lực, chuẩn bị bản nháp và minh bạch hóa căn cứ. Cách tiếp cận này phù hợp với mức độ tin cậy hiện tại của LLM và giảm rủi ro cho tổ chức sử dụng.
Về mặt triển khai, hệ thống vận hành được với chi phí thấp ở giai đoạn MVP nhờ dùng các dịch vụ cloud phổ biến ở gói miễn phí hoặc gói thấp, và nhờ kiến trúc tách phần: frontend, backend, cơ sở dữ liệu và vector store scale được độc lập khi nhu cầu tăng. Lớp provider thay thế được cho LLM và embedding cũng là một yếu tố khả thi về chi phí, vì nó cho phép đổi nhà cung cấp theo giá mà không phải sửa code.
Tuy vậy sản phẩm còn bốn rủi ro cần tiếp tục xử lý, và nhóm ghi rõ thay vì che. Rủi ro thứ nhất là cỡ mẫu đánh giá: 31 đến 40 case trên một dataset nội bộ, nên mọi tỷ lệ phần trăm ở trên nên được đọc như chỉ số phát hiện hồi quy, không phải KPI công bố — một case đổi trạng thái đã làm con số nhảy hai đến ba điểm. Rủi ro thứ hai là chất lượng OCR chưa được kiểm chứng: ba trong mười văn bản của corpus pilot hiện là bản mô phỏng thay cho PDF scan thật, nên kết quả ở nhóm câu hỏi về hiệu lực chỉ phản ánh cơ chế của pipeline chứ không nói được gì về chất lượng trích xuất trên tài liệu scan thực tế. Rủi ro thứ ba là chỉ số answer_relevancy của RAGAS chỉ đạt 0.291 và trượt ngưỡng; nguyên nhân đã phân tích và không phải dấu hiệu bịa đặt, vì thước đo này chấm bằng cách sinh ngược câu hỏi từ câu trả lời, trong khi câu trả lời của
PolicyMate luôn kèm phạm vi áp dụng, điều kiện loại trừ và cảnh báo hiệu lực — điểm thấp phân bố đều khắp mọi tier trong khoảng 0.240 đến 0.379, và tier thấp nhất chính là nhóm có nhiều cảnh báo hiệu lực nhất, tức thước đo đang trừng phạt đúng hành vi mà sản phẩm được thiết kế để có. Rủi ro thứ tư là mức độ sẵn sàng vận hành: hệ thống chưa có rate limiting, cơ chế chặn admin chỉ có hiệu lực ở chế độ production,
chưa đo coverage và chưa có kiểm thử end-to-end.
Ngoài bốn rủi ro trên còn hai khoảng trống về dữ liệu thực tế: chỉ tiêu giảm thời gian tra cứu chưa đo được vì thiếu baseline, và hệ thống chưa có phản hồi của người dùng thật — cơ chế thu phản hồi trên từng câu trả lời cùng form góp ý và trang quản trị đã chạy được, chỉ chưa có người dùng để thu. Những rủi ro và khoảng trống này không làm sản phẩm mất tính khả thi, nhưng chúng là nội dung chính của roadmap.
Giai đoạn tiếp theo được chia thành năm hướng, và thứ tự ưu tiên được đặt theo mức độ rủi ro chứ không theo độ dễ làm.
Hướng thứ nhất là hoàn thiện lớp cảnh báo hiệu lực, vì đây là phần rủi ro nhất còn lại. Việc đầu tiên là sinh cảnh báo trực tiếp từ metadata của chunk, độc lập với LLM, để cảnh báo vẫn hoạt động đúng lúc LLM hỏng — hiện nay khi hệ thống rơi sang nhánh trích xuất nguyên văn thì cảnh báo mất, và đó là thời điểm người dùng ít có khả năng tự kiểm chứng nhất. Việc thứ hai là bổ sung cảnh báo cho quan hệ họ hàng giữa các văn bản, tức trường hợp văn bản được trích còn hiệu lực nhưng một văn bản liên quan của nó đã bị thay thế.
Hướng thứ hai là nâng cấp truy xuất. Trước mắt là sửa case trượt đã định vị được nguyên nhân bằng cách ưu tiên văn bản đích khi câu hỏi nêu rõ số hiệu và hạ trọng số phần nhan đề Điều chứa tên văn bản khác. Xa hơn là bổ sung khả năng lần theo tham chiếu chéo giữa các điều khoản, và mở rộng lớp lọc theo thời điểm để ưu tiên văn bản còn hiệu lực tại một mốc thời gian cụ thể — điều này cần thiết khi người dùng phải tra căn cứ áp dụng cho một hồ sơ trong quá khứ, không phải cho hiện tại.
Hướng thứ ba là cải thiện cách trình bày câu trả lời. Vấn đề đã được chỉ ra bởi chính bộ đo: câu trả lời nói đúng nhưng nói nhiều hơn điều được hỏi. Hướng xử lý là tách phần trả lời trực tiếp khỏi phần bối cảnh áp dụng, trong cả prompt và giao diện, để người đọc thấy ngay câu trả lời cốt lõi rồi mới tới điều kiện và cảnh báo. Nhóm ghi rõ nguyên tắc: sửa để người đọc dễ đọc hơn, không cắt bớt nội dung chỉ để kéo điểm một thước đo lên.
Hướng thứ tư là hoàn thiện hai luồng rà soát và soạn thảo, vì đây là phần gắn trực tiếp với workflow pháp chế và hiện còn mỏng nhất so với luồng tra cứu. Với rà soát, cần cho phép highlight trực tiếp trên văn bản, phân loại mức độ rủi ro chi tiết hơn, cho người dùng thêm nhận xét, kiểm lại sau khi chỉnh sửa và xuất báo cáo rà soát; đồng thời xử lý trường hợp phát hiện được lỗi nhưng không dẫn được căn cứ, chẳng hạn khi dự thảo thiếu hẳn phần căn cứ nên không có gì trong kho để neo vào. Với soạn thảo, cần thư viện mẫu văn bản theo loại nghiệp vụ, gợi ý căn cứ pháp lý cho từng phần, lưu lịch sử bản nháp và so sánh các phiên bản.
Hướng thứ năm là production hardening và kiểm chứng bằng người dùng thật. Về kỹ thuật, cần thêm rate limiting, ép cơ chế chặn admin ở mọi môi trường, đưa test frontend và eval vào CI, đo coverage, bổ sung kiểm thử end-to-end và audit accessibility. Về dữ liệu, cần thay ba văn bản mô phỏng bằng scan thật rồi chạy lại toàn bộ eval, vì chỉ khi đó mới nói được điều gì có căn cứ về OCR; mở rộng kho từ mười văn bản pilot lên toàn bộ catalog năm mươi mốt văn bản; và quan trọng nhất là đưa ba đến năm chuyên viên vào dùng thật trong hai tuần để lấy baseline thời gian tra cứu cùng phản hồi thực tế — đây là hai thứ nhóm không thể tự tạo ra trong môi trường thử nghiệm.
Về dài hạn, PolicyMate AI có thể trở thành nền tảng trợ lý pháp chế dùng chung cho khối trường đại học, nơi mỗi trường có kho văn bản nội bộ riêng và phân quyền riêng nhưng dùng chung nền tảng tra cứu, rà soát và soạn thảo. Hướng này khả thi vì hai lớp lõi của hệ thống — chunk theo cấu trúc Điều/Khoản và cơ chế theo dõi hiệu lực văn bản — không gắn với một trường cụ thể nào; phần phải làm lại cho mỗi trường chỉ là nạp kho văn bản và cấu hình vai trò. Lợi thế của sản phẩm không nằm ở việc "có chatbot AI", mà nằm ở chỗ mỗi câu trả lời đều mở được ra đúng Điều, đúng Khoản của văn bản gốc, tự cảnh báo khi căn cứ đã hết hiệu lực, và dám nói không đủ căn cứ thay vì suy đoán.