# Unstructured: tách Word, PowerPoint và email của khách theo cấu trúc trước khi đưa vào RAG

> Với thiết lập mặc định, Unstructured bỏ qua file đính kèm email và cắt nhỏ các mục dài, nên có vài thứ cần đổi trước khi bạn index kho tài liệu của khách.

Bản gốc: https://fdetimes.net/vi/cong-cu/unstructured-doc-email-pptx-cho-rag/

Kho tri thức của khách hàng thường không nằm trong database. Nó nằm trong file Word hợp đồng, bộ slide PowerPoint gửi ban giám đốc, và những chuỗi email mà nội dung chính lại nằm ở file đính kèm. Nếu gom hết text thành một khối rồi cứ N ký tự cắt một chunk để đưa vào RAG, bạn mất ngay thứ quan trọng nhất: cấu trúc.

Nếu nội dung đã bị cắt vụn hoặc bỏ sót ngay từ bước ingest, về sau đổi model hay chỉnh prompt cũng khó bù lại, vì retriever không thể tìm ra thứ chưa từng được index.

Unstructured là thư viện mã nguồn mở theo giấy phép Apache-2.0, được nhóm phát triển giới thiệu là miễn phí, với mục tiêu chuyển tài liệu thành dữ liệu có cấu trúc. Theo chính công ty, các định dạng được hỗ trợ gồm Word, PowerPoint và email, bên cạnh Excel, HTML, ảnh và bản scan.

## Trước khi chunk, phải biết mỗi đoạn là gì

Bước đầu tiên trong Unstructured là partitioning, tức tiền xử lý tài liệu thô thành các element. Word dạng `.docx` có hàm riêng là `partition_docx`, PowerPoint dạng `.pptx` có `partition_pptx`.

Kết quả bạn nhận được không còn là một khối text phẳng mà là danh sách element, mỗi element mang metadata. Trường `parent_id` cho biết element đó nằm ở đâu trong cây phân cấp của tài liệu, nên khi một chunk được truy xuất, bạn có thể lần ngược về đúng mục đã sinh ra nó.

Metadata này nên đi theo chunk vào index. Jim Allen Wallace viết trên blog của Redis rằng các trường như tên tài liệu, tiêu đề mục và số trang giúp cả việc xếp hạng khi truy xuất lẫn chất lượng câu trả lời. Với khách, đó còn là thứ cho phép hệ thống trích dẫn "điều 4.2, trang 7" thay vì đưa ra một đoạn text không rõ nguồn.

## Email: nội dung thật thường nằm trong file đính kèm

Email là chỗ thiết lập mặc định dễ gây hại nhất. Tài liệu của Unstructured ghi rõ file đính kèm chỉ được partition khi bạn đặt `process_attachments=True`. Tài liệu của `UnstructuredEmailLoader` trong LangChain, vốn là lớp bọc Unstructured, cũng mô tả đây là tùy chọn phải chủ động bật.

Thử hình dung một chuỗi email với khách mà phần thân chỉ có câu "anh xem file đính kèm", còn bản đề xuất 12 trang nằm trong file Word. Quên tham số này thì RAG của bạn chỉ thấy câu chào.

Khi đã bật, metadata của element sinh ra từ file đính kèm có trường ghi tên file mà nó được đính kèm vào. Nhờ vậy, câu trả lời rút từ bản đề xuất vẫn trỏ được về đúng email đã gửi nó.

**Điểm mấu chốt:** Cấu trúc tài liệu đã mất ở bước ingest thì không retriever nào lấy lại được.

## Giữ ranh giới mục bằng by_title, rồi chỉnh con số 500

Bước tiếp theo là chunking. Chiến lược `by_title` giữ ranh giới giữa các mục, và có thể giữ cả ranh giới trang nếu bạn cần. Hệ quả thực tế là sẽ không có chunk nào chứa nửa cuối mục "Điều khoản thanh toán" cộng với nửa đầu mục "Bảo hành".

Con số cần để ý là `max_characters`, giới hạn cứng cho một chunk, mặc định là 500 ký tự. Thử tính với một mục "Điều khoản phạt" dài 1.800 ký tự: vì 1.800 lớn hơn 3 × 500, mục đó cần ít nhất bốn chunk.

Nếu điều kiện phạt nằm ở chunk đầu còn mức phạt nằm ở chunk cuối, câu hỏi "trễ hạn bị phạt bao nhiêu" có thể chỉ kéo về được một nửa đáp án. Bài viết của Redis mô tả đúng nguyên tắc của cách chunk theo cấu trúc: element chỉ bị cắt khi vượt kích thước tối đa, còn lại được giữ nguyên.

Vì thế câu hỏi thật là nên đặt giới hạn ở đâu. Redis khuyên coi kích thước chunk là tham số cần tinh chỉnh, không phải con số chép lại từ thiết lập cũ. Hãy chọn dựa trên độ dài thực tế của các mục trong tài liệu của khách và kết quả eval.

## Bảng biểu là chỗ dễ vỡ nhất

Bảng quá lớn bị Unstructured tách thành các mảnh `TableChunk` bằng cách cắt text. Thử hình dung khách hỏi về một bảng giá hay bảng SLA dài vài trang: nếu bảng bị cắt sai chỗ, câu trả lời sẽ sai theo.

