AI Document Analyzer with Gemini

Build a Python CLI tool that uses Google Gemini to return structured JSON analysis.

Introduction

30 Second Summary

Every time you use an AI tool that summarizes your emails or pulls action items from meeting notes, there's a pattern running behind the scenes. Your app sends text to an AI model, the model returns structured data, and your code displays it.

In this project, you will build that exact pattern from scratch using Python and Google Gemini. You'll create a command-line document analyzer that takes any text and returns a validated JSON analysis with summary, sentiment, action items, and urgency level.

What You'll Build

Paste an email or meeting notes into your terminal, hit Enter, and watch Gemini return a clean JSON object with the document's summary, sentiment, action items, and urgency level.

By the end of this project, you'll have:

  • A working tool where you paste any text and instantly see a structured analysis with summary, sentiment, action items, and urgency.
  • Reliable structured JSON responses from the AI every single time, forced into the exact shape your code expects using a Pydantic schema.
  • A reusable API integration pattern you can extend into any AI-powered application.
  • Secret Mission: Add multi-document comparison so you can analyze two texts side-by-side and get a structured comparison report.

Are there any prerequisites?

You'll need basic Python knowledge (variables, functions, loops) and familiarity with running Python scripts in VS Code. No AI or API experience required.

Before We Start

Before we write any code, let's lock in what you are about to build and why it matters for your role.

Set Up Your Gemini API Key and Python Environment

You have a clear picture of what you are building. Now it is time to wire up the tools that make it possible.

Your document analyzer needs two things before it can talk to Google Gemini: a free API key that proves your identity, and the official Python SDK that handles the network calls. In this step, you will set both up and confirm they work together.

In this step, get ready to:
  • Get a free API key from Google AI Studio (no credit card required).
  • Install the google-genai Python package.
  • Verify your setup by printing available Gemini models from the API.
Get a free API key from Google AI Studio

Google AI Studio gives you an API key instantly when you sign in. No credit card, no billing setup, no waiting period.

  • Open your browser and navigate to the Google AI Studio API key page:
  • https://aistudio.google.com/app/apikey
  • Sign in with your Google account if prompted.
  • Click Create API key.
  • In the dialog that appears, select Create API key in new project.
  • Copy the key that appears. Keep it somewhere safe for the next substep.

What is an API key?

An API key is a unique string that identifies your requests to a service. It tells Google which account is calling the Gemini API and enforces usage limits. Your key stays on the free tier unless you explicitly attach a billing account.

Keep your key private

Never commit an API key to a public repository or share it in screenshots. You will store it in an environment variable so it stays out of your code.

Install google-genai and set your API key

Your terminal needs two things: the SDK package and the environment variable holding your key. You will set both up here.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir document-analyzer
cd document-analyzer
  • Confirm the folder exists by running:
ls

You should see the document-analyzer folder listed on your Desktop (it will be empty for now).

Now set your API key as an environment variable so your Python code can access it without hardcoding the value.

macOS

  • Set the environment variable by running this command (replace your-api-key-here with the key you copied from Google AI Studio):
export GEMINI_API_KEY="your-api-key-here"

Windows

  • Set the environment variable by running this command (replace your-api-key-here with the key you copied from Google AI Studio):
$env:GEMINI_API_KEY="your-api-key-here"

Why an environment variable?

Environment variables keep sensitive values out of your source code. Your script reads the key at runtime from the system environment, so you never risk accidentally sharing it.

  • Install the google-genai package by running:
pip install -U google-genai

You should see output ending with a line like Successfully installed google-genai-... confirming the package is ready.

Seeing 'pip: command not found'?

  • Try pip3 install -U google-genai instead. Some systems map the Python 3 installer to pip3.
  • Make sure Python is on your PATH. Reopen your terminal and try again.

help me fix a pip not found error when installing google-genai

Verify your setup with a test script

Time to prove everything works end to end. You will write a small script that connects to the Gemini API and lists available models.

  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Select the document-analyzer folder on your Desktop, then click Open.
  • Create a new file called analyzer.py by clicking the new file icon in the Explorer sidebar (or press Cmd+N on macOS / Ctrl+N on Windows and save as analyzer.py).
  • Paste the following code into analyzer.py:
import os
from google import genai

# Create a client using your API key from the environment
client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))

# List all available models to confirm the connection works
for model in client.models.list():
    print(model.name)

What does this code do?

  • os.environ.get("GEMINI_API_KEY") reads your API key from the environment variable you just set.
  • genai.Client(...) creates an authenticated connection to the Gemini API.
  • client.models.list() fetches the catalog of available models, proving your key is valid and the network connection works.
  • Save analyzer.py.

