چگونه PDF، Word، HTML، کد و جدول را برای RAG آماده کنیم؟ تجربه طراحی Document Ingestion
استخراج متن فقط شروع کار است. در این مقاله Pipeline آمادهسازی PDF، DOCX، HTML، جدول و کد برای RAG را با تمرکز بر provenance، OCR، structure و chunking بررسی میکنیم.
- Document Ingestion RAG
- PDF RAG
- RAG PDF Python
- DOCX RAG
- HTML RAG
- OCR RAG

چگونه PDF، Word، HTML، کد و جدول را برای RAG آماده کنیم؟
وقتی درباره RAG صحبت میشود، معمولاً تمرکز روی embedding و vector database است. اما در پروژههای واقعی، یکی از پرهزینهترین بخشها قبل از embedding اتفاق میافتد: Document Ingestion.
یک فایل PDF، Word یا HTML «متن» نیست. هرکدام ساختار، metadata، نویز و edge caseهای خاص خود را دارند. اگر این مرحله ضعیف باشد، retrieval در ادامه روی داده خراب کار خواهد کرد.
در توسعه Semantic Chunk Search، ingestion از یک تابع ساده file-to-text به یک pipeline ساختاری تبدیل شد.
یک Pipeline خوب از کجا شروع میشود؟
معماری سادهشده ما این است:
File
-> Loader
-> Extract text + structure
-> Normalize
-> Security scan
-> Detect content type
-> Chunk
-> Attach provenance
-> Index
هر مرحله باید اطلاعات قبلی را تا حد ممکن حفظ کند.
PDF؛ پیچیدهتر از آن چیزی که به نظر میرسد
PDF در اصل یک format صفحهمحور است، نه document-semantic format. ممکن است متن ظاهراً یک paragraph باشد ولی در فایل به چند object جدا تقسیم شده باشد.
مشکلات رایج PDF:
- text order اشتباه
- header/footer تکراری
- page break وسط جمله
- جدولهایی که به متن مسطح تبدیل میشوند
- صفحه بدون text layer
- PDF اسکنشده
- character encoding عجیب
در ماژول، PDF loader با pypdf کار میکند و برای صفحات بدون متن، OCR hook قابل تزریق در نظر گرفته شده است.
چرا blank page نباید ingestion را خراب کند؟
یکی از باگهایی که در hardening پیدا کردیم این بود که صفحه خالی میتوانست مسیر ingestion را دچار مشکل کند.
در سند واقعی blank page کاملاً طبیعی است. ممکن است صفحه separator، تصویر یا scan بدون text layer باشد.
Pipeline باید بتواند:
- صفحه واقعاً خالی را skip کند
- صفحه image-only را به OCR بدهد
- شماره صفحه را در provenance حفظ کند
یک edge case کوچک در تست، میتواند در batch ingestion هزاران فایل به خطای عملیاتی تبدیل شود.
OCR باید injectable باشد
OCR انتخابهای زیادی دارد و کیفیت آن به زبان و نوع سند وابسته است. به همین دلیل بهتر است retrieval library یک OCR engine خاص را hard-code نکند.
Contract مناسب چیزی شبیه این است:
page image -> OCR provider -> extracted text + optional confidence
به این ترتیب deployment میتواند براساس نیاز خود سرویس OCR انتخاب کند.
برای فارسی، OCR confidence و source trust اهمیت بیشتری پیدا میکند؛ چون خطای یک حرف میتواند identifier یا عدد را عوض کند.
Page Provenance را از دست ندهید
اگر Chunk از صفحه ۱۲ گزارش آمده، این اطلاعات باید تا search result باقی بماند.
Provenance میتواند شامل این موارد باشد:
- source_id
- page number
- section path
- original offsets
- loader metadata
بدون provenance، citation در UI ضعیف میشود و debugging تقریباً غیرممکن است.
یکی از اصول ما این بود:
Retrieval result باید قابل برگشت به منبع اصلی باشد.
Word یا DOCX فقط paragraph نیست
DOCX structure غنیتری از text ساده دارد:
- heading
- paragraph
- table
- list
اگر فقط document.paragraphs را بخوانیم، tableها ممکن است نادیده گرفته شوند.
در نسخه hardened، ingestion مربوط به DOCX برای heading و table بهبود پیدا کرد.
Heading hierarchy برای Chunking و title representation بسیار ارزشمند است. Table نیز باید با schema خود حفظ شود.
چرا جدول را نباید flatten کرد؟
فرض کنید جدول این باشد:
| Service | Timeout | Retry |
|---|---|---|
| Payment | 30s | 3 |
| Auth | 10s | 2 |
اگر extraction فقط این خروجی را بسازد:
Payment 30s 3
مدل نمیداند 30s مربوط به Timeout است یا چیز دیگری.
Table-aware representation بهتر است header را همراه row نگه دارد:
Service=Payment; Timeout=30s; Retry=3
ساختار context، معنای داده را حفظ میکند.
HTML و مشکل Hidden Content
HTML شاید ساده به نظر برسد چون متن در DOM وجود دارد، اما همه DOM نباید index شود.
مواردی که باید حذف یا مدیریت شوند:
<script><style>- hidden elements
- navigation noise
- cookie banner
- accessibility-only duplicates
در audit ما hidden DOM indexing بهعنوان یک مشکل واقعی شناسایی و حذف شد.
این موضوع فقط کیفیت نیست؛ امنیت هم هست. attacker ممکن است دستور prompt injection را در محتوای hidden قرار دهد تا کاربر آن را نبیند ولی RAG آن را ingest کند.
Markdown؛ ساختار رایگان را دور نریزید
Markdown بهصورت طبیعی heading، code fence، list و table دارد.
اگر همه را به plain text تبدیل کنیم و بعد Chunk کنیم، اطلاعات ساختاری رایگان را دور ریختهایم.
Composite Chunking میتواند sectionهای Markdown را تشخیص دهد و هر بخش را با strategy مناسب پردازش کند.
کد یک نوع document متفاوت است
برای code repository، line-based splitting معمولاً کافی نیست.
Python AST اطلاعات صریح function و class میدهد. یک code chunk بهتر است واحد syntax معنادار باشد.
مثلاً function زیر باید تا جای ممکن کامل بماند:
def calculate_invoice_total(items, tax_rate):
subtotal = sum(item.price for item in items)
return subtotal * (1 + tax_rate)
اگر chunk وسط function بریده شود، retrieval بخشی از context لازم را از دست میدهد.
Source-wide Hashing
در ingestion صفحهای، identity source باید consistent باشد.
یکی از مشکلاتی که در hardening اصلاح شد این بود که hash page ingestion نباید بهصورت cumulative و متفاوت برای هر page ساخته شود. source hash باید نماینده source کامل باشد.
چرا مهم است؟ چون source replacement، versioning و idempotency به identity درست وابستهاند.
Metadata بخشی از محتواست
فرض کنید فایل هیچ تغییر متنی نکرده ولی این metadata تغییر کرده است:
{
"visibility": "private",
"allowed_groups": ["finance"]
}
سیستم نباید ingestion را صرفاً به دلیل ثابت بودن text skip کند.
در سیستم سازمانی، metadata و ACL بخشی از state source هستند.
Security Scan در ingestion
محتوای document ممکن است شامل prompt injection یا keyword stuffing باشد.
Security scanner بهتر است قبل از index شدن محتوا اجرا شود.
در v4 حتی مسیر ingest_pages() اصلاح شد چون در نسخهای از flow، page ingestion scanner را دور میزد.
این مثال نشان میدهد داشتن scanner کافی نیست؛ باید مطمئن شویم همه ingestion pathها از همان security contract عبور میکنند.
Normalization قبل یا بعد از Parsing؟
جواب بستگی به نوع transformation دارد.
Normalization character-level مثل ي -> ی را میتوان زود انجام داد، اما نباید markup لازم برای structure detection را نابود کرد.
بهتر است pipeline مرحلهبندی شده باشد:
Raw extraction
-> structural interpretation
-> text canonicalization
-> chunk preparation
و original text/provenance نیز حفظ شود.
Error Handling در batch ingestion
در محیط واقعی نباید یک فایل خراب کل batch را متوقف کند، مگر در strict mode.
بهتر است ingestion برای هر source status داشته باشد:
success
partial
skipped
failed
quarantined
و diagnostic ثبت کند.
Fail-open باید آگاهانه باشد، نه اینکه exception silently بلعیده شود.
این یکی از حوزههایی است که در نسخههای production باید logging و metrics جدی باشند.
معماری پیشنهادی برای سازمان کوچک
Upload/API
↓
Type Detector
↓
PDF / DOCX / HTML / Markdown Loader
↓
OCR if needed
↓
Normalization + Security Scan
↓
Composite Chunker
↓
Metadata + ACL + Provenance
↓
Embedding + Lexical Index
↓
Qdrant + metadata store
این معماری برای knowledge base داخلی نقطه شروع خوبی است.
چه چیزهایی را باید تست کنیم؟
یک test suite ingestion خوب فقط فایل happy-path ندارد.
نمونهها:
- PDF با blank page
- PDF image-only
- DOCX دارای table
- HTML دارای hidden content
- Markdown با code fence
- سند فارسی با اعداد عربی
- source با ACL تغییرکرده و متن ثابت
- duplicate paragraph در یک source
هرکدام از این caseها در production محتملاند.
جمعبندی
«متن را از فایل بگیر و embedding کن» برای Demo کافی است، اما Document Ingestion واقعی باید structure، provenance، security، normalization و lifecycle را مدیریت کند.
تجربه ما نشان داد کیفیت RAG تا حد زیادی قبل از vector database تعیین میشود.
اگر ورودی نامطمئن، ناقص یا بدون ساختار باشد، retrieval فقط همان مشکل را سریعتر و در مقیاس بزرگتر تکرار میکند.
بهروزرسانی:
همهٔ نوشتهها