Redis xếp việc bỏ qua cấu trúc vào nhóm lỗi phổ biến khi chạy production: bảng bị tách khỏi dòng tiêu đề, danh sách bị tách khỏi heading, và các chunk mất đi ý nghĩa vốn có. Một mảnh bảng chỉ còn các con số mà không còn tên cột thì retriever có tìm thấy cũng chẳng giúp được gì.

Cách chữa bắt đầu từ chính giới hạn kích thước. Vì element chỉ bị cắt khi vượt `max_characters`, nâng giới hạn này lên đủ để chứa các bảng quan trọng sẽ giữ chúng nguyên trong một chunk. Với những bảng vẫn quá dài, hãy đọc từng `TableChunk` và kiểm tra mảnh nào còn dòng tiêu đề cột, mảnh nào đã mất.

## Ba bước trong một đoạn code

Ghép lại, pipeline tối thiểu gồm ba bước: partition, tự đọc các element, rồi chunk theo mục với giới hạn lớn hơn mặc định.

```python
from unstructured.partition.docx import partition_docx
from unstructured.partition.email import partition_email
from unstructured.chunking.title import chunk_by_title

elements = partition_docx(filename="hop-dong.docx")

# In 10 element đầu để xem tài liệu bị tách ra sao
for el in elements[:10]:
    print(type(el).__name__, "|", el.metadata.parent_id, "|", el.text[:60])

chunks = chunk_by_title(elements, max_characters=2000)
print(len(chunks), "chunks")

# Email: phải bật thì file đính kèm mới được partition
mail = partition_email(filename="bao-gia.eml", process_attachments=True)
```

Con số 2000 chỉ là điểm xuất phát cho ví dụ mục 1.800 ký tự ở trên. Hãy chạy cả hai mức 500 và 2000 trên cùng một bộ câu hỏi thật, rồi giữ mức nào cho câu trả lời đầy đủ hơn.

## Rà năm lỗi này trước buổi demo

Nên rà bảng dưới đây trước khi index cả kho: mỗi dòng là một lỗi, kèm cách nhận ra nó và việc cần làm.

| Lỗi thường gặp | Dấu hiệu | Việc cần làm |
|---|---|---|
| Quên bật file đính kèm | RAG chỉ trả về lời chào trong email | Đặt `process_attachments=True` |
| Giữ nguyên 500 ký tự | Câu trả lời thiếu nửa sau của một điều khoản | So hai mức `max_characters` trên câu hỏi thật |
| Bảng bị cắt | Mảnh bảng không còn tên cột | Nâng `max_characters`, đọc lại từng `TableChunk` |
| Bỏ metadata khi embed | Không trích dẫn được mục hay trang | Lưu tiêu đề mục, số trang, `parent_id` cùng chunk |
| Chạy cả kho mà chưa xem mẫu | Mục bị gộp hoặc vỡ không rõ lý do | Partition vài file mẫu, tự đọc từng element |

Đừng xem nhẹ dòng cuối. Bỏ một buổi chiều partition vài file mẫu của chính khách và đọc danh sách element vẫn rẻ hơn nhiều so với phát hiện mục bị gộp hay bảng bị vỡ sau khi đã embed cả kho.

Khi ghi kỹ năng này vào CV, đừng chỉ viết "dùng Unstructured". Hãy kể bạn đã xử lý file đính kèm email, giữ bảng nguyên vẹn, chỉnh kích thước chunk theo độ dài mục, và nhờ đó cải thiện được bao nhiêu câu trả lời trên bộ câu hỏi thật của khách.

Khi demo, khách sẽ không hỏi bạn chọn embedding model nào. Họ sẽ hỏi vì sao hệ thống không tìm thấy bản báo giá trong email tuần trước, và câu trả lời thường nằm ở một tham số bạn chưa bật.

**Thử ngay tuần này:**

- Chọn 3 file thật (một .docx, một .pptx, một email có đính kèm), chạy partition rồi tự đọc danh sách element để xem tài liệu bị tách ra sao
- Chunk cùng một tài liệu với max_characters mặc định và với một giá trị lớn hơn, rồi so câu trả lời của RAG trên 5 câu hỏi thật của khách
- Tìm một tài liệu có bảng lớn, chunk nó và kiểm tra xem mảnh bảng nào đã mất dòng tiêu đề cột

## Nguồn

- [Partitioning](https://docs.unstructured.io/open-source/core-functionality/partitioning)

- [langchain.document_loaders.email.UnstructuredEmailLoader](https://sj-langchain.readthedocs.io/en/latest/document_loaders/langchain.document_loaders.email.UnstructuredEmailLoader.html)

- [Chunking](https://docs.unstructured.io/open-source/core-functionality/chunking)

- [Document elements and metadata](https://docs.unstructured.io/open-source/concepts/document-elements)

- [GitHub - Unstructured-IO/unstructured](https://github.com/Unstructured-IO/unstructured)

- [Convert Documents into Structured, AI-Ready Data | Unstructured](https://unstructured.io/)

- [Chunking for RAG: Strategies, tradeoffs & common mistakes](https://redis.io/blog/chunking-strategy-rag-pipelines/)