Before you run this, what do you expect to see? Think about what listing models means for confirming your setup.

  • Switch back to your terminal (make sure you are still inside the document-analyzer folder) and run the script:
python analyzer.py

You should see a list of model names printed in your terminal, such as models/gemini-2.0-flash and others. This confirms your API key is valid and the SDK is installed correctly.

Seeing an error instead of model names?

  • If you see an authentication error, make sure you set the GEMINI_API_KEY environment variable in the same terminal session where you ran the script. Environment variables do not persist across terminal windows.
  • If you see ModuleNotFoundError: No module named 'google', confirm you installed google-genai with the same Python that runs your script. Try pip3 install -U google-genai or python3 -m pip install -U google-genai.
  • On Windows, use python analyzer.py (not python3).

help me fix an error when running my Gemini API test script

✔️ Awesome, I've got everything!

Great. Double check you have saved analyzer.py and move on to the next step.

ⓧ I'd like to double check the full code

Here is the complete analyzer.py for this step:

import os
from google import genai

client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))

for model in client.models.list():
    print(model.name)

Your API key is working and the SDK is installed. Next up, you will send your first real text to Gemini and see what the AI sends back.

Make Your First AI Call and See Raw Output

Your environment is connected and working. You confirmed it by listing Gemini models in your terminal.

Now it's time to send actual text to the AI and see what comes back. Before building anything structured, you need to observe what happens when you give an AI a document with no constraints on how it should respond.

In this step, get ready to:
  • Send sample text to Gemini and print the raw response.
  • Observe that the AI returns unpredictable, free-form text.
  • Identify the problem this creates for code that needs reliable data.
Send sample text to Gemini

Right now your script lists available models. You are going to replace that with code that sends a piece of text to Gemini and prints whatever the AI says back.

  • In analyzer.py, find the for loop that lists models (the last two lines of the file):
for model in client.models.list():
    print(model.name)
  • Delete those two lines and replace them with the following code:
# A sample email to analyze
sample_text = """Hi team, we need to finalize the Q3 report by Friday. John, please send over the sales numbers. Sarah, can you review the draft and add your comments? This is urgent as the board meeting is Monday."""

# Send the text to Gemini with a simple prompt
interaction = client.interactions.create(
    model="gemini-2.0-flash",
    input=f"Analyze this document:\n{sample_text}",
)

# Print whatever the AI returns
print(interaction.output_text)

What does this code do?

  • sample_text holds a short team email with action items and a deadline. This is the document you are asking the AI to analyze.
  • client.interactions.create() sends your text to the gemini-2.0-flash model. The input parameter is the full prompt the AI receives.
  • interaction.output_text is a convenience property that gives you back the model's text response as a plain string.

Before you run this, what do you think the AI will return? Will it be a neat table, a bulleted list, or something else entirely?

  • Save analyzer.py.
  • Run the script in your terminal:
python analyzer.py

You should see a block of text in your terminal. Gemini analyzed the email and printed its thoughts. Notice the format it chose. It might use bullet points, headers, paragraph prose, or a mix.

Seeing an error instead of a response?

  • If you see a permission or authentication error, your GEMINI_API_KEY environment variable may have expired from your terminal session. Re-export it and try again.
  • If you see a model not found error, double-check that gemini-2.0-flash is spelled exactly as shown in the code above.

Help me debug my Gemini API call. I'm getting an error when running my script.

Run it again and observe the unpredictability

The real problem with raw AI output becomes clear when you run the same script a second time.

  • Run the script again with the same command:
python analyzer.py

Compare this output to what you saw the first time. The AI likely chose a different format, different section headings, or a different level of detail. The content is similar, but the structure is not consistent.

  • Run it one more time:
python analyzer.py

Three runs, potentially three different formats. This is the core problem. If your code needed to extract the urgency level from this response, where would it look? The AI might put it in a header, a bullet, a sentence, or skip it entirely.

Why is this a problem?

In a real application, you need to reliably extract specific fields from the AI's response. Maybe you want to display the summary in one part of your UI and the action items in another.

With raw text output, you would need to write fragile parsing code that breaks every time the AI decides to format things differently. This is exactly the problem that structured output solves in the next step.

✔️ Awesome, I've got everything!

Great. Double check you have saved analyzer.py and you can see raw text output in your terminal when you run it.

ⓧ I'd like to double check the full code

Here is the complete analyzer.py at this point:

import os
from google import genai

client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))

sample_text = """Hi team, we need to finalize the Q3 report by Friday. John, please send over the sales numbers. Sarah, can you review the draft and add your comments? This is urgent as the board meeting is Monday."""

