Як за допомогою ШІ автоматизувати заповнення PDF-форм

💡 Усі статті, обговорення, новини про AI — в одному місці. Приєднуйтесь до AI спільноти!

Привіт! Мене звати Діана, я Junior Software Developer в продуктовій компанії Tarta.ai. За останні три місяці я занурилася в роботу над технологією, яка має на меті полегшити життя юристам і звичайним людям, котрі витрачають купу часу на заповнення PDF-форм. У цій статті я хочу розповісти про рішення, яке стало відправною точкою для розробки цього проєкту.

Моя мета — поділитися своїм досвідом, розповісти про те, з чого все почалося і як ми дійшли до того, що маємо зараз. Сподіваюся, що ця стаття буде корисною розробникам, які прагнуть автоматизувати рутинні процеси та створювати ефективні інструменти для роботи з документами. Можливо, мій досвід надихне вас на власні ідеї та рішення.

Передумови

Інтерактивні PDF-документи широко використовуються в бізнесі та освіті, а їхнє заповнення вручну може бути справжнім випробуванням. Рутинність цього процесу та ймовірність допущення помилок при ручному введенні даних часто призводять до втрати часу та нервів. Автоматизація може стати справжнім порятунком, значно скоротивши час обробки документів та мінімізувавши кількість помилок.

Ми поставили собі за мету вирішити біль користувачів, які мають справу з PDF-формами, і поставили собі три ключових питання:

  1. Чи існують на цей момент продукти, які вирішують проблеми ручного заповнення таких форм?
  2. Як ми можемо вирішити ці проблеми, і яку додаткову цінність отримає наш потенційний користувач на випадок, якщо виникнуть альтернативні продукти?
  3. Чи можна повністю делегувати заповнення 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-форми. Те, як зберігаються поля форми, напряму впливає на кінцевий результат. І це один з ключових викликів, на котрий я все ще не маю повноцінного вирішення.

Висновок

Отже, підведімо підсумки. У цій статті ми розглянули, як за допомогою ШІ можна автоматизувати заповнення PDF-форм, зокрема на прикладі IRS W-9 та TR-205. Основна ідея полягала в тому, щоб показати, як можна зменшити ручну роботу та помилки, автоматизуючи процеси заповнення документів. Усі необхідні матеріали, використані під час написанні цієї статті, зокрема вихідний Python-код, ви можете знайти на GitHub.

У цьому матеріалі я поділилась з вами маленькою реалізацією великої ідеї. Нехай це спонукає вас робити експерименти та занурюватись у тему автоматизації будь-чого з використанням ШІ. Можливо, когось це також наштовхне на більш серйозні проєкти.

Особисто мені написання цієї статті допомогло усвідомити весь шлях, який пройшов продукт моєї кампанії: від такого невеликого Python-скрипта до цілої системи, яку ми маємо зараз. Так, вона потребує покращень і ще дуже багато роботи попереду, але різниця в якості заповнення дуже помітна. Маленькі кроки здатні призвести до вагомих результатів.

Запрошую вас залишати коментарі, ділитися своїми думками та досвідом. Якщо у вас виникли питання або ви хочете обговорити якісь аспекти статті, пишіть мені!

👍ПодобаєтьсяСподобалось11
До обраногоВ обраному7
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Чудова інтеграція, в знов нічого з AI. Він ніби-то є, але нічим не відрізняється від інтеграції будь якого API. Клікбейт :)

Два питання:
1. як тестується такий функціонал?
2. як вирішуються проблеми з передачею інформації сторонній сервіс (ШІ систему)?

2. OpenAI в своїх terms of use каже, що вони не тренують моделі на даних з апі.

1. Весь алгоритм можна розбити на 3 ключових етапи, кожен з яких тестується окремо: читання PDF, заповнення полів за допомогою ШІ, заповнення PDF. Виявлення помилок та їхнє усунення на будь-якому з етапів гарантуватимуть покращення роботи технології загалом.
2. Тут є 2 варіанти:
— 1 — покладатися на доброчесність провайдерів моделей ШІ, які обіцяють не використовувати цю інформацію, як зазначив Олександр;
— 2 — піднімати свою LLM, що дозволить мати набагато більше контролю над переданими даними, але це дорого, довго та зміщує фокус уваги з поліпшення технології автозаповнення PDF-форм на постійний розвиток власної моделі ШІ.

Як гарантована детермінованість результату при тестуванні? Я правильно розумію, що кожного разу відсутня гарантія правильності заповнення форм через можливі галюцинації моделі на певному вході?

Так, 100% гарантії не буде, бо ШІ може видавати різний результат навіть на один і той самий вхід. Але ми можемо наблизити впевненість у результаті до 100%. Є 2 напрями, над якими варто працювати паралельно:
1. Треба гарантувати, що на один і той самий вхід модель ЗАВЖДИ видаватиме один і той самий результат, причому такий, що буде нас задовільняти. Це в першу чергу передбачає роботу з промптом: нам треба перевірити його на якомога більшій кількості тестів з різними формами та даними для заповнення і за необхідності модифікувати. Як показує практика, prompt engineering — процес непреривний, до якого ми періодично повертаємось.
2. Треба давати моделі якісні дані на вхід: усе має бути чітко і по справі. Що стосується форми та полей, які ми витягнули — тут простіше, адже у нас більше контролю. Але дані для заповнення можуть бути у будь-якому форматі і містити інформацію різного роду, причому не обов’язково вся ця інформація має бути використана при заповненні. Тобто тут виникає питання, як ми маємо обробляти ці дані, щоб віддавати ШІ тільки справді релевантну інформацію.

Ці дії однозначно покращать якість на виході.
Напевно, це буде чудова система підтримки прийняття рішень або система підтримки певної повторюваної дії.

Проблема ж у цьому, що на жодному етапі ви не можете гарантувати точність висновку просто через сам підхід до вирішення проблеми. Будь-який враппер над OpenAI покладається на OpenAI та Prompt-engineering дозволяє «підлаштувати», але не «гарантувати».

Цей підхід працює чудово в системах, де потрібно «exploration» (copilot як приклад). Але в системах для заповнення державних форм цей підхід все одно потребуватиме роботи оператора, який ретельно перевірить введені дані при суворій забороні покладатися на результат роботи програми, інакше це може призвести до проблем. Особливо при роботі з IRS або іншими державними установами.

Тобто пайплайн може бути наприклад таким:
Ей AI допоможи заповнити -> Оператор перевіряє та є підтвердження того що він перевірив -> Eй AI подивись чи заповнено усе на твій погляд без помилок.

Автоматизація за відсутності детермінованості неможлива у данному контексті.

Але незважаючи на це — це дуже класний експеримент!

Так, я згодна з вами. ШІ у даному випадку навряд чи зможе повністю замінити людину, але стати ефективним помічником, який виконуватиме 90% роботи — цілком.

Підписатись на коментарі