PDF hóa đơn: đừng bắt AI đoán mò

PDF hóa đơn: đừng bắt AI đoán mò

Muốn trích xuất hóa đơn ổn định, đừng bắt đầu từ OCR. Hãy bắt đầu từ schema, bẫy kiểm thử và ledger đầu ra.

Có lần mình thấy một team khoe demo đọc hóa đơn PDF rất mượt: kéo file vào, model nhả ra vendor, subtotal, tax, total. Cả phòng gật gù. Đến lúc finance hỏi một câu rất đời: “Hóa đơn này đã trả một phần, vậy trạng thái là paid hay unpaid?” — con demo đứng hình như tàu gặp gió ngược.

Vấn đề không nằm ở chuyện model đọc được chữ hay không. Vấn đề là bạn đang giao cho AI một nghiệp vụ kế toán, nhưng lại chấm nó như bài OCR.

Luận điểm của mình: với invoice intelligence, quyết định kỹ thuật quan trọng nhất không phải “dùng model nào mới nhất”, mà là thiết kế pipeline theo schema, chấm theo từng field, rồi mới cho ra ledger. Model chỉ là một thủy thủ trên tàu; schema mới là hải đồ.

Sơ đồ minh họa cho bài PDF hóa đơn: đừng bắt AI đoán mò

Sơ đồ tóm tắt ý chính của bài viết.

Mục tiêu: biến PDF thành ledger, không phải thành văn bản đẹp

Nếu bạn đang xây hệ thống xử lý hóa đơn cho accounts payable, đầu ra thật sự không phải là đoạn text được OCR sạch sẽ. Đầu ra cần là dữ liệu có thể đi tiếp vào hệ thống kế toán:

Schema-guided extraction nghĩa là trích xuất theo một cấu trúc định trước, thay vì bảo model “đọc giúp tôi hóa đơn này”. Với builder, nó ảnh hưởng trực tiếp đến workflow: bạn có contract rõ giữa model output, validation layer và downstream ledger.

Ví dụ cụ thể: nếu po_number không có trên hóa đơn, output đúng phải là null, không phải model bịa một mã nhìn có vẻ hợp lý. Nếu balance_due còn lớn hơn 0, trạng thái không nên là paid chỉ vì hóa đơn có dòng “payment received”.

Đây là khác biệt giữa demo vui mắt và hệ thống có thể cho finance dùng mà không run tay.

Checklist trước khi chọn model

Trước khi bàn lift-pdf, vision-language model, hay pipeline hai model, mình sẽ bắt team trả lời 6 câu này trước:

  1. Field nào là bắt buộc, field nào được phép null?
  2. Field nào cần tính lại để kiểm tra? Ví dụ subtotal + tax - paid_amount = balance_due.
  3. Field nào dễ bị nhầm vì layout? Ví dụ bill_toship_to.
  4. Bạn chấm đúng/sai theo document hay theo field?
  5. Có cần lưu confidence không? Confidence là mức tin cậy của output, dùng để route sang người kiểm tra.
  6. Ledger cuối cùng cần format nào? CSV, JSON lines, bảng trong database, hay API payload.

Nói thẳng ra thì, nếu chưa trả lời được mấy câu này, việc đổi model chỉ giống đổi buồm khi chưa biết cảng đến.

Một schema tối thiểu có thể bắt đầu như sau:

{
  "vendor": {
    "name": "string",
    "tax_id": "string|null"
  },
  "parties": {
    "bill_to": "string",
    "ship_to": "string|null"
  },
  "invoice": {
    "invoice_number": "string",
    "po_number": "string|null",
    "invoice_date": "string",
    "due_date": "string|null"
  },
  "amounts": {
    "subtotal": "number",
    "tax": "number|null",
    "total_amount": "number",
    "balance_due": "number"
  },
  "payment_status": "paid|unpaid|partial|unknown",
  "line_items": [
    {
      "description": "string",
      "quantity": "number|null",
      "unit_price": "number|null",
      "amount": "number"
    }
  ]
}

Đừng xem schema là giấy tờ hành chính. Nó là mỏ neo để model không trôi sang những câu trả lời “có vẻ đúng”.

Mổ hệ thống theo 4 lớp cần giữ

Một pipeline invoice extraction đáng tin thường có 4 lớp. Không phải lớp nào cũng phải phức tạp, nhưng thiếu lớp nào thì lỗi sẽ chui qua đúng lúc bạn đang ngủ.

Lớp 1: tài liệu kiểm thử có chủ đích

Nguồn về lift-pdf dùng synthetic invoice PDF — hóa đơn giả lập có kiểm soát — để test. Điểm đáng học không phải “hãy dùng hóa đơn giả mãi”, mà là: hãy tự tạo bộ test có bẫy nghiệp vụ.

Hình dung thế này: bạn có 20 mẫu hóa đơn nội bộ. Nếu chỉ chọn hóa đơn sạch, cùng layout, đủ field, bạn đang luyện model đi trong hồ bơi. Ra biển thật, nó gặp scan lệch, dòng giảm giá, thanh toán một phần, địa chỉ ship khác địa chỉ bill — thế là lạc.

Bộ test nên có ít nhất các ca:

Lớp 2: inference có kiểm soát tài nguyên

Inference là bước chạy model để lấy output. Với PDF extraction, bạn sẽ đụng đến GPU, VRAM và batch size sớm hơn tưởng tượng.

VRAM là bộ nhớ GPU. Nếu không đủ, model không load được hoặc chạy rất chậm. Một hướng thường gặp là quantization, tức nén trọng số model để giảm bộ nhớ, ví dụ 4-bit NF4. Tradeoff là tiết kiệm tài nguyên nhưng cần kiểm tra xem độ chính xác field có tụt ở các ca khó không.

