Помимо функции extract_text: два слоя PDF-файла, определяющие качество RAG.
Enterprise Document Intelligence [Том 1 #5A] – Сигналы документа (метаданные, исходное оглавление, исходное программное обеспечение) и содержимое страницы (текст против сканов, таблицы, изображения, столбцы, профиль страницы)
Делиться

Эта статья — первый этап анализа документов в серии статей, посвященных построению корпоративной системы RAG из четырех компонентов: анализа, анализа вопросов, поиска и генерации. Анализ идет первым, и это первая из двух его частей. В этой части рассматривается первый из двух уровней анализа: знание природы документа (цифровой или отсканированный, исходное программное обеспечение, заявленные метаданные, собственное оглавление) плюс краткое резюме, написанное магистратом права, о том, что это за документ. В следующей части содержимое преобразуется в реляционный набор таблиц.

Прежде чем в процессе RAG начнется какое-либо извлечение данных, парсеру предстоит выполнить одну задачу: прочитать документ так, как это сделал бы человек, прежде чем ответить на вопрос о нем.
Что это за документ? Резюме, страховой договор, нормативный документ, научная статья? Сколько страниц? Создан в цифровом виде, отсканирован или сшит из того и другого? Что в нём содержится: абзацы, таблицы, многоколоночная верстка, встроенные изображения? На каком языке?
Каждая из этих проверок представляет собой сбой, от которого остальная часть конвейера не может восстановиться:
- Резюме, экспортированное из дизайнерского шаблона. Имя кандидата размещено в виде логотипа вверху первой страницы, остальная часть страницы — чистым текстом. При запросе «Как вас зовут?» поиск не находит ничего подходящего и возвращается к полю
authorв метаданных PDF-файла, то есть к тому, кто последним редактировал файл. Ответ оказывается неверным еще до начала генерации. - Договор страхования с
has_text_layer=True. Текстовый слой представляет собой результат оптического распознавания текста (OCR) с качеством 0,3. Фраза «Плата за продление 250 евро» отображается как «Плата за продление 250 евро». Поиск по ключевым словам никогда не дает совпадений; генерация считывает другое число рядом и подтверждает его. - Нормативный текст объемом 200 страниц, не содержащий оглавления и заголовков, которые может обнаружить парсер. Конвейер обработки воспринимает его как единый однородный массив. Парсер вопросов не знает, что на странице 4 находятся определения, а на странице 187 — исключения.
- Научная статья с двухколоночной структурой. Простой способ извлечения текста заключается в построчном чередовании левой и правой колонок. Полученный фрагмент текста читается как бессмыслица.
Каждый раз одна и та же форма. Эксперту задали вопрос о документе, который он никогда не открывал. Он угадал. Конвейер обработки данных сделал то же самое.
Анализ текста состоит из двух уровней. В этой статье (5_A) рассматривается первый: знание характера документа (цифровой или отсканированный, исходное программное обеспечение, заявленные метаданные, собственное оглавление, если таковое имеется) и краткое резюме , написанное в рамках программы LLM (количество страниц плюс три-четыре предложения, указывающие тип документа, основную тему и содержащиеся в нем поля). В следующей статье (5_B) рассматривается второй уровень: точное знание содержимого с помощью реляционной базы данных, где каждая строка, фрагмент текста, изображение и запись в оглавлении становятся одной строкой, ключом которой является страница и позиция.
В статье используется PyMuPDF (также импортируемый как fitz ), бесплатная библиотека Python, которая считывает байты PDF-файлов напрямую. Никаких внешних инструментов, никакого ключа API. Достаточно быстрая для работы во время загрузки и точная для PDF-файлов, изначально созданных в цифровом виде. Тот же контракт parse_pdf может быть реализован более мощными движками (Azure Layout, Docling, Camelot, запасной вариант vision-LLM). Когда страница требует большей глубины, чем может обеспечить fitz, происходит адаптивная каскадная обработка. Это расширение — тема для дальнейшего обсуждения, выходящая за рамки данной статьи.
1. Сигналы на уровне документа
PDF-файл предоставляет два типа информации. Сигналы уровня документа : метаданные, собственные закладки, объявленные свойства. Содержимое уровня страницы : что содержит каждая страница. Парсер считывает их в указанном порядке и доверяет содержимому, если данные на разных страницах не совпадают.

Метаданные — это несколько полей, которые PDF-файл передает за миллисекунды. Производитель, Создатель, собственные закладки, флаг шифрования. На уровне документа, без обхода страниц. Вы считываете их в самом начале каждого анализа, чтобы выполнить маршрутный запрос. Экспорт в Word → прямое извлечение. Сканирование в Kofax → конвейер распознавания текста (OCR). Все неоднозначные данные → более медленный этап обработки содержимого.
Метаданные иногда вводят в заблуждение. Ghostscript и qpdf перезаписывают поле Producer при повторном сжатии, поэтому PDF-файл Word, дважды пересжатый, будет выдавать себя за файл Ghostscript и ничего не скажет об истинном происхождении. Вспомогательная функция предоставляет как предполагаемую метку, так и исходные строки creator_raw / producer_raw , чтобы правила, действующие в потоке, могли опровергнуть это.
1.1. Исходный код программного обеспечения
В PDF-файлах происхождение почти всегда указывается в полях Creator и Producer . Этот единственный сигнал позволяет оценить сложность дальнейшего анализа и выбрать правильную стратегию перед открытием любой страницы.
Производителей можно разделить примерно на пять категорий, расположенных в порядке от самых простых к самым сложным для анализа. Под «векторными таблицами» ниже подразумеваются таблицы, представленные в виде строк и текста (ячейки сохраняются как данные); противоположным является таблица, преобразованная в единое изображение (только OCR может восстановить ячейки).
- Инструменты для создания офисных документов (самые простые). Microsoft Word, PowerPoint, LibreOffice (Writer / Impress), OpenOffice, экспорт в Google Docs и Slides, Apple Pages и Keynote. Они сохраняют логическую структуру (заголовки, списки, абзацы) с использованием собственных векторных шрифтов. Прямое извлечение текста работает хорошо, порядок чтения надежен, таблицы обычно векторные. Основная часть «офисных документов», которые вы увидите в корпоративном корпусе.
- Документные процессоры . Движки LaTeX (pdfTeX, XeTeX, LuaTeX), Pandoc, Quarto, R Markdown, ReportLab, WeasyPrint. Отличная точность переноса текста, но со своими особенностями: перенос слов происходит через строки, математические формулы отображаются в виде векторных контуров или изображений (а не в виде извлекаемого текста), ссылки и цитаты имеют необычное расстояние между символами. Таблицы в большинстве случаев являются векторными.
- Инструменты для дизайна и публикации : Adobe InDesign, Illustrator, QuarkXPress, Affinity Publisher. Многоколоночная структура с неудобным порядком чтения. PyMuPDF часто неправильно определяет порядок чтения на плотных макетах. Таблицы могут отображаться как векторная графика, а не как векторные таблицы. Подписи, боковые панели и декоративные элементы усложняют анализ. При работе с плотными макетами следует ожидать перехода на парсер, учитывающий структуру документа.
- Конвейеры печати и рекомпрессоры . Печать через браузер (Chrome, Safari, Firefox), диалоговые окна печати в PDF, Ghostscript, qpdf, инструменты класса distiller. Разное качество. PDF-файлы, напечатанные через браузер, сохраняют текст, но теряют гиперссылки и закладки. Ghostscript и qpdf часто передают содержимое, но перезаписывают поле
Producer, поэтому исходный сигнал теряется. Именно поэтому вспомогательная функция предоставляет доступ к параметрамcreator_rawиproducer_raw. - Программное обеспечение для сканирования и приложения для захвата изображений (самая сложная задача). Kofax, ABBYY, Adobe Scan, ScanSnap, CamScanner, конвейеры обработки факсов. Чистое изображение, без встроенного текста. Обязательное распознавание текста (OCR). Приложения класса CamScanner добавляют проблемы с качеством изображения (искажение, низкое разрешение, артефакты JPEG).
def detect_source_software(doc: fitz.Document) -> str: """Classify the producing software using Creator/Producer metadata.""" meta = doc.metadata or {} combined = f"{(meta.get('creator') or '').lower()} {(meta.get('producer') or '').lower()}" # Bucket 1 — office authoring tools if "microsoft" in combined and "word" in combined: return "word_export" if "pdfmaker" in combined and "word" in combined: return "word_export" if "powerpoint" in combined: return "powerpoint_export" if any(s in combined for s in ("libreoffice", "openoffice")): return "libreoffice_export" # Bucket 2 — document processors if any(s in combined for s in ("pdftex", "xetex", "luatex")): return "latex_export" if "pandoc" in combined: return "pandoc_export" # Bucket 3 — design and publishing tools if "indesign" in combined: return "indesign_export" # Bucket 4 — print pipelines and recompressors if "ghostscript" in combined: return "ghostscript" if any(s in combined for s in ("chrome", "safari", "firefox")): return "browser_print" # Bucket 5 — scanner software (OCR mandatory) if any(s in combined for s in ("kofax", "abbyy", "adobe scan", "scansnap", "camscanner")): return "scanner_software" return "unknown_source"
Обнаружение несовершенно: в PDF-файле Word, обработанном с помощью Ghostscript, поле Producer будет перезаписано, а редкие значения Producer попадут в unknown_source . На смешанном корпусе документов, отсканированных контрактов, распечатанных в браузере отчетов и экспортированных из Office файлов примерно девять из десяти PDF-файлов попадают в нужную категорию при первом прочтении. Этого достаточно для управления маршрутизацией. Мы предоставляем как предполагаемую метку source_software , так и исходные строки creator_raw / producer_raw , чтобы последующие правила могли вносить необходимые корректировки.
Остальная часть статьи представлена в двух демонстрационных PDF-файлах: статья «Внимание — это все, что вам нужно» (Vaswani et al. 2017; лицензия на неисключительное распространение arXiv, указанная на странице аннотации arXiv) и документ NIST Cybersecurity Framework 2.0 (CSWP-29; работа правительства США, общественное достояние в США, см. заявление NIST об авторских правах). Детектор распределяет их по двум разным категориям:

1.2. Оригинальное оглавление
PyMuPDF предоставляет доступ к структуре документа через doc.get_toc() , которая возвращает список триплетов [level, title, page] . build_toc_df оборачивает этот список и добавляет parent_idx и breadcrumb , чтобы иерархию можно было запрашивать.
Если применить его к статье «Внимание — это все, что вам нужно» (Vaswani et al. 2017; лицензия на неисключительное распространение arXiv, указанная на странице аннотации arXiv), вы получите реальную трехуровневую структуру:

breadcrumb и вычисляемым end_page – Изображение предоставлено авторомЕсли документ содержит оглавление, мы рассматриваем его как заявленную структуру: документ сам описывает, как он организован.
Когда документ не содержит оглавления (большинство отсканированных документов и быстрых экспортов), get_toc() возвращает пустой список. Восстановление оглавления на основе типографических сигналов (большие жирные строки, схемы нумерации) — это отдельная задача, выходящая за рамки данной статьи.
1.3. Другие заявленные объекты недвижимости
Шифрование ( doc.is_encrypted , doc.needs_pass ), поля форм ( doc.is_form_pdf ), цифровые подписи, даты создания и изменения. Все это легко читается. Некоторые данные важны для маршрутизации при анализе (зашифрованные PDF-файлы требуют обработки); большинство важны на уровне корпуса (версионирование, аудит, контроль доступа) и рассматриваются в статьях, посвященных корпусу (статьи 15-20).
2. Что содержится на каждой странице
После прочтения метаданных мы переходим к анализу страниц. Содержание — это истина: когда PDF-файл заявлен как экспорт в Word, но каждая страница — это отсканированный документ, который кто-то вставил в Word и экспортировал заново, это можно определить только по содержанию. Метаданные говорят одно, а ограничивающие рамки — другое. Мы верим ограничивающим рамкам.
Для каждой страницы мы извлекаем элементы контента в порядке приоритета.

2.1. Текст и режим рендеринга
Текст — наиболее важный результат работы. Естественная единица измерения — строка: строка содержит текст, его ограничивающую рамку (прямоугольник, который её заключает на странице), преобладающие типографические элементы (шрифт, размер, полужирный, курсив, цвет) и важный параметр — режим рендеринга : код уровня PDF, который указывает, был ли текст написан изначально или незаметно добавлен слоем OCR поверх отсканированного изображения.
raw = page.get_text("rawdict") native_chars = 0 ocr_chars = 0 for block in raw["blocks"]: if block["type"] != 0: continue for line in block["lines"]: for span in line["spans"]: if span.get("render_mode", 0) == 3: ocr_chars += len(span["text"]) else: native_chars += len(span["text"])
Режим рендеринга 3 означает, что текст отображается невидимо: это слой, который программное обеспечение OCR размещает под изображением страницы, чтобы отсканированное изображение стало доступным для поиска. Текст присутствует, но только в виде скрытых символов. Важно отличать режим рендеринга 3 от исходного текста: это единственный надежный способ узнать, есть ли на отсканированной странице уже пригодный для поиска слой или ее необходимо повторно распознать с помощью OCR.
Идем дальше: когда типографика меняется в пределах одной строки (жирное слово в середине предложения, цветной заголовок, повернутая метка), для ее фиксации необходимо перейти на уровень фрагмента текста. Мы рассматриваем извлечение на уровне фрагмента текста в разделе 3 этой статьи, поскольку это необходимо на некоторых последующих этапах (обнаружение заголовков, агрегирование списков по длинным ответам).
2.2. Изображения и размещение на всю страницу
Изображения занимают второе место, поскольку они часто содержат текст или важную визуальную информацию, которую в противном случае конвейер RAG потерял бы. Логотипы идентифицируют сторону, выпустившую данные. Схемы описывают системы. Фотографии документируют доказательства. Таблицы, экспортируемые в виде изображений, содержат данные.
Для каждого встроенного изображения мы записываем отображаемый ограничивающий прямоугольник (в точках PDF), его внутренние размеры (в пикселях) и хэш содержимого для дедупликации. Изображение также извлекается и сохраняется (в S3 или локальном хранилище), чтобы последующие этапы могли его обрабатывать.
Распространенная ошибка: page.get_images() возвращает внутренние размеры каждого изображения, а не область, отображаемую на странице. Для вычисления истинного покрытия используйте page.get_image_info() , которая возвращает ограничивающий прямоугольник в точках PDF, как он отображается.
page_area = page.rect.width * page.rect.height max_coverage = 0.0 for info in page.get_image_info(): bbox = info["bbox"] img_area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) max_coverage = max(max_coverage, img_area / page_area) has_full_page_image = max_coverage >= 0.95
Страница, на которой одно изображение занимает ≥ 95% поверхности, с очень высокой вероятностью является отсканированной. Порог в 95% является эмпирическим: он допускает небольшие поля, которые сканер добавляет по краям страницы, не выявляя страницы, которые законно используют большое главное изображение внутри макета. Значения от 90% до 99% работают на практике; ужесточите порог, если в вашем корпусе много обложек, занимающих всю страницу, и ослабьте его, если сканеры сильно обрезают изображение.
2.3. Векторные таблицы
Таблицы не разбиваются на блоки, как сплошной текст. Наивная линеаризация разрушает семантику ячеек. На этапе синтаксического анализа отмечается их наличие и местоположение; фактическое структурированное извлечение происходит на последующем этапе адаптивного синтаксического анализа, который переходит к механизму, учитывающему структуру, когда предположение Фитца о строке кажется ненадежным.
PyMuPDF (начиная с версии 1.23) обнаруживает векторные таблицы, то есть таблицы, построенные из нарисованных линий в сочетании с исходным текстом, с помощью функции page.find_tables() . Вызов возвращает объект TableFinder список .tables которого содержит одну запись для каждой обнаруженной таблицы: tables = page.find_tables(); n_tables = len(tables.tables) .
Для отсканированных таблиц, отображаемых в виде изображений, find_tables() не сработает. В этом случае для обнаружения требуются визуальные инструменты (Camelot, Docling, PaddleStructure), что выходит за рамки данной статьи.
2.4. Столбцы: левый, правый, одиночный, многоколоночный
Обнаружение столбцов — сложная задача. Двухколоночная структура нарушает наивный порядок чтения: в исследовательской работе, проанализированной без учета столбцов, выдается строка 1 первого столбца, затем строка 1 второго столбца, затем строка 2 первого столбца и так далее, встраивая предложения из разных столбцов в шум. Три и более столбцов делают любую разумную эвристику ненадежной.
Прагматичным решением будет указать горизонтальную позицию каждой строки, а не пытаться восстановить идеальный порядок чтения. Мы добавляем поле column_position в line_df с четырьмя значениями:
-
single: страница имеет одну колонку. -
left/right: страница имеет две колонки; строка попадает в одну из них. -
multi: страница имеет три или более столбцов; мы отмечаем это, вместо того чтобы гадать.
Обнаружение кластеризует левый край каждой строки вдоль оси x: в line_df этот край обозначен как bbox_x0 , координата x, с которой начинается строка. Страница, где каждая строка начинается примерно в одной и той же горизонтальной полосе, является одноколоночной. Страница с двумя четкими полосами является двухколоночной, и мы разделяем строки по тому, в какую полосу они попадают. На странице 4 статьи об Attention мы получим действительные числа в разделе 4.2: x0 ≈ 148 для левой колонки, x0 ≈ 364 для правой.
def assign_column_positions( line_df: pd.DataFrame, gap_threshold: float = 80.0, min_cluster_fraction: float = 0.10, ) -> pd.DataFrame: """Add a `column_position` field: single / left / right / multi.""" out = line_df.copy() out["column_position"] = "single" for _, sub in line_df.groupby("page_num"): x0_values = sub["x0"].tolist() if not x0_values: continue clusters = _cluster_x0(x0_values, gap_threshold) sig = _significant_clusters(clusters, len(x0_values), min_cluster_fraction) n_cols = max(1, len(sig)) if n_cols == 1: continue if n_cols == 2: c1_center = sum(sig[0]) / len(sig[0]) c2_center = sum(sig[1]) / len(sig[1]) split = (c1_center + c2_center) / 2 left_idx = sub.index[sub["x0"] < split] right_idx = sub.index[sub["x0"] >= split] out.loc[left_idx, "column_position"] = "left" out.loc[right_idx, "column_position"] = "right" else: out.loc[sub.index, "column_position"] = "multi" return out
Значение gap_threshold по умолчанию равно 80 PDF-пунктам (1 PDF-пункт = 1/72 дюйма, следовательно, 80 ≈ 2,8 см). Это типичная ширина промежутка между столбцами в статье в стиле NeurIPS или в двухколоночном документе с изложением политики. Более узкий промежуток, скорее всего, будет отступом абзаца, а не разрывом столбца.
Зачем вообще нужны поля «слева» и «справа»? Этот параметр оправдан в случае структурированных данных на странице, где позиция является схемой . В счетах-фактурах адрес отправителя находится в верхнем левом углу, адрес клиента — в верхнем правом (или наоборот, в зависимости от шаблона). Запрос на извлечение «блока клиента» из правой половины первой страницы гораздо естественнее, чем запрос на точное указание блока данных. Аналогичная схема используется в формах, выписках и договорах с блоком заголовка. После добавления поля в line_df , последующие этапы могут фильтровать данные по column_position == "right" как и при любом другом запросе к таблице.
Пользователь также может указать на него напрямую. Операторы, знакомые со своими документами, скажут: «Ответ находится в левой колонке» или «Номер полиса находится справа». Это предложение представляет собой запрос к column_position , а не задачу компьютерного зрения.
Эта метка оправдана при наличии двух колонок. При наличии трех и более колонок понятие «слева или справа» теряет смысл, и мы помечаем страницу multi », а не гадаем. Газеты, объемные справочники и страницы с боковыми полями — это те случаи, за которыми следует следить. Когда на важной странице появляется column_position == "multi" , это сигнал к передаче запроса парсеру, учитывающему структуру страницы.
Здесь кроется частый сбой в «минимальных» конвейерах RAG. Автор тестирует документ Word ( column_position == "single" везде), поиск работает, затем клиент отправляет двухколоночный годовой отчет, и система начинает возвращать предложения, обрезанные пополам. Ошибка, похоже, связана с проблемой генерации («модель не может читать»); причина — в проблеме синтаксического анализа (строки изначально не были расположены в правильном порядке).
2.5. Классификация страниц
На основе собранных сигналов для каждой страницы, каждая страница получает основной тип (взаимоисключающие) и дополнительные флаги (независимые логические значения).
Основные типы:

