Skip to content

Nôm v0.1 — pipeline trích xuất tài liệu

Tài liệu này đặc tả luồng dữ liệu đầy đủ cho nom.doc.extract. Mỗi stage có lựa chọn primary, một alternative đã test, và kế hoạch benchmark. Số liệu hoặc đo tại đây hoặc trích từ phía trên — không bao giờ tự nghĩ ra.

Hình dạng: bytes/Path → Pipeline.run(schema) → dict typed. Mỗi stage là một Stage protocol; người dùng có thể thay implementation hoặc chèn thêm stage của riêng mình.

Stage 1 · Load

Lựa chọn: stdlib pathlib + mimetypes. Không cần dep bên thứ ba. Detect format từ extension trước, fallback magic-bytes (python-magic, optional).

Đầu vàoDetectĐịnh tuyến tới
.pdfextension + magic %PDFparse → có thể cần OCR per-page
.png / .jpg / .tiffextension + magicOCR trực tiếp
.txt / .mdextensionnormalize → extract (skip 2 & 3)

Stage 2 · Parse (text + layout từ PDF native)

Lựa chọn: PyMuPDF (fitz). Nhanh nhất, hơn 19× trên PDF thực tế.

Thư việnThời gian/doc trung bìnhLayoutLicenseGhi chú
PyMuPDF (fitz)0.5sbảng, block, fontAGPL hoặc commercialmặc định — nhanh nhất, giàu nhất
pypdf4.2sblock cơ bảnMITthao tác đơn giản
pdfplumber9.5sbảng tốt nhấtMITfallback cho dự án không tương thích AGPL

Nguồn: py-pdf/benchmarks

Hành vi: trích text per-page; track box layout; flag các page mà text rỗng (= scan, định tuyến sang OCR).

Stage 3 · OCR (scan/ảnh sang text)

Lựa chọn: VietOCR (Transformer) khi có sẵn, fallback sang Tesseract cho tính portable.

EngineAcc công bố trên VNTốc độXử lý dấuLicense
VietOCRtrain trên VNchậm hơn (Transformer)mạnh nhấtApache 2.0
PaddleOCR PP-OCRv594.5% trên OmniDocBench [1]trung bìnhmạnh (multilingual)Apache 2.0
EasyOCR~79% chung56 FPS [2]tốt hơn TesseractApache 2.0
Tesseract 5 + vie70-97% (phụ thuộc chất lượng ảnh) [3]9.8 FPS [2]yếu — nhầm dấu chồng [4]Apache 2.0

Hành vi: OCR per-page cho scan, sau đó post-process bằng nom.text.fix_diacritics để vá lỗi dấu do OCR gây ra. Benchmark trong benchmarks/accuracy/bench_ocr.py (scaffold hôm nay, số thực ở v0.1).

Stage 4 · Normalize (dọn text)

Lựa chọn: nom.text (chính package này).

  • normalize — Unicode NFC (tất định, 9M ops/s)
  • fix_diacritics — khôi phục dấu mất khi OCR
    • v0.0.1 (hiện tại): rule-based, baseline đo được 41%
    • v0.0.2: ML-backed qua PyVi hoặc DistilBERT-Viet, target ~90%+
    • v0.1: LLM-backed khi truyền llm=
  • tokenize (dự kiến v0.0.2): dùng Underthesea (acc tách từ 80% [5]) thay PyVi (57.8%)

Stage 5 · Extract (LLM + schema)

Lựa chọn: Instructor wrap quanh LLM của người dùng.

Thư việnCách tiếp cậnStarLợiBất lợi
InstructorFunction calling + Pydantic11k15+ provider (OpenAI/Claude/Ollama/...), 3M dl/tháng, type-safe, retryyêu cầu mô hình hỗ trợ function-calling
LangExtractFew-shot + controlled generationđang lớntrích có nguồn, traceability vị trí chính xácmới hơn, tune cho Gemini
OutlinesConstrained token samplingmạnhchạy với mọi mô hình HF/vLLM, đảm bảo schema cứngyêu cầu kiểm soát nội bộ mô hình