Quyết định thực dụng:

Lớp 3: validation theo field, không chấm cảm giác

Field-level evaluation là chấm từng trường riêng, thay vì chỉ nhìn toàn bộ JSON có “ổn” hay không. Đây là lớp nhiều team bỏ qua nhất.

Ví dụ vendor_name đúng, invoice_date đúng, nhưng balance_due sai. Nếu bạn chỉ nhìn output tổng thể, có thể vẫn thấy đẹp. Nhưng với finance, sai balance_due là sai trọng yếu.

Bảng chấm tối thiểu nên có:

| Field | Cách chấm | Nếu sai thì xử lý |
|---|---|---|
| invoice_number | exact match | gửi human review |
| po_number | exact/null match | cho phép null nếu vắng mặt |
| subtotal | numeric tolerance | đối chiếu lại line items |
| total_amount | numeric tolerance | kiểm tra tax/discount |
| balance_due | numeric tolerance | không auto-post ledger |
| payment_status | rule + model output | ưu tiên rule nếu conflict |

Điểm mấu chốt: một số field nên để model đọc, một số field nên để rule kiểm tra lại. Đừng bắt model làm kế toán trưởng nếu một phép cộng đơn giản đã đủ bắt lỗi.

Lớp 4: ledger generation có hàng chờ kiểm tra

Ledger là sổ dữ liệu giao dịch có thể đưa vào hệ thống kế toán hoặc phân tích. Khi model nhả JSON, đừng vội ghi thẳng vào ledger chính.

Mình thích chia output thành 3 luồng:

  1. Auto-accept: field bắt buộc đủ, validation pass, confidence ổn.
  2. Review: thiếu field quan trọng, số tiền không khớp, layout lạ.
  3. Reject: file lỗi, không phải invoice, hoặc model output không parse được.

Pipeline tốt không phải pipeline không bao giờ sai. Pipeline tốt là pipeline biết khi nào cần kéo cờ đỏ.

Làm trong một buổi: bản playbook gọn

Nếu team bạn muốn thử ngay mà không biến nó thành dự án quý, đây là lộ trình một buổi chiều.

Bước 1: Chốt schema v0

Chỉ lấy 12 đến 20 field thật sự cần cho downstream. Đừng bắt đầu bằng schema khổng lồ. Với mỗi field, ghi rõ:

Bước 2: Tạo 10 PDF kiểm thử có bẫy

Có thể dùng synthetic PDF để kiểm soát ground truth. Quan trọng là mỗi file phải có đáp án chuẩn đi kèm.

Ví dụ cấu trúc thư mục:

invoices/
  INV-001.pdf
  INV-001.truth.json
  INV-002.pdf
  INV-002.truth.json
results/
  extracted/
  scores/
  ledger/

Bước 3: Chạy extraction theo schema

Dù bạn dùng lift-pdf hay model khác, prompt/API call nên ép output theo schema. Không nhận prose, không nhận markdown, không nhận “tôi nghĩ là”. Chỉ nhận JSON parse được.

Bước 4: Chấm field-level

Tạo script so sánh extracted.json với truth.json. Với số tiền, parse về decimal trước khi so. Với field optional, phân biệt rõ null và chuỗi rỗng.

Bước 5: Sinh ledger nháp

Chỉ đưa những invoice pass rule vào ledger_draft.csv hoặc database staging. Những case còn lại đi vào review_queue.jsonl.

invoice_id,vendor,total_amount,balance_due,status,review_reason
INV-001,Acme Ltd,1200.00,0.00,paid,
INV-002,Delta Co,980.00,300.00,review,balance_due_conflict

Đây là bản đủ nhỏ để làm, nhưng đủ thật để lộ lỗi.

Bẫy dễ bị hype quá mức

Có ba thứ mình sẽ cảnh giác.

Một là tin rằng multimodal model tự hiểu nghiệp vụ. Model nhìn được layout, bảng, chữ, thậm chí bounding boxes — tọa độ vùng trên trang — nhưng nghiệp vụ “partial payment vẫn còn nợ” cần rule hoặc validation rõ.

Hai là lấy synthetic test làm bằng chứng production. Synthetic PDF rất hữu ích để kiểm soát bẫy, nhưng cuối cùng vẫn phải có tập hóa đơn thật: scan mờ, font kỳ, dấu mộc, tiếng Việt lẫn tiếng Anh, vendor viết số tiền không nhất quán.

Ba là gom mọi việc vào một model lớn. Nguồn AWS về xử lý yearbook dùng pipeline hai model: một model trích xuất multimodal, model khác reasoning theo không gian. Với invoice, bạn cũng có thể tách: model đọc layout, rule kiểm số tiền, classifier route review. Không phải lúc nào “một call làm hết” cũng là hướng dễ vận hành nhất.

Sau bài này, nên đổi cách nghĩ gì?

Đừng hỏi đầu tiên: “Model nào đọc PDF hóa đơn tốt nhất?”

Hãy hỏi: “Schema nào khóa được nghiệp vụ, bộ bẫy nào đo được lỗi, và ledger nào dám nhận output này?”

Khi câu hỏi đổi, kiến trúc cũng đổi: từ OCR demo sang pipeline có kiểm soát. Và lúc đó, nếu con tàu gặp gió ngược, ít nhất bạn còn biết đang lệch vì model, schema, validation hay ledger — chứ không đứng trên boong đoán mò.

---
Bụi Wire — nghiện đọc release notes lúc 2 giờ sáng

Nguồn tham khảo