Дополнительные флаги описывают содержимое страницы независимо от её типа:
-
has_text/has_native_text/has_ocr_layer: любой присутствующий текст; любой исходный (не OCR) текст; любой невидимый слой OCR. -
has_image/has_full_page_image: любое встроенное изображение; одно изображение, занимающее ≥ 95% страницы. -
has_vector_table: обнаружена как минимум одна таблица с помощьюpage.find_tables()(строки + исходный текст, не преобразованный в изображение). -
has_vector_graphics: страница содержит нарисованные контуры, которые НЕ являются векторной таблицей (диаграммы, схемы, декоративные фигуры, математические фигуры). Стоит отметить, потому что это содержимое PDF-файла, которое текстовый экстрактор воспринимает как ничто.
Разделение типа и флагов позволяет нам учитывать различные критерии: «все страницы с векторной таблицей» независимо от типа, «смешанные страницы, содержащие также таблицу» и так далее.
Классификатор использует объект PageFeatures : подмножество сигналов для каждой страницы, необходимых для принятия решения. Показатель text_quality_score в этом объекте представляет собой соотношение 0–1: 0 означает, что текст на странице искажен (высокая доля нераспознанных символов), 1 — чистый исходный текст. Адаптивный каскад формирует его полностью из исходных сигналов; здесь это всего лишь один из входных параметров для классификатора:
@dataclass class PageFeatures: char_count: int n_fonts: int n_images: int has_full_page_image: bool native_chars: int ocr_chars: int text_quality_score: float
В этой статье представлены три представления «полей уровня страницы». Схема (диаграмма page_df ниже в разделе 3.3) перечисляет все поля, на которые ориентирована модель данных. PageFeatures — это подмножество, которое читает classify_page . Текущий пример page_df представляет собой основную тройку, которую пакет создает сегодня: аддитивные флаги из схемы добавляются постепенно по мере того, как последующие этапы запрашивают их.
Сама логика классификации довольно краткая:
def classify_page(features: PageFeatures) -> str: if features.char_count < 10 and features.n_images == 0: return "empty" if features.n_fonts == 0 and features.has_full_page_image: return "scanned" if (features.has_full_page_image and features.ocr_chars > features.native_chars and features.ocr_chars > 50): return "scanned_ocr_good" if features.text_quality_score >= 0.7 else "scanned_ocr_bad" if features.has_full_page_image and features.native_chars > 50: return "mixed" if features.n_fonts > 0 and features.native_chars > 0: return "native_with_image" if features.n_images > 0 else "native" return "unknown"
Решающие сигналы носят структурный, а не статистический характер : заявленные шрифты, режим рендеринга, охват отображаемого изображения. Мы никогда не полагаемся только на пороговые значения количества символов для определения того, является ли текст нативным или отсканированным. Страница с тремя строками текста, написанная нативно, всё равно остаётся нативной.
Идем дальше: оценка качества распознавания текста (используемая выше
text_quality_score) заслуживает отдельного рассмотрения. Двумя надежными показателями являются доля символов замены Unicode и доля слов, найденных в словаре. Следует избегать списков «подозрительных символов», таких как ●◦•; в отформатированных документах это вполне допустимые маркеры списка. Полный алгоритм оценки — тема для отдельного обсуждения.
3. Семантическая зона parsing_summary: один вызов LLM, оценка по системной подсказке.
В разделах 1 и 2 были рассмотрены NIST CSF и статья Attention, обе богатые структурными сигналами. В разделе 3 рассматривается тип документа, где одна лишь структура ничего не решает: одностраничное резюме. В качестве примера используется вымышленное резюме Сары Митчелл, аналитика данных.
Сигналы из разделов 1 и 2 — это всё, что может сгенерировать детерминированный парсер за несколько секунд без вызова модели. Они говорят нам, что это за документ и как он структурирован. Они не говорят нам, о чём он. Два одностраничных документа с одинаковым количеством страниц, одинаковой одноколоночной структурой и одним и тем же генератором word_export всё равно будут отличаться по каждому вопросу, который будет задан при поиске.
Краткое прозаическое резюме заполняет этот пробел. Один вызов LLM во время синтаксического анализа, которому были переданы первые одна-две страницы, запрашивал три-четыре предложения, указывающие тип документа, основную тему и содержащиеся в нем поля. Около двухсот токенов. Кэшируются навсегда, поскольку синтаксический анализ выполняется один раз для каждого документа. Результат попадает в три поля одного и того же словаря уровня документа ( parsing_summary ): doc_type , typical_fields и summary .
Если пропустить это очищенное резюме через парсинг, семантическая зона parsing_summary будет выглядеть следующим образом:
{ "doc_type": "resume", "typical_fields": ["name", "email", "phone", "experience", "education", "skills", "languages"], "summary": "One-page resume of Sarah Mitchell, a Data Analyst based in London with about four years of experience. Lists positions at Northwind Retail and Brightwave Insurance, a BSc in Statistics from Leeds, and skills in Python, SQL, BigQuery and Power BI. Standard CV sections: Summary, Experience, Education, Skills." }
Вставленная в системную подсказку парсера вопросов, эта команда исправляет ошибку «как зовут?» из вступительного вопроса. Теперь парсер видит, что этот документ посвящен Саре Митчелл, еще до того, как увидит вопрос пользователя. Имя больше не является неоднозначным словом, обозначающим роль, требующим буквального соответствия. Парсер знает, что имя кандидата — Сара Митчелл, и направляет вопрос в этом направлении.
Те же три поля работают для каждого вопроса в одном и том же документе. Вопрос «Где она работала?» теперь имеет референт. Вопрос «Какой у нее набор технологий?» соответствует разделу «Навыки», указанному в typical_fields . Количество страниц сохраняется в том же словаре: «сводка страницы 1» в одностраничном резюме становится «сводкой всего документа», поиск пропускается, генерация считывает полное содержимое.
Форма поля summary важнее его длины. Вот несколько рабочих правил:
- Три-четыре предложения , простой язык, фактический стиль изложения. Никакого маркетингового подтекста («блестящее резюме с обширными достижениями» отравляет каждый последующий ответ заявлениями, которые затем будет распространять парсер).
- Откройте документ, указав тип документа и основную тему : «Одностраничное резюме Сары Митчелл, аналитика данных…». Парсер использует первую именную группу для уточнения значения слов, обозначающих роль, таких как имя, должность, работодатель.
- Перечислите стандартные разделы, если они существуют: «Стандартные разделы резюме: Краткое описание, Опыт работы, Образование, Навыки». Парсер использует это для сопоставления тем вопросов с областями поиска.
- Придерживайтесь фактов, которые читатель может проверить на первой или второй странице. Не делайте заявлений о содержании, с которым не сталкивался автор магистерской диссертации.
Набор вымышленных резюме одинаковой структуры (одна-две страницы, кандидат вверху, разделы внизу), но с разным оформлением и качеством содержания, подчеркивает важность этого подхода. Краткое описание типа «резюме , , с » подходит для всех резюме. Краткое описание, затрагивающее варианты отображения («двухколоночный макет с цветной боковой панелью»), слишком сильно подстраивается под один файл и некорректно отображается в следующем.
Это тот фрагмент кода, который превращает метафору беглого взгляда на документ в нечто, что может использовать чат-бот. Детерминированные сигналы из разделов 1 и 2 определяют, как производить синтаксический анализ. Семантическая зона parsing_summary указывает, что именно было проанализировано. Вместе они образуют словарь уровня документа, который читает каждый последующий блок, начиная с системной подсказки парсера вопросов.
Все это отображается в Enterprise Document Intelligence, настольном приложении, которое я разрабатываю. На скриншоте ниже открыто то же самое вымышленное резюме, с отображенными и выделенными на странице полями контекста документа: имя кандидата, целевая должность, опыт работы. Краткое описание, написанное один раз во время анализа, управляет этой панелью.