interaction = client.interactions.create(
    model="gemini-2.0-flash",
    input=f"Analyze this document:\n{sample_text}",
)

print(interaction.output_text)

You have now seen the problem firsthand. The AI gives you useful analysis, but in a format your code cannot rely on. Next up, you will force Gemini to always return data in the exact JSON shape you define.

Force Structured JSON Output with a Schema

In the previous step, you saw the problem firsthand: Gemini returns free-form text that changes shape every time you run it. An application that needs to reliably extract a summary, sentiment, and action items cannot parse unpredictable prose.

Structured output solves this by forcing Gemini to always return data in an exact JSON shape you define. You define the schema once, and every response conforms to it.

In this step, get ready to:
  • Define a Pydantic model that describes the exact JSON structure you want back.
  • Configure the API call to use structured output with your schema.
  • Parse and validate the JSON response into a typed Python object.
Define a Pydantic model for your analysis schema

A Pydantic model is a Python class that describes the exact fields and types you expect in a piece of data. When you hand this schema to Gemini, it guarantees the response will contain those fields and nothing else.

  • In VS Code, open analyzer.py and replace the entire file contents with the following code:
import os
from google import genai
from pydantic import BaseModel, Field
from typing import List


class DocumentAnalysis(BaseModel):
    summary: str = Field(description="A 2-3 sentence summary of the document.")
    sentiment: str = Field(description="The overall sentiment: positive, negative, or neutral.")
    action_items: List[str] = Field(description="A list of action items or tasks mentioned.")
    urgency: str = Field(description="The urgency level: low, medium, or high.")

What does this code do?

  • The two new imports (BaseModel, Field from pydantic, and List from typing) let you define typed data structures.
  • DocumentAnalysis is your schema. It declares exactly four fields: summary, sentiment, action_items, and urgency.
  • Each Field(description=...) tells Gemini what to put in that field. The AI reads these descriptions to understand your intent.
  • Save analyzer.py. You will not see output yet because the model is just a definition. The next chunk uses it.
Write the analyze function and run it

Now you will write a function that sends text to Gemini with the response_format parameter pointing at your schema. Gemini reads the schema and forces its output to match it exactly.

  • Add the following code below your DocumentAnalysis class in analyzer.py:
def analyze_document(text: str) -> DocumentAnalysis:
    client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))

    prompt = f"""Analyze the following document and extract structured information.

Document:
{text}
"""

    interaction = client.interactions.create(
        model="gemini-2.0-flash",
        input=prompt,
        response_format={
            "type": "text",
            "mime_type": "application/json",
            "schema": DocumentAnalysis.model_json_schema(),
        },
    )

    result = DocumentAnalysis.model_validate_json(interaction.output_text)
    return result


sample_text = """Hi team, we need to finalize the Q3 report by Friday. John, please send over the sales numbers. Sarah, can you review the draft and add your comments? This is urgent as the board meeting is Monday."""

analysis = analyze_document(sample_text)
print(analysis)

What does this code do?

  • analyze_document() wraps the entire AI call. It builds a prompt, sends it to Gemini, and returns a validated DocumentAnalysis object.
  • response_format is the key. By passing "mime_type": "application/json" and "schema": DocumentAnalysis.model_json_schema(), you tell Gemini to only produce JSON matching your model.
  • DocumentAnalysis.model_validate_json() parses the raw JSON string into a typed Python object. If the response somehow broke the schema, this line would raise an error instead of silently passing bad data.
  • The bottom three lines call the function with the same sample email from the previous step, so you can compare the result directly.

Before you run this, what do you expect the output to look like? Will it be free-form text like before, or something different?

  • Save analyzer.py and run it:
python analyzer.py

You should see output that looks like a Python object with exactly four fields:

summary='...' sentiment='...' action_items=['...', '...'] urgency='...'

Every field you defined in DocumentAnalysis is present. The AI filled them in according to your descriptions.

  • Run it again to confirm the structure stays consistent:
python analyzer.py

The exact wording may differ between runs (the AI still generates the content), but the shape is always the same: summary, sentiment, action_items, urgency. That consistency is the entire point of structured output.

Seeing a validation error or empty output?

  • Make sure your GEMINI_API_KEY environment variable is still set in this terminal session. If you opened a new terminal, you need to export it again.
  • Check that response_format is inside the client.interactions.create() call, not outside it.
  • Confirm that DocumentAnalysis.model_json_schema() has parentheses. Missing them passes the method itself instead of the schema dictionary.

Why isn't my structured output working?

Why structured output instead of asking nicely?

You could add "please respond in JSON" to your prompt, but the AI might still drift. It might add extra commentary, wrap the JSON in markdown code fences, or use slightly different field names each time.

The response_format parameter enforces the schema at the API level. Gemini's output is constrained to valid JSON matching your model before it reaches your code.

✔️ Awesome, I've got everything!

Great. Double check you have saved analyzer.py and that your output shows all four fields.

ⓧ I'd like to double check the full code

Your analyzer.py should contain two sections, in this order:

  • Imports and the DocumentAnalysis class (the first code block you pasted).
  • The analyze_document() function, followed by sample_text, analysis = analyze_document(sample_text), and print(analysis) (the second code block you pasted).

Scroll up and confirm both blocks are present in your file with no leftover code from the previous step.

Your script now produces reliable, predictable data every single time. Next up, you will turn this into a polished tool that accepts any text input and displays the analysis in a clean format.

Build the Complete Document Analyzer

Your script can already send text to Gemini and get structured JSON back. The problem is that it only works with hardcoded text, dumps a raw Python object to the terminal, and crashes without explanation if anything goes wrong.

In this step, you'll turn the script into a polished command-line tool. It will accept any pasted text, display a cleanly formatted analysis, and handle errors gracefully. This is what turns a script into something you could actually demo.

In this step, get ready to:
  • Format the AI output with clear labels and structure.
  • Accept any pasted text as user input.
  • Handle errors for API failures and empty input.
Add a display function for formatted output

Right now, print(analysis) dumps a raw Pydantic object that looks like a wall of text. A dedicated display function prints each field with clear labels so a human can actually read the results.

  • In analyzer.py, add import json on a new line directly below import os.
  • Below the analyze_document() function (after its return result line), add this new function:
def display_analysis(analysis: DocumentAnalysis) -> None:
    print("\n" + "=" * 50)
    print("DOCUMENT ANALYSIS")
    print("=" * 50)
    print(f"\nSummary: {analysis.summary}")
    print(f"\nSentiment: {analysis.sentiment}")
    print(f"\nUrgency: {analysis.urgency}")
    print("\nAction Items:")
    if analysis.action_items:
        for i, item in enumerate(analysis.action_items, 1):
            print(f"  {i}. {item}")
    else:
        print("  No action items found.")
    print("\n" + "=" * 50)

What does this code do?

This function takes a validated DocumentAnalysis object and prints each field with a clear label. The = borders create visual separation so the output is easy to scan.

Action items get numbered with enumerate() so the learner sees a clean list. If no action items exist, a fallback message prints instead of an empty space.

  • Now change the last line of the file from print(analysis) to display_analysis(analysis) so it uses your new function.
  • Save analyzer.py.
  • Run the script to see the formatted output:
python analyzer.py

You should see a bordered output with labeled fields for Summary, Sentiment, Urgency, and a numbered Action Items list. Compare this to the raw object dump from before.

Seeing a NameError?

If you see NameError: name 'display_analysis' is not defined, make sure your new function is placed above the line that calls it. The function definition must come before display_analysis(analysis) at the bottom of the file.

help me fix a NameError in my analyzer.py

Replace the test code with an interactive main function

The hardcoded sample text was useful for testing, but a real tool needs to accept whatever the user pastes in. You also want the script to fail gracefully if something goes wrong with the API call.

  • In analyzer.py, find and delete these lines at the bottom of the file:
sample_text = """Hi team, we need to finalize the Q3 report by Friday. John, please send over the sales numbers. Sarah, can you review the draft and add your comments? This is urgent as the board meeting is Monday."""

analysis = analyze_document(sample_text)
display_analysis(analysis)
  • In place of the deleted code, add this main() function and entry point:
def main():
    print("AI Document Analyzer")
    print("-" * 30)
    print("Paste your text below (press Enter twice to submit):\n")

    # Read multi-line input until two consecutive blank lines
    lines = []
    while True:
        line = input()
        if line == "":
            if lines and lines[-1] == "":
                break
            lines.append(line)
        else:
            lines.append(line)

    text = "\n".join(lines).strip()

    if not text:
        print("No text provided. Exiting.")
        return

    print("\nAnalyzing...")

    # Call the API and handle any failures gracefully
    try:
        analysis = analyze_document(text)
        display_analysis(analysis)
    except Exception as e:
        print(f"\nError: {e}")
        print("Check your API key and internet connection.")


if __name__ == "__main__":
    main()

What does this code do?

The while True loop reads lines one at a time. When the user presses Enter on an empty line twice in a row, the loop stops. This lets users paste multi-line text (emails, meeting notes) without needing special delimiters.

