Як за допомогою ШІ автоматизувати заповнення PDF-форм
Привіт! Мене звати Діана, я Junior Software Developer в продуктовій компанії Tarta.ai. За останні три місяці я занурилася в роботу над технологією, яка має на меті полегшити життя юристам і звичайним людям, котрі витрачають купу часу на заповнення PDF-форм. У цій статті я хочу розповісти про рішення, яке стало відправною точкою для розробки цього проєкту.
Моя мета — поділитися своїм досвідом, розповісти про те, з чого все почалося і як ми дійшли до того, що маємо зараз. Сподіваюся, що ця стаття буде корисною розробникам, які прагнуть автоматизувати рутинні процеси та створювати ефективні інструменти для роботи з документами. Можливо, мій досвід надихне вас на власні ідеї та рішення.
Передумови
Інтерактивні PDF-документи широко використовуються в бізнесі та освіті, а їхнє заповнення вручну може бути справжнім випробуванням. Рутинність цього процесу та ймовірність допущення помилок при ручному введенні даних часто призводять до втрати часу та нервів. Автоматизація може стати справжнім порятунком, значно скоротивши час обробки документів та мінімізувавши кількість помилок.
Ми поставили собі за мету вирішити біль користувачів, які мають справу з PDF-формами, і поставили собі три ключових питання:
- Чи існують на цей момент продукти, які вирішують проблеми ручного заповнення таких форм?
- Як ми можемо вирішити ці проблеми, і яку додаткову цінність отримає наш потенційний користувач на випадок, якщо виникнуть альтернативні продукти?
- Чи можна повністю делегувати заповнення PDF-форм штучному інтелекту?
Правду кажучи, останнє питання актуальне і досі. Але доволі стрімкий розвиток великих мовних моделей дозволяє нам робити велику ставку на те, що участь людини в монотонному заповненні PDF-форм може бути зведена до нуля. На цьому і базується рішення, про яке я розповідаю далі в матеріалі.
Архітектура рішення: від читання PDF до заповнення форми за допомогою ШІ
Переходимо до суті. Основна концепція рішення полягає у:
- читанні PDF-форми — витягуванні тексту та полів форми;
- використанні моделі штучного інтелекту для аналізу та заповнення полів;
- а потім оновленні PDF-файлу новими значеннями.
Загалом, нічого складного, правда? Це можна реалізувати з використанням будь-якої мови програмування, але ми зупинились саме на Python, бо він гнучкий, багатофункціональний та швидкий в контексті написання коду та проведенні різноманітних експериментів. Адже нам постійно треба перевіряти нові гіпотези та ухвалювати швидкі рішення, базуючись на отриманих результатах. Тож на виході ви також отримаєте скрипт, готовий до використання на ваших PDF-формах.
Реалізація та покрокові інструкції
Читання PDF
Скрипт починає роботу з читання PDF-файлу, щоб витягти з нього текст і поля форми. Текст PDF-документу надасть ШІ-моделі загальну інформацію про форму та сферу її застосування. Це задає контекст, в якому ШІ-модель працюватиме, заповнюючи необхідні поля.
{ def extract_pdf_text(pdf_bytes: bytes) -> str: pdf_text = '' pdf_document = fitz.open(PDF_EXT, pdf_bytes) for page_num in range(len(pdf_document)): page = pdf_document.load_page(page_num) text_page = page.get_textpage() pdf_text += text_page.extractText() return pdf_text }
Не менш важливий момент — дістати якомога більше інформації про ці поля та зберегти їх у зрозумілому для ШІ-моделі форматі. Це підвищує якість заповнення, адже що більше контексту та деталей ми надаємо штучному інтелекту, то вища ймовірність, що він згенерує бажаний результат. Великі мовні моделі схильні до того, щоб завжди повертати якийсь результат, але якість таких відповідей може бути сумнівна, тож наша задача мінімізувати частоту виникнення будь-яких галюцинацій.
{ def extract_pdf_fields(pdf_bytes: bytes) -> list[dict]: form_fields = [] pdf_document = fitz.open(PDF_EXT, pdf_bytes) for page_num in range(len(pdf_document)): page = pdf_document.load_page(page_num) widget_list = page.widgets() if widget_list: for widget in widget_list: form_fields.append({ 'name': widget.field_name, 'label': widget.field_label, 'type': widget.field_type_string, 'max_length': widget.text_maxlen }) return form_fields }
Делегуємо всю роботу ШІ
Перед використанням штучного інтелекту для заповнення полів форми скрипт генерує промпт. Саме від якості цього промпту залежить, чи станеться магія. Промпт містить загальну інструкцію і в якості параметрів приймає PDF-компоненти, отримані в попередньому кроці, та дані в неструктурованому форматі. Ці дані — джерело інформації, необхідної для заповнення форми. Для простоти скрипту вони зчитуються з текстового файлу, але у загальному випадку це можуть бути файли будь-якого формату: PDF, DOCX, JPG і т. д.
{ def fill_fields_prompt(pdf_text: str, fields: list[dict], source_info: str) -> str: return f""" You are an automated PDF forms filler. Your job is to fill the following form fields using the provided materials. Field keys will tell you which values they expect: {json.dumps(fields)} Materials: - Text extracted from the PDF form, delimited by <>: <{pdf_text}> - Source info attached by user, delimited by ##: #{source_info}# Output a JSON object with key-value pairs where: - key is the 'name' of the field, - value is the field value you assigned to it. """ }
В якості ШІ-провайдера було обрано OpenAI та модель GPT-4o. Вона достатньо розумна та доволі швидка. Ви можете поекспериментувати з іншими провайдерами та моделями, але не забувайте підганяти промпт під кожну LLM. Prompt engineering на цьому етапі — ваше все. Після наведеного нижче виклику ми будемо мати словник <назва поля>:<значення поля>, який готовий для використання у наступному кроці.
{ def call_openai(prompt: str, gpt_model: str = 'gpt-4o'): response = openai_client.chat.completions.create( model=gpt_model, messages=[{'role': 'system', 'content': prompt}], response_format={"type": "json_object"}, timeout=TIMEOUT, temperature=0 ) response_data = response.choices[0].message.content.strip() return json.loads(response_data) }
Заповнення PDF
Залишився останній штрих — заповнити PDF значеннями, якими штучний інтелект збагатив поля форми.
{ def fill_pdf_fields(pdf_bytes: bytes, field_values: dict) -> io.BytesIO: pdf_document = fitz.open(PDF_EXT, pdf_bytes) for page_num in range(len(pdf_document)): page = pdf_document.load_page(page_num) widget_list = page.widgets() if widget_list: for widget in widget_list: field_name = widget.field_name if field_name in field_values: widget.field_value = field_values[field_name] widget.update() output_stream = io.BytesIO() pdf_document.save(output_stream) output_stream.seek(0) return output_stream }
Тестуємо рішення
Перш за все наш продукт орієнтується на американську аудиторію, тож в якості прикладу я взяла форму IRS W-9 — поширений у США документ, який використовується для збору ідентифікаційної інформації про платника податків. Ця форма часто потрібна для фрілансу, контрактних угод і різних фінансових операцій. З огляду на широке використання цієї форми, вона слугує ідеальним прикладом для демонстрації роботи розглянутого рішення.
В ролі інформації для заповнення цієї форми було використано наведені нижче тестові дані:
{ 1. personal info john smith 222 Victory St, 125 San Francisco, CA, 94111 2. business info BOTMAKERS, LLC has foreign partners account numbers: 1234567890, 0987654321 tin: 123456789 3. requester jane smith 87 Independence St }
Тепер найцікавіше: подивімося на результат заповнення форми та проаналізуємо якість роботи цього рішення.
Бачимо, що всі поля заповнені. На якість поки що не зважаємо і робимо перший висновок, що генеративний ШІ зі своєю задачею впорався. А тепер проаналізуємо, що ж він нам нагенерував: маємо очевидні успіхи та невдачі. Деякі поля (виділені зеленим) заповнені коректно, а решта (виділені червоним) — таке враження, що хтось заплющеними очима натайпав. Пропоную зазирнути глибше й розібратись, чому так сталось. Звертаю вашу увагу на поля, що були витягнуті з PDF-форми (наводжу лише частину з них, всі інші мають аналогічний вигляд):
{ { "name" : "topmostSubform[0].Page1[0].Boxes3a-b_ReadOrder[0].c1_2[0]", "label" : null, "type" : "CheckBox", "max_length" : 0 }, { "name" : "topmostSubform[0].Page1[0].f1_05[0]", "label" : null, "type" : "Text", "max_length" : 0 }, { "name" : "topmostSubform[0].Page1[0].f1_06[0]", "label" : null, "type" : "Text", "max_length" : 0 }, { "name" : "topmostSubform[0].Page1[0].Address_ReadOrder[0].f1_07[0]", "label" : null, "type" : "Text", "max_length" : 0 } }
Назва поля є ключовим параметром для ШІ під час заповнення. Однак у цьому разі ми стикаємося з проблемою: назви не містять достатньо інформації для однозначного визначення очікуваних значень цих полів. Цим і пояснюється незадовільний результат.
Спробуймо заповнити іншу форму — TR-205 (Request for Trial by Written Declaration). Цей документ використовується в Каліфорнії, щоб подати клопотання про розгляд справи за письмовою декларацією. Така можливість дозволяє водіям оскаржувати дорожні порушення без необхідності з’являтися в суді.
Використовуємо такі тестові дані для заповнення:
{ Court: SUPERIOR COURT OF CALIFORNIA 100 NORTH STATE STREET UKIAH CA 95482 BRANCH #23480 Citation: KV19133 Case: 231N07977 Defendant: John Smith 1234 Main Street, Apt. 101 Due date: 07/21/24 Bail amount: $441 Date mailed: 06/21/24 Evidence: - photographs (5) - witness testimony Statement of facts: I increased my speed to avoid a possible traffic accident due to a truck in the middle of the road. Today: 06/21/24 }
І отримуємо такий результат:
Зверніть увагу, що з цією формою скрипт впорався краще. Адже вся інформація, надана для заповнення, потрапила у «потрібні» поля. Як ви могли здогадатись, вся справа у тому, як саме ці поля зберігаються в PDF, тож порівняйте їх з попередніми:
{ { "name" : "TR-205[0].Page1[0].P1Caption[0].CitationNumber[0].CitationNumber[0]", "label" : "CITATION NUMBER:", "type" : "Text", "max_length" : 0 }, { "name" : "TR-205[0].Page1[0].P1Caption[0].CaseNumber[0].CaseNumber[0]", "label" : "CASE NUMBER:", "type" : "Text", "max_length" : 0 } }
Висновок
Отже, підведімо підсумки. У цій статті ми розглянули, як за допомогою ШІ можна автоматизувати заповнення PDF-форм, зокрема на прикладі IRS W-9 та TR-205. Основна ідея полягала в тому, щоб показати, як можна зменшити ручну роботу та помилки, автоматизуючи процеси заповнення документів. Усі необхідні матеріали, використані під час написанні цієї статті, зокрема вихідний Python-код, ви можете знайти на GitHub.
У цьому матеріалі я поділилась з вами маленькою реалізацією великої ідеї. Нехай це спонукає вас робити експерименти та занурюватись у тему автоматизації будь-чого з використанням ШІ. Можливо, когось це також наштовхне на більш серйозні проєкти.
Особисто мені написання цієї статті допомогло усвідомити весь шлях, який пройшов продукт моєї кампанії: від такого невеликого Python-скрипта до цілої системи, яку ми маємо зараз. Так, вона потребує покращень і ще дуже багато роботи попереду, але різниця в якості заповнення дуже помітна. Маленькі кроки здатні призвести до вагомих результатів.
Запрошую вас залишати коментарі, ділитися своїми думками та досвідом. Якщо у вас виникли питання або ви хочете обговорити якісь аспекти статті, пишіть мені!
8 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів