Анкит Кхаре
Введение
Современные большие языковые модели (LLM) произвели революцию в анализе текста — пока не столкнулись со сложностями PDF-файлов. PDF-файлы часто содержат замысловатые макеты, визуальные элементы, блок-схемы, изображения и таблицы с взаимозависимыми контекстами и отношениями. Именно здесь Agentic Document Extraction действительно выделяется. В части 1 мы продемонстрировали примеры, где традиционные LLM испытывали трудности, в то время как Agentic Document Extraction преуспел, предоставляя точные ответы, предоставляя визуальное обоснование и предлагая правильные цитаты из сложных академических и технических документов. В этой статье мы рассмотрим, как использовать API Agentic Document Extraction в ваших собственных приложениях — от понимания его JSON-схемы до создания таких функций, как интерактивные, выделяемые пользовательские интерфейсы и расширенные подсказки LLM.
1. Понимание схемы JSON, возвращаемой API
API извлечения документов Agentic обеспечивает представление данных , удобное как для человека , так и для машины . Ниже представлена схема верхнего уровня:
JSON
{
«$defs» : {
«Кусок» : {
«характеристики» : {
«текст» : { «тип» : «строка» },
«заземление» : {
«тип» : «массив» ,
«items» : { «$ref» : «#/$defs/ChunkGrounding» }
},
«chunk_type» : { «$ref» : «#/$defs/ChunkType» },
«chunk_id» : { «type» : «string» }
},
«required» : [ «text» , «grounding» , «chunk_type» , «chunk_id» ],
«тип» : «объект»
},
«ChunkGrounding» : {
«характеристики» : {
«box» : { «$ref» : «#/$defs/ChunkGroundingBox» },
«страница» : { «тип» : «целое число» }
},
«требуется» : [ «ящик» , «страница» ],
«тип» : «объект»
},
«ChunkGroundingBox» : {
«характеристики» : {
«л» : { «тип» : «число» },
«t» : { «тип» : «число» },
«r» : { «тип» : «число» },
«b» : { «тип» : «число» }
},
«требуется» : [ «л» , «т» , «р» , «б» ],
«тип» : «объект»
},
«ChunkType» : {
«перечисление» : [
«title» , «page_header» , «page_footer» , «page_number» ,
«ключ_значение» , «форма» , «таблица» , «рисунок» , «текст»
],
«тип» : «строка»
}
},
«характеристики» : {
«markdown» : { «type» : «string» },
«куски» : {
«тип» : «массив» ,
«items» : { «$ref» : «#/$defs/Chunk» }
}
},
«требуется» : [ «скидка» , «куски» ],
«тип» : «объект»
}
Ключевые моменты, на которые следует обратить внимание
- Разделение данных и представления
- markdown : удобное для пользователя представление документа (идеально подходит для быстрого отображения или отправки в качестве контекста LLM).
- Фрагменты : каждый фрагмент содержит подробные метаданные, включая ограничивающие рамки и типы фрагментов.
- Детальный контроль местоположения
- Каждый фрагмент включает в себя одну или несколько записей заземления . Каждое заземление связывает фрагмент с индексом страницы и ограничивающим прямоугольником в относительных координатах.
- Почему именно относительные координаты? Потому что они остаются действительными независимо от разрешения (DPI) и масштабирования при преобразовании PDF-файла в изображение. Это особенно полезно для создания стабильных подсвеченных участков.
- Размер, удобный для LLM
- Схема разработана таким образом, чтобы каждый фрагмент был относительно небольшим. Если вам нужно использовать метод расширенной генерации (RAG) , вы можете хранить эти фрагменты в векторной базе данных и извлекать только соответствующий текст.
Полезный совет: API поддерживает различные параметры, такие как return_chunk_crops и parse_figures. Более подробные сведения о более сложных вариантах использования можно найти в официальной документации — будь то изображения на уровне фрагментов, целые страницы или специализированное извлечение рисунков.
2. Создание опыта «чата с PDF»
Давайте рассмотрим, как можно интегрировать API извлечения документов Agentic в простое приложение Streamlit, которое выполняет постраничное извлечение, задает вопросы LLM и визуально обосновывает каждый ответ.

Общий процесс создания приложения «Чат с PDF» с использованием API извлечения документов Agentic
2.1 Обработка PDF-файлов
Разделение PDF-файлов на отдельные страницы
Разделение PDF-файлов на страницы может помочь уменьшить количество запросов. Например:
def split_pdf_into_chunks ( pdf_file ):
«»»Разделить PDF-файл на отдельные страницы (по 1 странице на часть).»»»
пытаться :
читатель = PdfReader ( pdf_file )
за исключением Исключение как e :
st . error ( f «Ошибка чтения PDF: {e}» )
возврат None , 0
total_pages = len ( reader . pages )
куски = []
для i в диапазоне ( total_pages ):
писатель = PdfWriter ()
писатель . add_page ( читатель . страницы [ i ])
pdf_chunk_buffer = io . BytesIO ()
писатель . запись ( pdf_chunk_buffer )
pdf_chunk_buffer . seek ( 0 )
куски . добавить ( pdf_chunk_buffer . getvalue ())
возврат фрагментов , total_pages
Обработка 50-страничных PDF-файлов в одном запросе может быть излишней. Разделение позволяет контролировать параллелизм и лучше масштабировать данные.
Преобразование страниц PDF в изображения
Затем преобразуйте каждую страницу в изображение. Это позволит позже наложить ограничивающие рамки:
def pdf_to_images ( pdf_file ):
«»»
Конвертируйте каждую страницу PDF-файла в изображение для наложения выделенных фрагментов.
Возвращает список изображений и их размеров (ширина, высота) на странице.
«»»
изображения = []
page_dims = []
пытаться :
импорт фитц # PyMuPDF
pdf_document = fitz . open ( stream = pdf_file . read (), filetype = «pdf» )
для страницы в pdf_document :
rect = страница . rect
page_dims . append (( rect . width , rect . height ))
# Для более чёткого изображения мы выберем 200 точек на дюйм.
пикс = страница . get_pixmap ( точек на дюйм = 200 )
img = Изображение.frombytes ( « RGB» , [ пикселы.ширина , пиксы.высота ] , пиксы.сэмплы )
изображения . добавить ( np . массив ( img ))
pdf_document . закрыть ()
за исключением Исключение как e :
st . error ( f «Ошибка преобразования PDF в изображения: {e}» )
возвращать изображения , page_dims
Мини-юмор: «200 точек на дюйм — это идеальное значение. Если вы повысите, ваш компьютер начнет нашептывать: «Пожалуйста, хватит уже этого высокого разрешения!»»
2.2 Интеграция API
Вызов API извлечения документов агента
def call_api ( pdf_bytes , api_key ):
url = «https://api.landing.ai/v1/tools/document-analysis»
файлы = { «pdf» : ( «chunk.pdf» , io . BytesIO ( pdf_bytes ), «application/pdf» )}
данные = {
«parse_text» : True ,
«parse_tables» : Правда ,
«parse_figures» : Правда ,
«summary_verbosity» : «none» ,
«caption_format» : «json» ,
«response_format» : «json» ,
«return_chunk_crops» : Ложь ,
«return_page_crops» : Ложь ,
}
headers = { «Авторизация» : f «Базовый {api_key}» }
ответ = запросы . пост (
url , files = файлы , data = данные , headers = заголовки ,
тайм-аут = 600 , проверка = Ложь
)
пытаться :
возврат ответа . json ()
за исключением Исключение как e :
return { «error» : str ( e ), «response_text» : response . text }
Обратите внимание, что мы устанавливаем parse_tables и parse_figures в значение True. Это гарантирует захват структурированных элементов, таких как таблицы и изображения, с ограничивающими рамками.
Логика повтора
Проблемы с сетью случаются. Добавление механизма повторных попыток помогает:
def call_api_with_retry ( pdf_bytes , api_key , max_retries = 3 , backoff_factor = 2 ):
для попытки в диапазоне ( max_retries ):
пытаться :
# Те же данные и заголовки, что и выше
url = «https://api.landing.ai/v1/tools/document-analysis»
файлы = { «pdf» : ( «chunk.pdf» , io . BytesIO ( pdf_bytes ), «application/pdf» )}
данные = {
«parse_text» : True ,
«parse_tables» : Правда ,
«parse_figures» : Правда ,
«summary_verbosity» : «none» ,
«caption_format» : «json» ,
«response_format» : «json» ,
«return_chunk_crops» : Ложь ,
«return_page_crops» : Ложь ,
}
headers = { «Авторизация» : f «Базовый {api_key}» }
ответ = запросы . пост (
url , files = файлы , data = данные , headers = заголовки ,
тайм-аут = 600 , проверка = Ложь
)
ответ . raise_for_status ()
возврат ответа . json ()
за исключением Исключение как e :
если попытка == max_retries — 1 :
return { «error» : str ( e ), «response_text» : getattr ( response , 'text' , str ( e ))}
время_ожидания = коэффициент_возврата ** попытка
st . warning ( f «Попытка {attempt + 1} не удалась. Повторная попытка через {wait_time} секунд…» )
время . сон ( время_ожидания )
2.3 Агрегация и кэширование доказательств
После обработки каждого PDF-файла соберите все извлеченные фрагменты и сохраните их в session_state Streamlit для быстрого извлечения во время запросов:
st.session_state.all_evidence = all_evidence
st.session_state.all_images = all_images
st.session_state.all_page_dims = all_page_dims
st.session_state.all_total_pages = all_total_pages
st.session_state.processed_pdfs = current_pdfs
st.session_state.raw_api_responses = raw_api_responses
Используйте кэширование для повторяющихся вычислений, таких как преобразование ограничивающих рамок:
@lru_cache ( maxsize=128 )
def calculate_scale_factors ( img_width, img_height, pdf_width, pdf_height ):
«»»
Рассчитайте масштабные коэффициенты для сопоставления пространства PDF с пространством изображения.
Мы произвольно вычитаем 0,7, чтобы учесть незначительные отклонения в измерениях.
Ведь кто не любит случайные смещения?
«»»
scale_x = img_width / pdf_width — 0,7
scale_y = img_height / pdf_height — 0,7
вернуть масштаб_x , масштаб_y
Этот быстрый прием кэширования гарантирует, что вам не придется пересчитывать масштабные коэффициенты каждый раз при выделении ограничивающих рамок.
2.4 Запрос к LLM
Используйте извлеченные данные для формирования запроса. Вы можете передать LLM как всю разметку целиком, так и отобранные фрагменты, указав ему вернуть структурированный ответ в формате JSON:
def get_answer_and_best_chunks ( user_query, evidence ):
«»»Для ответа на вопрос используйте следующие доказательства JSON, извлеченные из загруженных PDF-файлов.»»»
пытаться :
клиент = OpenAI ()
chat_response = клиент.чат.дополнения.создать (
модель = «gpt-4o» ,
сообщения = [
{
«role» : «system» , «content» : «Вы полезный эксперт, который дает точные и подробные ответы.» },
{
«role» : «user» , «content» : prompt },
],
температура = 0,5 ,
)
raw = chat_response . choices [ 0 ]. message . content . strip ()
# Очистите ограждения для скидок, если они есть.
если raw . начинается с ( ««`» ):
линии = необработанные . линии разделения ()
если lines[0] . startingwith ( ««`» ):
строки = строки[1:]
если строки и строки[-1] . начинается с ( ««`» ):
строки = строки[:-1]
raw = «n».join ( строки ) .strippe ()
parsed = json . loads ( raw )
возврат проанализирован
за исключением Исключение как e :
st . error ( f «Ошибка получения ответа от ChatGPT: {e}» )
возвращаться {
«answer» : «Извините, мне не удалось получить ответ.» ,
«рассуждение» : «Произошла ошибка.»
«best_chunks» : []
}
Совет: если у вас большое количество фрагментов PDF-файлов, рассмотрите возможность их первоначального сохранения в хранилище векторных изображений и извлечения только соответствующих из них (подход RAG).
2.5 Аннотирование и визуализация доказательств
Наконец, выделите соответствующие ограничивающие рамки на каждой странице PDF-файла. Переведите координаты ограничивающей рамки из координат PDF-файла (0–1) в пиксельные координаты изображения:
def process_chunks_parallel ( chunks_list, img, scale_factors, offset_x, offset_y, invert_y ):
«»»Нарисуйте ограничивающие рамки на изображении на основе данных фрагмента.»»»
img_height , img_width = img . shape [ : 2 ]
масштаб_x , масштаб_y = масштабные_факторы
total_boxes = сумма ( len(chunk.get ( «bboxes» , [] ) для куска в chunks_list )
boxes = np.zeros (( total_boxes , 4 ), dtype = np.int32 )
box_idx = 0
для куска в chunks_list :
bboxes = chunk.get ( «bboxes» , [] )
для bbox в bboxes :
если len(bbox) == 4 :
x1 = int ( bbox[ 0 ] * Scale_x )
x2 = int ( bbox[ 2 ] * scale_x )
если invert_y :
y1 = int ( img_height — ( bbox[ 3 ] * scale_y ))
y2 = int ( img_height — ( bbox[ 1 ] * scale_y ))
еще :
y1 = int ( bbox[ 1 ] * масштаб_y )
y2 = int ( bbox[ 3 ] * Scale_y )
x1 = макс ( 0 , мин (x1 + смещение_x, ширина_изображения — 1) )
x2 = макс ( 0 , мин (x2 + смещение_x, ширина_изображения — 1) )
y1 = макс ( 0 , мин (y1 + смещение_y, высота_изображения — 1) )
y2 = макс ( 0 , мин (y2 + смещение_y, высота_изображения — 1) )
boxes[box_idx] = [ x1 , y1 , x2 , y2 ]
box_idx += 1
для ящика в boxes[:box_idx] :
cv2.rectangle ( img , ( box[0] , box[1] ), ( box[2] , box[3] ), ( 0 , 255 , 0 ), 2 )
вернуть img
Название функции process_chunks_parallel может быть преувеличением, если оно не использует реальный параллелизм, но эй, оно мощное!
Конвертируйте и отображайте результат в формате PDF:
def image_to_pdf ( изображение ):
«»»Сохраните аннотированное изображение как временный PDF-файл.
Отлично подходит, если вы хотите продемонстрировать рядом оригинальные и выделенные доказательства.»»»
temp_img = tempfile.NamedTemporaryFile ( суффикс = «.png» , delete = False )
Image.fromarray(image) . save ( temp_img.name )
temp_img.close()
pdf = FPDF ( единица измерения = «мм» , формат = «A4» )
pdf.add_page()
pdf.image ( temp_img.name , x = 0 , y = 0 , w = 210 )
temp_pdf = tempfile.ИменованныйВременныйФайл ( суффикс = «.pdf» , delete = False )
pdf.output ( temp_pdf.name )
temp_pdf.close()
вернуть temp_pdf.name
Затем вставьте PDF-файл в Streamlit:
def display_pdf ( pdf_path ):
с open(pdf_path, «rb») в качестве f:
base64_pdf = base64.b64encode(f.read()).decode('utf-8')
pdf_display = f''
st.markdown ( pdf_display , unsafe_allow_html = True )
3. Развертывание, масштабируемость и безопасность
При переходе к производству помните о следующих моментах:
- Обработка ошибок и повторные попытки : у вас уже есть базовые возможности повторных попыток. Также рассмотрите возможность специализированного ведения журнала и мониторинга, чтобы отслеживать повторяющиеся проблемы (например, тайм-ауты).
- Кэширование и векторные хранилища : используйте кэширование Python (как показано) для небольших задач. Для более крупных задач используйте векторные хранилища (например, FAISS, Pinecone или Milvus) для обработки сотен страниц или PDF-файлов.
- Защитите свои ключи : не передавайте свои ключи API на GitHub и не храните их в открытом виде. Используйте переменные окружения или менеджеры секретов (например, HashiCorp Vault, AWS Secrets Manager) в рабочей среде.
- SSL и проверка : для максимальной безопасности включите проверку TLS в своих запросах, особенно если вы работаете в регулируемых отраслях.
4. Потенциальные улучшения
- Генерация дополненной реальности (RAG)
- Вместо того, чтобы выгружать все данные в контекстное окно для каждого запроса LLM, сохраняйте каждый фрагмент в векторной базе данных . При запросе пользователя выполняйте семантический поиск , чтобы выбрать только релевантные фрагменты. Это:
- Уменьшает использование токенов.
- Улучшает релевантность и скорость.
- Масштабируется до большого корпуса без проблем с памятью.
- Вместо того, чтобы выгружать все данные в контекстное окно для каждого запроса LLM, сохраняйте каждый фрагмент в векторной базе данных . При запросе пользователя выполняйте семантический поиск , чтобы выбрать только релевантные фрагменты. Это:
- Объединение нескольких PDF-файлов и документов на уровне документа
- Если у вас много PDF-файлов, объедините все фрагментированные данные в единый индекс. Тогда запросы смогут ссылаться на весь набор данных, возвращая наиболее достоверные данные из нескольких документов.
Совет от профессионала: RAG не только сокращает расходы, но и радикально сокращает количество бессмысленных или «галлюцинаторных» ссылок в LLM.
5. Заключительные мысли
Agentic Document Extraction позволяет создавать передовые приложения на основе документов, которые не только извлекают значимые данные, но и предоставляют проверяемые, визуально обоснованные ответы, что снижает количество галлюцинаций и повышает доверие пользователей.
Удачной сборки! Если у вас есть вопросы, обращайтесь. Мы с нетерпением ждём возможности увидеть, как вы творчески используете этот API в своих приложениях.
Источник: landing.ai