Vì sao Instructor: trưởng thành nhất, hỗ trợ provider rộng nhất, Pydantic-native (khớp khai báo schema của chúng tôi), chạy với các lựa chọn LLM tier của chúng tôi.

Định tuyến LLM theo tier

TierKhuyến nghịLý do
Local mặc địnhQwen3-8B qua OllamaApache 2.0, chạy 6GB VRAM (Q4) hoặc 16GB CPU
Cloud openQwen3-235B-A22B qua Together / FireworksTop hiệu năng VN open, ~$0.50–1/M input token
Cloud closedgpt-4o / claude-sonnetTốt nhất VN chung, tham chiếu cho benchmark
Vision (skip OCR)Qwen2.5-VL-72B-InstructBest open vision-LLM cho extraction tài liệu có cấu trúc [6]

Nguồn xếp hạng mô hình VN: VMLU leaderboard + SiliconFlow 2026 review.

Stage 6 · Validate

Lựa chọn: Pydantic v2. Đã là dep transitive qua Instructor.

Schema do user khai báo chính là model Pydantic. Validation chạy ở thời điểm nhận kết quả extract. Coercion:

  • "date"datetime.date
  • "amount_vnd"int (parse được cả 1.500.000.000một tỷ năm trăm triệu)
  • "party" → model Party lồng nhau với name, tax_id, address, representative

Type shorthand built-in nằm ở nom.doc.schemas.

End-to-end: phiên user v0.1 (dự kiến)

python
from nom.doc import extract
from nom.llm import Ollama

result = extract(
    "hop_dong.pdf",
    schema={
        "so_hop_dong": str,
        "ngay_ky": "date",
        "ben_a": "party",
        "ben_b": "party",
        "tong_gia_tri": "amount_vnd",
    },
    llm=Ollama(model="qwen3:8b"),
)
# {
#   'so_hop_dong': 'HĐ-2025-002',
#   'ngay_ky': date(2025, 3, 14),
#   'ben_a': Party(name='Công ty Cổ phần Hồng Hà', tax_id='0123456789', ...),
#   'ben_b': Party(name='Bà Nguyễn Thị Hương', ...),
#   'tong_gia_tri': 1_500_000_000,
# }

Benchmark theo từng stage

StageFile benchmarkĐo cái gìTrạng thái
1. Loadtầm thườngn/a
2. Parsebenchmarks/perf/bench_pdf.py (dự kiến)throughput parse PDF, char accuracyv0.1
3. OCRbenchmarks/accuracy/bench_ocr.py (scaffold hôm nay)char/word accuracy + tốc độ qua các enginev0.1
4. Normalizebenchmarks/accuracy/bench_diacritics.pykhôi phục dấu mức từv0.0.1
4. Normalize (perf)benchmarks/perf/bench_text.pythroughput per-functionv0.0.1
5. Extractbenchmarks/models/bench_extraction.py (scaffold)extraction accuracy qua các LLMv0.1
End-to-endbenchmarks/accuracy/bench_pipeline.py (scaffold hôm nay)accuracy hỗn hợp trên hợp đồng thựcv0.1

Nguồn

Cố ý không nằm trong v0.1

  • Module embedding tuỳ biến — RAG là mối quan tâm riêng. Khi thêm nom.embeddings, lựa chọn (đã verify với VN-MTEB) sẽ là: AITeamVN/Vietnamese_Embedding (BGE-M3 base) cho retrieval, dangvantuan/vietnamese-document-embedding (8192 ctx) cho doc dài. Quyết định ở v0.3+.
  • Đường vision-only (Donut/LayoutLM) — hứa hẹn nhưng chưa train VN. Theo dõi tài liệu; tích hợp khi có một mô hình đáng khuyến nghị.
  • Extraction layout-aware — PyMuPDF cho ta block; reasoning layout đầy đủ chờ v0.2+.
  • Phân loại tài liệu — ngoài phạm vi. Dùng Pydantic discriminated union nếu cần.