The if not text check catches the case where someone submits without typing anything. The try/except block catches API errors (bad key, no internet) and prints a helpful message instead of a stack trace.

The if __name__ == "__main__" guard ensures main() only runs when you execute the file directly, not when you import it as a module.

  • Save analyzer.py.
Run the complete analyzer with real input

Before you run the script, think about what you expect to happen. The tool should prompt you for input, wait for you to paste text, and then display a structured analysis.

  • Run your analyzer:
python analyzer.py

You'll see a prompt asking you to paste text. Try this sample:

Hi team, the client demo is tomorrow at 2pm. Please make sure the staging environment is updated. Also, we need to fix the login bug before the demo. Let me know if anyone needs help preparing slides.
  • Paste the text above into the terminal, then press Enter twice to submit.

You should see a cleanly formatted analysis with a bordered header, summary, sentiment, urgency level, and numbered action items. Each field has a label so you can instantly find what you need.

Why press Enter twice?

A single blank line might appear in normal pasted text (between paragraphs, for example). Two consecutive blank lines are unlikely to appear in real content, making it a reliable signal that the user is done pasting.

Script exits immediately with no output?

If you pressed Enter twice before pasting anything, the empty input check triggers and the script exits. Run it again and paste your text before pressing Enter twice.

If you see an API error, make sure your GEMINI_API_KEY environment variable is still set in your current terminal session. Closing and reopening the terminal clears it.

help me troubleshoot my document analyzer script

✔️ Awesome, I've got everything!

Great. Double check you've saved analyzer.py and your formatted analysis printed cleanly.

ⓧ I'd like to double check the full code

Your analyzer.py should contain these sections in order. Scroll up and confirm each one is present:

  • Imports: os, json, genai, BaseModel, Field, List.
  • The DocumentAnalysis Pydantic model with four fields.
  • The analyze_document() function that calls the Gemini API with structured output.
  • The display_analysis() function that prints formatted output with labels and borders.
  • The main() function with multi-line input, empty-input check, and try/except error handling.
  • The if __name__ == "__main__" guard calling main().

Your document analyzer is complete. You can now paste any text and get a structured, readable analysis back. Next up, the Secret Mission challenges you to compare two documents side-by-side.

Secret mission

Multi-Document Comparison

Real AI-as-a-Service products rarely analyze one document in isolation. They compare, contrast, and prioritize across multiple inputs. In this secret mission, extend your analyzer to compare two documents side-by-side and get a structured comparison report back from Gemini.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely on the Gemini free tier, so there are no ongoing costs.

Resources you used:

  • Google AI Studio API key (free, no recurring charges).
  • The google-genai Python package installed on your machine.
  • Local project file: analyzer.py.

Keep everything running

No action needed. Choose this if you plan to keep building with the Gemini API.

  • Your API key stays active and free. You can reuse it for future projects at no cost.
  • The google-genai package remains installed and ready for your next script.
  • Your analyzer.py file stays on your machine, ready to run again.

Pause - I'll come back to this later

Free up terminal resources while keeping your files and API key intact.

  • Close any terminal windows running the analyzer.
  • Your API key remains active at no cost. You do not need to revoke it.
  • Your analyzer.py file stays saved on your machine.
  • When you return, open a new terminal, re-export your GEMINI_API_KEY environment variable, and run the script again.

Delete - I don't want to use this again

Remove all project resources and revoke API access.

  • Delete the API key by opening the Google Cloud API Credentials page at https://console.cloud.google.com/apis/credentials.
  • Find your Gemini API key, click the Actions icon, and select Delete API key.
  • In the confirmation modal, click Delete. It takes a few minutes to propagate.

Uninstall the Python package by running:

pip uninstall google-genai

Delete your project file by running:

macOS

rm ~/Desktop/analyzer.py

Windows

Remove-Item ~\Desktop\analyzer.py

Nice Work!

Nice Work!

You just built a production-ready AI-powered document analyzer from scratch using Python and Google Gemini. You now own the exact integration pattern that powers every AI-as-a-Service product: take input, call an AI API, force structured output, and display results.

You've learned how to:

  • Build a working AI API integration that sends text to Google Gemini and receives intelligent analysis back.
  • Use Pydantic schemas and structured output to force reliable, validated JSON from an AI model every single time.
  • Secure your API key with environment variables and handle errors gracefully so your tool is demo-ready.
  • Secret Mission: Extended the tool to compare two documents side-by-side and return a structured comparison with differences, urgency ranking, and recommendations.

Ready to quiz yourself?