Заключение
PDF-файл представляет собой два документа, расположенных друг над другом: объявленные сигналы (метаданные, собственное оглавление, исходный код) и содержимое страницы (текст или отсканированный текст, изображения, таблицы, столбцы, профиль страницы). Парсер считывает их в указанном порядке и доверяет содержимому, если данные не совпадают. Рядом с ними в том же словаре parsing_summary находится краткое поле с кратким summary , написанное программистом, оплачиваемое один раз за документ и кэшируемое, и парсер вопросов считывает его как часть системного запроса при каждом вызове.
Каждый сигнал, сохраненный во время парсинга, становится столбцом, который считывает остальная часть конвейера. Каждое решение на уровне страницы направляет страницу к соответствующему обработчику: страницы с чистым текстом проходят через OCR-skip, страницы с большим количеством таблиц — через путь структурированного извлечения, а страницы с несколькими столбцами получают порядок чтения с учетом столбцов. Разница между парсером, который передает плоскую строку, и парсером, который передает то, что может запросить последующий код, заключается именно в тех сигналах, которые он счел нужным записать.
В следующей статье («Прекратите возвращать плоский текст из PDF: реляционная структура, необходимая RAG») будут показаны восемь DataFrames, которые парсер генерирует на основе этих сигналов, с демонстрацией на двух реальных документах. Эти же DataFrames являются входными данными, которые минимальный конвейер RAG обрабатывает от начала до конца, и они входят в более широкую серию статей «Интеллектуальное управление корпоративными документами».
Источники и дополнительная литература
Ранее в серии:
- От регулярных выражений до моделей машинного зрения: какой метод RAG подходит для какой задачи — подход к выбору метода синтаксического анализа для каждого документа.
- Анализ документов — введение к серии — четыре основных принципа, начиная с анализа.
- Переранжировщики тоже не волшебство: когда слой кросс-кодировщика оправдывает себя — слой переранжирования, который запускается после парсинга.
Описанный в этой статье парсер имеет ту же архитектуру, что и Docling (Auer et al., Docling Technical Report, IBM Research 2024): определение макета, TableFormer, порядок чтения. Извлечение таблиц без границ использует модель из работы Smock et al. (PubTables-1M / Table Transformer, CVPR 2022). Таксономия классов страниц построена на той же базовой модели, что и в работе Pfitzmann et al. (DocLayNet, KDD 2022). В статье добавлен проход определения режима рендеринга (нативный / отсканированный / смешанный) с оценкой качества OCR. Парсер создает реляционный набор таблиц ( line_df , page_df , image_df , toc_df , object_registry , cross_ref_df , span_df , а также словарь parsing_summary ); При последующем извлечении, генерации и аннотировании PDF-файл не считывается повторно, а запрашиваются DataFrames.
В том же направлении, что и в статье:
- Ауэр и др., Технический отчет Docling, IBM Research 2024 (arXiv:2408.09869). Эталонная архитектура конвейера, описываемого в данной статье: определение макета, TableFormer, порядок чтения, унифицированное представление документа.
- Смок, Песала, Абрахам, PubTables-1M / Table Transformer (TATR), CVPR 2022 (arXiv:2110.00061). Обнаружение таблиц и распознавание структуры на основе машинного зрения; модель, лежащая в основе большинства современных парсеров таблиц.
- Пфицманн и др., DocLayNet, KDD 2022 (arXiv:2206.01062). Эмпирическая базовая модель для таксономии классов страниц и эталонных показателей обнаружения макета.
- Ло и др., PaperMage, демонстрации EMNLP 2023. Соответствует разделению на индексирование и чтение (анализ для поиска не является анализом для генерации ответов).
Другой ракурс, другой контекст:
- Фейсс и др., ColPali: Эффективный поиск документов с использованием языковых моделей визуального восприятия, 2024 (arXiv:2407.01449). Поиск визуального восприятия на основе изображения страницы. Контекстом является поиск, где изображение страницы является артефактом, без этапа преобразования в таблицы. В этой статье в качестве основы используются DataFrames, привязанные к ограничивающим рамкам.
- Ван и др., DocLLM: Генеративная языковая модель с учетом компоновки для понимания мультимодальных документов, JPMorgan 2024 (arXiv:2401.00908). Языковая модель с учетом компоновки, которая считывает PDF-файл напрямую без явного блока реляционного анализа. Тот же подход, что и у ColPali; отличается от реляционного артефакта, доступного для запросов, описанного в этой статье.
- Ким и др., OCR-free Document Understanding Transformer (Donut), ECCV 2022 (arXiv:2111.15664). Сквозное понимание документов без OCR; полезный контраст с этапом оценки качества OCR, который в этой статье добавляется к обнаружению в режиме рендеринга.
Кежан Ши Посмотреть все Кежан Ши
Источник: towardsdatascience.com
Похожие записи
Похожие записи
Иммунные молекулы могут влиять на настроение
02.07.2025
Криптографы добиваются абсолютной секретности с помощью несовершенных устройств.
20.03.2026
По сообщениям, в iOS 27 Apple Intelligence позволит выбирать сторонние модели искусственного интеллекта.
31.05.2026Подписка на рассылку
Получайте свежие новости и идеи на почту. Без спама — только самое интересное.
Нажимая «Подписаться», вы соглашаетесь с политикой конфиденциальности.
