Trước Khi Xây Agentic RAG: Nắm Cách Ra Quyết Định và Chọn Phương Pháp Parse PDF
Bài viết phân tích cách xây dựng một bộ điều phối (dispatcher) thay vì để LLM tự quyết định trong pipeline RAG doanh nghiệp. Quy trình gồm bốn bước: xác định bản chất tài liệu, lập kế hoạch, thực thi và tổng hợp kết quả từ nhiều phương pháp parsing như fitz, Docling, EasyOCR, MinerU hay Surya. Đây là bài viết giúp lập trình viên hiểu rõ ranh giới giữa rule-based dispatcher và agentic thực thụ, đồng thời có cơ sở để chọn đúng công cụ cho từng loại PDF.

Trong giới AI engineering, cụm từ agentic AI đang được nhắc đến ở khắp nơi với nguyên tắc đơn giản: “hãy để model tự quyết định”. Cách tiếp cận này phù hợp với trợ lý đa năng, nhưng lại tiềm ẩn rủi ro lớn khi áp dụng vào hệ thống RAG doanh nghiệp — nơi câu trả lời ảnh hưởng trực tiếp đến quyết định kinh doanh. Bài viết này sẽ phân tích một bước quan trọng thường bị “mỹ miều hóa” thành agentic: chọn phương pháp parsing PDF phù hợp cho từng loại tài liệu.
Thay vì để LLM tự chọn, chúng ta sẽ xây dựng một dispatcher (bộ điều phối) có thể kiểm soát hoàn toàn: nó đọc bản chất của file PDF, lên kế hoạch các phương pháp phù hợp, thực thi theo thứ tự, rồi tổng hợp mọi đầu ra thành một corpus thống nhất. Mọi quyết định đều tường minh, có log và có thể kiểm tra trước khi chạy.
Sơ đồ quy trình nature-plan-execute-synthesize của dispatcher
Vì sao cần thận trọng với từ “agentic”
Hầu hết các nhà cung cấp RAG hiện nay đều gọi vòng lặp parsing tài liệu của họ là agentic. Nhưng mở code ra, bạn sẽ thấy gần như cùng một mô-típ: một bộ điều phối dạng luật (rule-based) đọc vài tín hiệu từ file, chọn một chuỗi phương pháp có thứ tự, chạy tuần tự rồi gộp kết quả. LLM chỉ xuất hiện ở các nhánh nhỏ bên trong, như vòng lặp kiểm tra heading, vision reader trên biểu đồ, hay bước hậu xử lý OCR.
Không có LLM nào ở tầng điều phối quyết định bước tiếp theo. Không có vòng phản hồi kiểu agent quan sát đầu ra rồi lập kế hoạch lại. Vì vậy, gọi nó là agentic là một cách nói “kéo dãn” khái niệm. Bài viết dùng từ này trong dấu ngoặc kép, và xây dựng phiên bản trung thực nhất của mô hình mà thị trường vẫn gọi là agentic hiện nay: rule-based routing + LLM ở các nhánh lá.
Cụ thể hơn:
detect_document_nature(): sáu cờ xác định từ line_df/span_df nhưis_scanned,has_native_outline,has_sommaire,is_composite,has_rich_figures,has_tables_signal. Docstring ghi rõ: “deterministic; no LLM”.plan_parsing_methods(): thuần Python if/elif dựa trên nhãn nature. Mỗi nhánh trả về một danh sáchMethodStepđược hard-code.parse_pdf_agentic(): vòng lặp qua kế hoạch và gọi adapter cho từng phương pháp. Dispatcher không gọi LLM.synthesize_parsing_outputs(): gộp DataFrame bằng heuristic_pick_richer. Không có LLM.
Một agentic thực thụ sẽ cần thêm: LLM đọc đầu ra của mỗi phương pháp, quyết định corpus đã đủ chưa, có nên chạy lại với tham số khác không, hoặc thêm/bớt phương pháp trong lúc chạy. Đó là nội dung của Volume 3 trong series. Còn bài này dừng lại ở một bước trước đó — nhưng đã đủ để “khóa” brick đầu tiên bằng một hàm parse_pdf_agentic(path) duy nhất.
Bản chất: Đọc, lập kế hoạch, thực thi và tổng hợp
Vòng lặp gồm bốn giai đoạn. Mỗi giai đoạn đều xác định, rẻ và có thể kiểm tra được. Quyết định chạy phương pháp nào không phải là quyết định của LLM. LLM chỉ xuất hiện bên trong từng phương pháp theo hợp đồng riêng (schema cố định, callable được tiêm vào, JSON được cache) — không bao giờ ở tầng điều phối.
1. Nature: đọc nhanh bản chất tài liệu
Bước đầu tiên đọc các tín hiệu giá rẻ từ file PDF: probe file, xây line_df và span_df, không dùng LLM. Sáu cờ được suy ra:
is_scanned: line_df trống hoặc số dòng quá ít so với số trang → cần OCR.has_native_outline:doc.get_toc()trả về danh sách không rỗng.has_sommaire: trang đầu có 5 dòng dot-leader trở lên (kiểuTitle ....... 12).is_composite: phát hiện ranh giới tài liệu do đánh số lại, phá vỡ style hoặc trang bìa.has_rich_figures: mật độ ảnh cao hơn mức trung bình của tài liệu văn xuôi.has_tables_signal: bộ dò grid đơn giản tìm thấy hàng có từ 3 trường ngắn trở lên.
2. Plan: từ bản chất đến danh sách phương pháp
plan_parsing_methods() đọc nature và trả về danh sách MethodStep, mỗi bước gồm tên phương pháp, lý do một dòng, và cờ cho phép bỏ qua nếu lỗi. Có hai luật quan trọng:
- Mọi kế hoạch đều bắt đầu bằng fitz_native: line/span frame là nền cho mọi phương pháp downstream.
- Cờ optional được dùng rất hạn chế:
image_pipelinevàvision_llm_figurescó thể opt-out vì gọi công cụ nặng. Các phương pháp TOC và layout là bắt buộc vì chúng là đầu ra chịu lực.
Các nhóm phương pháp parsing được phân loại theo màu
3. Execute: gọi từng phương pháp theo thứ tự
Dispatcher chạy từng bước qua một shim nhỏ _run_step(). Mỗi module parsing như fitz, azure_layout, docling, easyocr, mistral_ocr, toc, vision, images đều đã sẵn sàng cho cuộc gọi này. Hai điểm đáng chú ý:
- Xử lý lỗi riêng từng bước: nếu bước optional lỗi, exception được bắt vào
_errorvà pipeline vẫn chạy tiếp. Nếu bước bắt buộc lỗi, toàn bộ tiến trình dừng để caller nhìn thấy lỗi thật ngay lập tức. - Audit dễ dàng: đầu ra thô của từng phương pháp được giữ trong
step_outputsbên cạnh corpus đã gộp. Không cần đoán mò, có thể đọc trace từ đầu đến cuối.
4. Synthesize: gộp mọi đầu ra thành một corpus
Giai đoạn cuối gộp đầu ra thành một dict với sáu frame: line_df, span_df, toc_df, image_df, reference_df, table_df, kèm danh sách sources ghi rõ phương pháp nào đóng góp frame nào. Quy tắc gộp đơn giản:
- Giữ nguyên các frame gốc.
- Khi hai phương pháp cùng tạo ra một key, giữ frame nhiều thông tin hơn (nhiều dòng hơn và bộ cột tương thích).
- Các trường hợp còn lại thì nối tiếp (concatenate).
Việc đối chiếu chi tiết cấp cột giữa Docling table cells và Azure Layout cells đã nằm trong từng module; dispatcher không viết lại logic đó.
Kho vũ khí các phương pháp parsing
Tác giả bài gốc xây dựng một bộ “identity card” cho từng phương pháp, bao gồm họ (family), giấy phép, nơi chạy, tốc độ, có gọi LLM hay không, có lộ typography không, đầu vào, đầu ra và điểm mạnh/yếu. Các họ chính:
- Native text parsers (màu xanh dương): đọc trực tiếp tầng văn bản, không dùng model. Gồm
fitz(baseline rẻ),PyMuPDF4LLM(xuất Markdown),pdfplumber(tọa độ chính xác, bảng có đường kẻ),pdfminer.six(trình trích xuất cấp thấp cổ điển). - Layout and table models (màu xanh mòng két): chạy deep learning layout pass. Gồm
Docling(bảng cấp độ cell),Marker(Markdown trên diện rộng),MinerU(công thức toán và CJK),Surya(layout analysis). - OCR readers (màu hổ phách): đọc pixel khi không có tầng văn bản. Gồm
Tesseract,EasyOCR,docTR,PaddleOCR(độ chính xác cao và hỗ trợ bảng). - Structure and TOC recovery (màu tím): dựng lại mục lục. Đọc native outline trước, fallback sang in sommaire hoặc body headings, hoặc dùng
Unstructuredđể phân hoạch toàn tài liệu.
Mỗi identity card được lưu thành file PNG riêng để có thể dùng trong slide deck, báo cáo đánh giá hoặc bài đăng LinkedIn.
Minh họa một identity card cho nhóm phương pháp parsing
Chạy thử trên bài báo “Attention Is All You Need”
Tác giả chạy dispatcher trên file data/paper/1706.03762v7.pdf — bài báo NIPS 2017 dài 15 trang, có native outline. Kết quả:
- Nature trả về:
native-with-outline - Plan gồm bốn bước:
fitz_native(bắt buộc),fitz_native_toc(bắt buộc, lấy từdoc.get_toc()),toc_body_structure(tư vấn, bắt các subsection cấp 3 mà outline bỏ sót),image_pipeline(tùy chọn) - Corpus dict:
toc_df15 dòng,line_df1048 dòng,span_df3480 dòng - Image và reference frame rỗng vì bài báo không có biểu đồ và phương pháp trích xuất reference chưa được nối vào plan
Toàn bộ chỉ tốn một lần xây line_df, một lần xây span_df, một lần đọc native TOC và một vòng body-structure. Không vision LLM, không OCR, không Docling. Plan đã khớp đúng bản chất tài liệu.
Chi phí và khi nào không nên dùng
Agentic parsing không miễn phí. Trên bài báo 15 trang, chi phí chỉ vài trăm mili-giây. Nhưng với hợp đồng 300 trang có bảng biểu, hình ảnh và không có native outline, plan có thể phình lên 7–8 phương pháp, một số gọi LLM — tổng thời gian có thể lên tới một phút và tốn tiền thật.
Nguyên tắc của tác giả: chỉ chạy agentic parsing cho những tài liệu được dùng đến mức tối đa — hợp đồng mà reviewer sẽ đọc từ đầu đến cuối, bài báo mà search result hiện ra và người đọc click vào. Còn với ingestion quy mô lớn, hãy dùng adaptive parsing — chạy parsing on-demand, chỉ khi retrieval thực sự cần đến trang đó.
Kết thúc và hướng đi tiếp
Bài viết này khép lại brick đầu tiên của series Enterprise Document Intelligence: từ một đường dẫn PDF, người dùng gọi parse_pdf_agentic(pdf_path) và nhận về một corpus dict đầy đủ line_df, span_df, toc_df, image_df, reference_df, table_df — ở mức độ mà tài liệu đó hỗ trợ. Mọi lựa chọn phương pháp đều xác định; LLM nằm trong module riêng với hợp đồng riêng, không xuất hiện ở tầng dispatcher. Nhờ vậy, plan có thể đọc và kiểm tra trước khi chạy, execution có thể audit từng bước.
Hai việc tiếp theo đang chờ: bổ sung integration test đầy đủ cho các module stub như azure_layout, docling_local, easyocr_scan, mistral_ocr, vision_llm_figures, image_pipeline; và viết bài về adaptive parsing — chế độ đối trọng, nơi parsing chạy một cách “lười biếng” theo nhu cầu của retrieval. Với các nhà phát triển đang xây dựng hệ thống RAG cho tài liệu pháp lý, báo cáo tài chính hay hồ sơ khoa học tại Việt Nam, đây là một hướng triển khai thực tế hơn nhiều so với việc chạy theo buzzword “agentic” mà không hiểu rõ thứ gì đang quyết định thứ gì.
Tham khảo
- Vaswani et al., Attention Is All You Need, arXiv:1706.03762, NeurIPS 2017 — bài báo dùng để chạy thử dispatcher.
- PyMuPDF (fitz) documentation,
page.get_text("dict"). - Sculley et al., Hidden Technical Debt in Machine Learning Systems, NIPS 2015 — mô hình “glue code” mà dispatcher cần đi đúng ranh giới.
- Series Enterprise Document Intelligence trên Towards Data Science — đặc biệt là các bài về parsing PDF, Azure Layout, Docling, OCR và tái dựng mục lục.
