DEV Community

Diana Sihuta for Instafill.ai

Posted on

3 1 1 2 1

How to Automate Filling PDF Forms Using AI

Context

Interactive PDF documents are widely used in business and education, but manually filling them out can be a real challenge. The repetitive nature of this process and the risk of errors during manual data entry often lead to wasted time and frustration. Automation can be a game-changer, significantly reducing document processing time and minimizing errors.

Instafill.ai team aimed to address the pain points of users dealing with PDF forms and we asked ourselves three key questions:

  1. Are there existing products today that solve the problems of manually filling out such forms?
  2. How can we solve these problems, and what additional value would our potential user gain if alternative products already exist?
  3. Can the task of filling PDF forms be entirely delegated to artificial intelligence?

To be honest, the last question remains relevant even now. However, the rapid advancement of large language models allows us to confidently bet on reducing human involvement in monotonous PDF form filling to zero. This idea forms the foundation of the solution I will discuss further in this article.

Solution Architecture: From Reading PDFs to Filling Forms Using AI

Let’s get to the core. The main concept of the solution involves:

  • Reading the PDF form — extracting text and form fields.
  • Using an artificial intelligence model to analyze and populate the fields.
  • Updating the PDF file with new values.

Overall, nothing too complicated, right? This can be implemented using any programming language, but we chose Python specifically because it’s flexible, versatile, and efficient for writing code and conducting experiments. After all, we constantly need to test new hypotheses and make quick decisions based on the results. By the end, you’ll also have a script ready to use on your PDF forms.

Implementation and Step-by-Step Instructions

1. Reading the PDF

The script starts by reading the PDF file to extract its text and form fields. The text from the PDF document provides the AI model with general information about the form and its purpose. This sets the context in which the AI model will operate to fill in the required fields.

  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
Enter fullscreen mode Exit fullscreen mode

Equally important is extracting as much information as possible about these fields and saving it in a format understandable to the AI model. This improves the quality of the output because the more context and details we provide to the AI, the higher the likelihood of generating the desired result. Large language models tend to always return some output, but the quality can be questionable. Our goal is to minimize the frequency of hallucinations.

  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
Enter fullscreen mode Exit fullscreen mode

2. Delegating the Work to AI

Before using the AI to populate the form fields, the script generates a prompt. The quality of this prompt determines whether the "magic" happens. The prompt contains general instructions and accepts PDF components (extracted in the previous step) and unstructured data as parameters. This data is the source of information needed to fill the form. For simplicity, the script reads it from a text file, but in general, it could be any format: PDF, DOCX, JPG, etc.

  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.
    """
Enter fullscreen mode Exit fullscreen mode

We chose OpenAI and the GPT-4o model as the AI provider. It is sufficiently intelligent and fast. You can experiment with other providers and models, but remember to tailor the prompt for each LLM. Prompt engineering is critical at this stage. After the API call (shown below), we’ll have a dictionary of <field name>:<field value>, ready for the next step.

  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)
Enter fullscreen mode Exit fullscreen mode

3. Filling the PDF

The final touch is populating the PDF with the values the AI has generated for the form fields.

  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
Enter fullscreen mode Exit fullscreen mode

Testing the Solution

First and foremost, our product targets a U.S. audience, so as an example, I used the IRS W-9 form — a common document in the United States used to collect taxpayer identification information. This form is often required for freelancing, contractual agreements, and various financial transactions. Given its widespread use, it serves as an ideal example to demonstrate how our solution works.

For filling out this form, we used the following test data:

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
Enter fullscreen mode Exit fullscreen mode

Now for the most interesting part: let’s look at the filled-out form and analyze the quality of the solution’s work.

IRS W-9 filled with AI

We see that all fields are populated. Ignoring quality for now, our initial conclusion is that the generative AI handled its task. Let’s dig deeper into what it generated: there are clear successes and failures. Some fields (highlighted in green) are filled correctly, while others (highlighted in red) look like someone typed them blindly. Let’s explore why this happened. Here are some of the fields extracted from the PDF form (only a subset is shown; others follow the same pattern):

{
    "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
}
Enter fullscreen mode Exit fullscreen mode

The field name is a critical parameter for the AI when filling in values. However, in this case, we face a problem: the names lack sufficient information to unambiguously determine the expected values for these fields. This explains the unsatisfactory results.

Let’s try filling out another form — TR-205 (Request for Trial by Written Declaration). This document is used in California to submit a request for a trial by written declaration. It allows drivers to contest traffic violations without appearing in court.

We used the following test data to fill it out:

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
Enter fullscreen mode Exit fullscreen mode

And here’s the result:

TR-205 filled with AI, page 1
TR-205 filled with AI, page 2

Notice that the script performed better with this form. All provided information landed in the “correct” fields. As you might guess, the difference lies in how these fields are stored in the PDF. Compare them to the previous example:

{
    "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
}
Enter fullscreen mode Exit fullscreen mode

Thus, we discovered that the quality of form filling heavily depends on the internal structure of the input PDF form. How the form fields are stored directly impacts the final result. This remains one of the key challenges we still don’t have a complete solution for.

Conclusion

Let’s summarize. In this article, we explored how AI can automate filling PDF forms, using the IRS W-9 and TR-205 as examples. The core idea was to demonstrate how manual labor and errors can be reduced by automating document processing. All materials used in this article, including the original Python code, are available on GitHub.

Feel free to leave comments, share your thoughts, or discuss your own experiences. If you have questions or want to explore any aspects of this article further, you are welcome!

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (1)

Collapse
 
agamanyuk profile image
Oleksandr Gamanyuk

Thanks for the guide!

Playwright CLI Flags Tutorial

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • 0:56 --last-failed
  • 2:34 --only-changed
  • 4:27 --repeat-each
  • 5:15 --forbid-only
  • 5:51 --ui --headed --workers 1

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Click on any timestamp above to jump directly to that section in the tutorial!

Watch Full Video 📹️

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️