AI-Powered Document Analyst
Build a Python CLI that uses Gemini AI to analyze and chat about any document.
Introduction
30 Second Summary
You have a 20-page document sitting in front of you. Maybe it is meeting notes, a research paper, or a contract. You need answers from it fast, but reading the whole thing again feels painful.
In this project, you will build a Python tool that reads any text file and turns it into a conversational knowledge base powered by the Gemini API. You will make your first LLM API call, engineer prompts that control how the AI responds, and build multi-turn conversations with memory.
What You'll Build
You run your script, point it at any document, and start chatting with an AI that knows every detail inside it.
By the end of this project, you'll have:
- A working Python script that reads any .txt file and answers questions about its contents using the Gemini API.
- Multiple analyst modes (summarizer, fact-checker, action-item extractor) that produce completely different outputs from the same document by changing a single system prompt.
- A multi-turn conversation loop where you ask follow-up questions and the AI remembers what you discussed earlier.
- Secret Mission: A comparison mode that loads two documents and identifies differences between them.
Are there any prerequisites?
Basic familiarity with Python syntax (variables, functions, loops) is all you need. No AI or machine learning experience required.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters to you. Think about a document you work with regularly that you'd love to have an AI expert ready to answer questions about.
Set Up Your Python Environment and Gemini API Key
Before you can build anything with AI, you need a connection to the model. That connection has two parts: an SDK (the Python library that talks to Gemini) and an API key (your personal credential that proves you are allowed to make calls).
Getting this right once means every future AI project starts in seconds. In this step, you will install the SDK, grab a free API key, and confirm everything works with a single command.
In this step, get ready to:
- Create a project folder and Python virtual environment.
- Get a free Gemini API key from Google AI Studio.
- Verify the SDK is installed and your key is ready to use.
Create a project folder and virtual environment
A virtual environment keeps your project's packages isolated from the rest of your system. You will create one inside a new project folder on your Desktop.
- In VS Code, open the integrated terminal by clicking Terminal in the menu bar, then clicking New Terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir document-analyst
cd document-analyst
- Confirm the folder exists by running:
ls
You should see an empty folder listing. Your terminal prompt now shows you are inside document-analyst.
- Create a virtual environment by running:
python -m venv venv
- Activate the virtual environment by running:
macOS
source venv/bin/activate
Windows
venv\Scripts\activate
Your terminal prompt now starts with (venv). This confirms the virtual environment is active.
What is a virtual environment?
A virtual environment is an isolated Python workspace. Packages you install inside it stay separate from your system Python, so different projects never conflict with each other.
- Install the project dependencies by running:
pip install google-genai python-dotenv
You should see output ending with a line that starts with Successfully installed followed by the package names.
What did you just install?
- google-genai is the official Python SDK for calling the Gemini API.
- python-dotenv loads environment variables from a .env file so your API key stays out of your code.
Seeing an error during install?
- Make sure your virtual environment is active. You should see (venv) at the start of your terminal prompt.
- If you see a permissions error, confirm you activated the venv before running pip install.
Ask for help: Why is pip install failing in my virtual environment?
- Now open this folder as your VS Code workspace. Click File in the menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the document-analyst folder, and click Open.
VS Code reloads with document-analyst as the workspace. The Explorer sidebar on the left shows the venv folder you created.
Get your free Gemini API key
Your API key is what authenticates your Python script with Google's servers. You will grab one from Google AI Studio, which is free and requires no credit card.
- Open your browser and go to aistudio.google.com.
- Sign in with your Google account.
What happens when you sign in?
AI Studio automatically creates a Google Cloud project and an API key for new users after you accept the Terms of Service. You do not need to configure anything manually.
- In AI Studio, click the API keys link in the left sidebar (or navigate directly to aistudio.google.com/app/apikey).
- If you already see a key listed, click the copy icon next to it. If no key exists yet, click Create API key, then copy the generated key.
Keep your API key private
Never paste your API key directly into code files or share it publicly. You will store it in a .env file that stays on your machine only.
- Back in VS Code, open a new terminal by clicking Terminal in the menu bar, then New Terminal.
- Create the .env file. Click File in the menu bar, then click New File.
- Save it immediately with the name .env by pressing Cmd+S (macOS) or Ctrl+S (Windows) and typing .env as the filename.
- Paste the following into the file, replacing your-api-key-here with the key you just copied:
GEMINI_API_KEY=your-api-key-here
- Save the file.
What does this file do?
The python-dotenv library reads .env at runtime and loads its values as environment variables. Your code calls os.environ.get("GEMINI_API_KEY") to fetch the key without ever hardcoding it.
✔️ Awesome, I've got everything!
Great. Double check that you saved .env and that it appears in the Explorer sidebar.
⨯ I'd like to double check the full code
Your .env file should contain exactly one line:
GEMINI_API_KEY=your-api-key-here
Verify the setup
Time to confirm everything is wired up. You will run a single Python command that imports the SDK and prints a success message.
- In your VS Code terminal, make sure you see (venv) in the prompt. If you do not, re-activate the virtual environment using the activation command from earlier.
Before you run the next command, what do you expect to see if the SDK installed correctly?
- Verify the SDK is installed by running:
python -c "from google import genai; print('SDK ready')"
You should see SDK ready printed with no errors. That single line confirms the google-genai package is installed and importable.
Seeing ModuleNotFoundError?
- Check that (venv) appears in your terminal prompt. If it does not, you are running the system Python instead of your virtual environment.
- If you reopened VS Code after creating the venv, the terminal may not have reactivated it automatically. Run the activation command again.
- Re-run pip install google-genai inside the activated venv and try the verify command again.
Ask for help: Why am I getting ModuleNotFoundError for google.genai?
Your environment is ready. You have a virtual environment, the Gemini SDK, and a securely stored API key. Next up, you will use all three to make your first live call to the Gemini model.
Make Your First LLM API Call
Your SDK is installed and your API key is safely stored in a .env file. Python can find the google-genai package, and you're ready to talk to the Gemini model.
Before building anything complex, you need to see the raw API in action. One call, one response. This is the smallest building block of every AI application.
In this step, get ready to:
- Create main.py with a working Gemini API call.
- Run the script and observe Gemini's response in your terminal.
Create main.py with a minimal API call
Every Gemini API call follows the same pattern: create a client, point it at a model, and pass in your prompt. The response comes back as a text string you can print or process.
- In the VS Code Explorer sidebar, click the new file icon at the top.
- Name the file main.py.
- Add the following code to main.py:
import os
from dotenv import load_dotenv
from google import genai
# Load environment variables from .env file
load_dotenv()
# Create a Gemini API client using your API key
client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))
# Choose which Gemini model to use
MODEL = "gemini-2.5-flash"
# Send a prompt to the model and get a response
response = client.models.generate_content(
model=MODEL,
contents="What is prompt engineering?",
)
# Print the model's text response
print(response.text)
What does this code do?
- load_dotenv() reads your .env file and loads GEMINI_API_KEY into the environment so your code can access it.
- genai.Client(...) creates a connection to the Gemini API using your key.
- MODEL = "gemini-2.5-flash" stores which Gemini model you'll talk to. This is the free-tier model.
- generate_content() sends your prompt to the model and returns its response.
- response.text gives you just the plain text string from the model's reply.
- Save main.py.
Before you run this, what do you think will happen? Will you see a multi-sentence explanation, a single line, or an error?
- Run the script by entering this command in your terminal:
python main.py
You should see a multi-paragraph explanation of prompt engineering printed in your terminal. The exact wording varies each time you run it, but it will be a coherent answer about the topic.
Seeing an error instead of a response?
- If you see a ModuleNotFoundError mentioning dotenv or google, your virtual environment is not active. Look for (venv) at the start of your terminal prompt. If it's missing, activate it with venv\Scripts\activate and try again.
- If you see a 400 or 403 error mentioning an invalid API key, open your .env file and check that it reads GEMINI_API_KEY= followed by your actual key with no spaces around the = sign. Make sure the key was generated in Google AI Studio.
- If you see a timeout or 500 error, wait a moment and run the command again. Transient server errors resolve on retry.
help me debug my Gemini API call in Python
✔️ Awesome, I've got everything!
Great. Double check you've saved main.py and move on to the next section.
ⓧ I'd like to double check the full code
Here is the complete main.py file. Make sure yours matches:
import os
from dotenv import load_dotenv
from google import genai
load_dotenv()
client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))
MODEL = "gemini-2.5-flash"
response = client.models.generate_content(
model=MODEL,
contents="What is prompt engineering?",
)
print(response.text)
Experiment with the response
The model's output quality depends heavily on how you phrase your prompt. Vague prompts produce generic answers. Specific prompts produce focused, useful responses.
- In main.py, find the contents string:
contents="What is prompt engineering?",
- Replace "What is prompt engineering?" with a specific question about a topic you know well. For example: "What are three common causes of memory leaks in Python web applications?".
- Save main.py and run it again:
python main.py
Notice how the focused question produces a detailed, structured answer. The model gives you concrete causes, not generic fluff.
- Now try a vague version of the same topic. Replace your prompt with something broad like "Tell me about Python" and run python main.py again.
Compare the two responses. The vague prompt gives a generic overview. The specific prompt gives actionable detail.
Your first taste of prompt engineering
This is prompt engineering in action: the same model can give you a paragraph of fluff or a precise, useful answer. The difference is entirely in how you ask.
Specificity is the single most impactful lever you have. Throughout this project, you'll see how different instructions to the same model produce completely different outputs.
- Before moving on, set your contents string back to "What is prompt engineering?" and save main.py.
You've made your first LLM API call and experienced how prompt wording shapes the output. Next, you'll feed the model a real document and turn it into a specialist on your data.
Load a Document and Ask Questions About It
Your first API call proved that Gemini can answer general questions. But a general-purpose answer engine is something anyone can get from a search bar.
The real power of an LLM API comes when you give the model YOUR data as context. Feed it a document, and it becomes an instant specialist on that document's contents. In this step, you will load a text file and ask the AI questions about it.
In this step, get ready to:
- Create a sample document and write a function to load it.
- Pass the document as context in your prompt and ask questions about it.
- Discover a limitation when asking questions outside the document.
Create a sample document for analysis
Your Document Analyst needs a document to analyze. You will create a set of meeting notes that gives the AI something concrete to work with.
- In VS Code's Explorer sidebar, right-click inside the document-analyst folder and select New File.
- Name it sample_document.txt and paste the following content:
Project Phoenix - Weekly Status Meeting Notes
Date: July 21, 2025
Attendees: Sarah Chen (PM), Marcus Johnson (Engineering Lead), Priya Patel (Design), James Wilson (QA)
Summary:
The team reviewed progress on the new user onboarding flow. Engineering is 80% complete on the backend API endpoints. Design finalized the mobile mockups and handed off to engineering on Monday. QA identified 3 critical bugs in the authentication module that need to be resolved before the beta launch.
Action Items:
1. Marcus: Fix authentication bugs by July 25 (critical priority)
2. Priya: Update the error state designs based on QA feedback by July 23
3. Sarah: Schedule stakeholder demo for July 28
4. James: Write test cases for the new onboarding endpoints by July 24
Risks:
- Authentication bugs may delay beta launch if not resolved by Friday
- Mobile performance testing has not started yet
- Third-party API integration (payment provider) is blocked pending contract approval
Next Meeting: July 28, 2025
Using your own document?
You can use any .txt file you have on hand instead. Meeting notes, an article, a research summary, or even your resume all work. The sample above gives everyone the same baseline for testing.
- Save sample_document.txt.
- Confirm the file exists by running this in your terminal:
ls
- You should see sample_document.txt alongside main.py and the venv folder in the output.
Don't see sample_document.txt?
Make sure you saved the file with Ctrl+S (Windows) or Cmd+S (macOS). Also check that the file was created inside the document-analyst folder, not in a subfolder.
Help me find my sample_document.txt file
Add a function to read document files
Your script needs a way to read any text file the user provides. A dedicated function keeps the file-reading logic clean and reusable.
- Open main.py in VS Code.
- Find the line that says MODEL = "gemini-2.5-flash".
- Add the following function below it:
def load_document(filepath):
# Open the file and return its full text content
with open(filepath, "r", encoding="utf-8") as f:
return f.read()
What does this function do?
- load_document(filepath) takes a file path as input and returns the entire file as a single string.
- encoding="utf-8" ensures the file is read correctly regardless of special characters in the text.
Pass the document as context in your prompt
Now you need to combine the document with a question into a single prompt. When Gemini sees both together, it can answer questions specifically about your document's contents.
- In main.py, find and delete these lines at the bottom of the file:
response = client.models.generate_content(
model=MODEL,
contents="What is prompt engineering?",
)
print(response.text)
- Replace those deleted lines with the following code, which adds a message-building function and a new interactive flow:
def build_initial_message(document_content, user_question):
# Combine document text and question into one prompt
return f"Document:\n---\n{document_content}\n---\n\nQuestion: {user_question}"
# Ask the user for a file path and load it
filepath = input("Enter the path to your text file: ").strip()
document_content = load_document(filepath)
# Ask a question about the document
question = input("Ask a question about the document: ").strip()
message = build_initial_message(document_content, question)
# Send the combined prompt to Gemini
response = client.models.generate_content(
model=MODEL,
contents=message,
)
print(response.text)
What does this code do?
- build_initial_message() wraps the document inside a clearly marked block (between --- separators) followed by the user's question. This structure helps the model distinguish the source material from the question.
- input() prompts you in the terminal to type a file path and then a question. The script is now interactive.
- The full combined string (document + question) is passed as contents to generate_content(). Gemini reads everything in one shot and responds based on the full context.
- Save main.py.
Before you run this, what do you think the AI will do when you ask it a question about the meeting notes?
- Run the script by entering this command:
python main.py
- When prompted for the file path, type sample_document.txt and press Enter.
- When prompted for a question, type What are the action items from this meeting? and press Enter.
You should see Gemini respond with a summary of the four action items from the meeting notes, referencing Marcus, Priya, Sarah, and James by name.
Seeing a FileNotFoundError?
Make sure you typed sample_document.txt exactly (no extra spaces or wrong extension). The file must be in the same folder where you run the script.
If you get an API error instead, check that your .env file still contains your key and that the virtual environment is active (you should see (venv) in your terminal prompt).
Help me debug my document loading script
Test the limitation: ask an off-topic question
The AI answered your document question accurately. But what happens if you ask something that the document does NOT contain?
- Run the script again:
python main.py
- Enter sample_document.txt as the file path.
- This time, ask a question whose answer is definitely not in the meeting notes. Try: What is the company's annual revenue?
Before you press Enter, predict: will the model say it does not know, or will it try to answer anyway?
You will likely see the model attempt to answer the question or make up plausible-sounding information. The meeting notes never mention revenue, but the model may produce a confident response as if the information exists.
What just happened?
This is called hallucination. The model generates text that sounds correct but is not grounded in the source material. Without explicit instructions to stay within the document, it fills in gaps with its general training data.
This is the gap between a toy demo and a reliable tool. You will fix this in the next step.
✔️ Awesome, I've got everything!
Great. Double check you have saved both main.py and sample_document.txt.
ⓧ I'd like to double check the full code
Scroll up and confirm your main.py has these sections, in order:
- The imports (os, dotenv, genai), load_dotenv(), client setup, and MODEL from Step 2.
- The load_document(filepath) function that reads a file and returns its content.
- The build_initial_message(document_content, user_question) function that combines the document and question.
- The interactive execution flow: two input() calls, then generate_content() with the combined message, then print(response.text).
The old hardcoded contents="What is prompt engineering?" line should no longer be in your file.
Your script can now load any text file and answer questions about it. But as you discovered, it has no guardrails. Next up, you will add system instructions that force the AI to stay focused on the document and refuse to hallucinate.
Add System Instructions to Control the AI's Behaviour
In the previous step, you saw the model answer questions about your document. But you also noticed a problem: when you asked something not covered by the document, the model still tried to answer. It made things up rather than admitting it did not know.
This is called hallucination. Raw context alone is not enough to stop it. System instructions are how professionals constrain LLM behaviour. They are the difference between a toy demo and a reliable tool.
In this step, get ready to:
- Add a system instruction that prevents the AI from hallucinating.
- Create multiple analyst modes that produce completely different outputs from the same document.
- Test how the same content looks through different system prompts.
Add a system instruction that constrains the model
The Gemini API lets you pass a system_instruction alongside your prompt. This instruction runs before anything the user says and sets the rules the model must follow for the entire conversation.
- In main.py, find this line near the top:
from google import genai
- Add a new import directly below it so both lines read:
from google import genai
from google.genai import types
What is types?
The types module contains configuration classes for the Gemini API. You will use types.GenerateContentConfig to pass system instructions and control parameters like temperature.
Now you need to define the system prompts that will tell the model how to behave. Each prompt creates a different "analyst mode" with its own personality and constraints.
- In main.py, find this line:
MODEL = "gemini-2.5-flash"
- Add the following SYSTEM_PROMPTS dictionary directly below it:
SYSTEM_PROMPTS = {
"analyst": (
"You are a document analyst. Only answer questions based on the "
"provided document. If the answer is not in the document, say: "
"'That information is not in the document.' Do not make up information."
),
"summarizer": (
"You are a document summarizer. Provide concise bullet-point summaries "
"of the document content. Only use information from the document. "
"If asked about something not in the document, say so."
),
"fact_checker": (
"You are a fact checker. When answering questions, quote the exact "
"relevant passages from the document. If the document does not contain "
"the information, clearly state that. Never invent facts."
),
"action_items": (
"You are an action-item extractor. Identify all tasks, deadlines, "
"and responsibilities from the document. Present them in a structured "
"format with who, what, and when. Only extract what is explicitly stated."
),
}
What do these modes do?
Each mode gives the model a different job:
- analyst answers questions strictly from the document. This is the general-purpose mode.
- summarizer produces concise bullet-point summaries.
- fact_checker quotes exact passages as evidence.
- action_items extracts tasks, deadlines, and owners into a structured list.
Every mode includes a constraint: the model must refuse to answer if the information is not in the document. This is what prevents hallucination.
Next, you need to replace the bottom section of your script so it uses these system prompts.
- In main.py, find these lines at the bottom of the file:
filepath = input("Enter the path to your text file: ").strip()
document_content = load_document(filepath)
question = input("Ask a question about the document: ").strip()
message = build_initial_message(document_content, question)
response = client.models.generate_content(
model=MODEL,
contents=message,
)
print(response.text)
- Replace that entire section with the following code:
filepath = input("Enter the path to your text file: ").strip()
document_content = load_document(filepath)
print(f"Loaded: {filepath} ({len(document_content)} characters)")
print(f"Available modes: {', '.join(SYSTEM_PROMPTS.keys())}")
current_mode = input("Choose a mode (default: analyst): ").strip() or "analyst"
if current_mode not in SYSTEM_PROMPTS:
print(f"Unknown mode '{current_mode}', using 'analyst'.")
current_mode = "analyst"
question = input("Ask a question about the document: ").strip()
message = build_initial_message(document_content, question)
response = client.models.generate_content(
model=MODEL,
contents=message,
config=types.GenerateContentConfig(
system_instruction=SYSTEM_PROMPTS[current_mode],
temperature=0.3,
),
)
print(response.text)
What changed here?
Three key changes from your previous version:
- The script now prints the available modes and lets the user pick one before asking a question.
- The config parameter passes the selected system prompt as a system_instruction. This tells Gemini to follow those rules before processing your question.
- Setting temperature=0.3 makes the model more deterministic. Lower temperature means less creative variation and more reliable, factual answers.
- Save main.py.
Before you run this, think: what do you expect will happen if you ask about something that is NOT in the meeting notes?
- Test the constraint by running your script:
python main.py
- When prompted, enter sample_document.txt as the file path.
- Choose the default mode by pressing Enter (this selects analyst).
- Ask an off-topic question like: What is the company's stock price?
The model now refuses gracefully. Instead of inventing an answer, it responds with something like "That information is not in the document."
Still getting hallucinated answers?
- Make sure you added the config parameter inside generate_content(). Without it, the system instruction is not being sent.
- Check that the from google.genai import types import is at the top of your file. A missing import causes a NameError.
- Verify that current_mode matches one of the keys in SYSTEM_PROMPTS. A typo means the wrong prompt (or no prompt) is sent.
Help me debug my system instruction setup.
Test multiple analyst modes on the same document
The real power of system instructions is not just preventing hallucination. Different prompts make the same model behave in completely different ways. The same document, the same API call, but a different instruction produces a different style of output.
- Run your script again:
python main.py
- Enter sample_document.txt as the file path.
- This time, type summarizer when asked to choose a mode.
- Ask: What happened in this meeting?
You should see a concise bullet-point summary instead of a long paragraph. The summarizer mode produces structured, scannable output.
- Run the script one more time and choose action_items mode.
- Ask: What needs to happen next?
This time the output is a structured list of tasks with owners and deadlines. Same document, same question pattern, completely different output.
Why does this matter?
You just experienced prompt engineering firsthand. The model itself did not change. The data did not change. Only the system instruction changed, and the output transformed completely.
This is the core skill of AI engineering: writing instructions that produce the exact behaviour you need from a general-purpose model.
- Try the fact_checker mode with the question: Is mobile testing complete?
The fact checker quotes the exact passage from the document: "Mobile performance testing has not started yet." It cites its source rather than paraphrasing.
Seeing similar outputs across modes?
- Make sure you are typing the mode name exactly as shown (summarizer, fact_checker, action_items). A typo defaults you back to analyst mode.
- Check the terminal output at startup. It should print the mode you selected (e.g. "Choose a mode (default: analyst):"). If it says "Unknown mode", you mistyped.
Help me understand why my modes produce similar output.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete main.py at this point. Compare it against yours:
import os
from dotenv import load_dotenv
from google import genai
from google.genai import types
load_dotenv()
client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))
MODEL = "gemini-2.5-flash"
SYSTEM_PROMPTS = {
"analyst": (
"You are a document analyst. Only answer questions based on the "
"provided document. If the answer is not in the document, say: "
"'That information is not in the document.' Do not make up information."
),
"summarizer": (
"You are a document summarizer. Provide concise bullet-point summaries "
"of the document content. Only use information from the document. "
"If asked about something not in the document, say so."
),
"fact_checker": (
"You are a fact checker. When answering questions, quote the exact "
"relevant passages from the document. If the document does not contain "
"the information, clearly state that. Never invent facts."
),
"action_items": (
"You are an action-item extractor. Identify all tasks, deadlines, "
"and responsibilities from the document. Present them in a structured "
"format with who, what, and when. Only extract what is explicitly stated."
),
}
def load_document(filepath):
with open(filepath, "r", encoding="utf-8") as f:
return f.read()
def build_initial_message(document_content, user_question):
return f"Document:\n---\n{document_content}\n---\n\nQuestion: {user_question}"
filepath = input("Enter the path to your text file: ").strip()
document_content = load_document(filepath)
print(f"Loaded: {filepath} ({len(document_content)} characters)")
print(f"Available modes: {', '.join(SYSTEM_PROMPTS.keys())}")
current_mode = input("Choose a mode (default: analyst): ").strip() or "analyst"
if current_mode not in SYSTEM_PROMPTS:
print(f"Unknown mode '{current_mode}', using 'analyst'.")
current_mode = "analyst"
question = input("Ask a question about the document: ").strip()
message = build_initial_message(document_content, question)
response = client.models.generate_content(
model=MODEL,
contents=message,
config=types.GenerateContentConfig(
system_instruction=SYSTEM_PROMPTS[current_mode],
temperature=0.3,
),
)
print(response.text)
You now have a Document Analyst that refuses to hallucinate and adapts its output style based on the mode you choose. Next up, you will transform this single-shot tool into a full multi-turn conversation where you can ask follow-up questions and the AI remembers the entire discussion.
Build Multi-Turn Conversation
Your Document Analyst can answer a single question about a document. That is useful, but limited. Real conversations involve follow-up questions, drilling into details, and referencing earlier answers.
In this step, you will transform your one-shot script into an interactive conversation loop. The AI will remember every question and answer, so you can ask follow-ups naturally, just like chatting with a colleague who read the document.
In this step, get ready to:
- Replace the single-call pattern with a conversation loop that maintains message history.
- Add conversation controls so the user can quit, reset, and switch modes mid-conversation.
- Verify multi-turn reasoning by asking follow-up questions that reference earlier answers.
Implement a conversation loop
Right now your script makes one API call and exits. To support follow-up questions, you need a loop that accumulates a history of messages and passes that entire history to the model on every call.
- In main.py, select and delete everything below the build_initial_message function (from the filepath = input(...) line all the way to the end of the file).
- Replace it with this main() function that sets up the document loading, mode selection, and starts a conversation loop:
def main():
print("=== Document Analyst ===")
print()
filepath = input("Enter the path to your text file: ").strip()
if not os.path.exists(filepath):
print(f"File not found: {filepath}")
return
document_content = load_document(filepath)
print(f"Loaded: {filepath} ({len(document_content)} characters)")
print()
print("Available modes: analyst, summarizer, fact_checker, action_items")
current_mode = input("Choose a mode (default: analyst): ").strip() or "analyst"
if current_mode not in SYSTEM_PROMPTS:
print(f"Unknown mode '{current_mode}', using 'analyst'.")
current_mode = "analyst"
print(f"\nMode: {current_mode}")
print("Commands: 'quit' to exit, 'reset' to clear history, 'mode' to switch modes")
print("-" * 40)
history = []
What does this code do?
This is the setup portion of your main() function. It loads the document, lets the user pick a mode, and initialises an empty history list.
The history list is the key to multi-turn conversation. It will store every message (from both the user and the model) so the AI always has full context of what was discussed.
Add conversation controls
A conversation loop needs a way out. You will add commands so the user can quit, reset the conversation, or switch analyst modes without restarting the script.
- Directly below the history = [] line, add the start of the while True loop with command handling:
while True:
user_input = input("\nYou: ").strip()
if not user_input:
continue
if user_input.lower() == "quit":
print("Goodbye!")
break
if user_input.lower() == "reset":
history = []
print("Conversation reset.")
continue
if user_input.lower() == "mode":
print(f"Available modes: {', '.join(SYSTEM_PROMPTS.keys())}")
new_mode = input("Choose a mode: ").strip()
if new_mode in SYSTEM_PROMPTS:
current_mode = new_mode
history = []
print(f"Switched to '{current_mode}' mode. History cleared.")
else:
print(f"Unknown mode '{new_mode}'. Keeping '{current_mode}'.")
continue
What do these commands do?
- quit breaks out of the loop and ends the program.
- reset clears the history list so the next question starts fresh (no prior context).
- mode lets the user switch analyst modes. It also clears history because a different system prompt changes how the model interprets prior messages.
- Still inside the while True loop (at the same indentation level as the if statements above), add the message building, API call, and history tracking:
if not history:
message_text = build_initial_message(document_content, user_input)
else:
message_text = user_input
history.append(types.Content(role="user", parts=[types.Part(text=message_text)]))
response = client.models.generate_content(
model=MODEL,
contents=history,
config=types.GenerateContentConfig(
system_instruction=SYSTEM_PROMPTS[current_mode],
temperature=0.3,
),
)
assistant_text = response.text
history.append(
types.Content(role="model", parts=[types.Part(text=assistant_text)])
)
print(f"\nAnalyst: {assistant_text}")
if __name__ == "__main__":
main()
How does history give the AI memory?
On the first question, build_initial_message() packages the full document plus the question into one string. On follow-up questions, only the user's text is needed because the document is already in the history.
Each message is stored as a types.Content object with a role (user or model). The entire list is passed to generate_content() every time, so the model sees the full conversation and can reference its own earlier answers.
- Save main.py.
- Run your script by entering this command:
python main.py
- When prompted, enter sample_document.txt as the file path.
- Press Enter to accept the default analyst mode.
- Ask a question like: What are the action items?.
You should see the AI respond, then the prompt returns so you can ask another question. Type quit to exit when you are done exploring.
Seeing a NameError or IndentationError?
- Make sure the entire while True block is indented one level inside def main(): (4 spaces for the function, 8 spaces for the loop body).
- Check that if __name__ == "__main__": is at the very bottom of the file with no indentation.
- Confirm you deleted ALL of the old script-level code (everything from the old filepath = input(...) through print(response.text)).
Help me debug my conversation loop in main.py
Test multi-turn reasoning
The real test of conversation memory is whether follow-up questions work. If the model truly remembers context, you can reference earlier answers without repeating yourself.
- Run your script again with sample_document.txt in analyst mode.
- Ask a broad question first: What are the biggest risks mentioned in this document?.
- After the AI responds, ask a follow-up: Can you elaborate on point 2?.
Before you read the response, predict: will the AI know what "point 2" refers to, or will it be confused?
The AI correctly elaborates on the second risk from its own previous answer. It knows what "point 2" means because the full conversation history is in the contents parameter.
- Try typing reset and then asking the same follow-up ("Can you elaborate on point 2?") again.
This time the model has no context. It does not know what "point 2" means because you cleared the history. This confirms the memory is working correctly.
- Type mode and switch to action_items.
- Ask: What needs to happen this week?.
The response now uses the action-item extractor style: structured output with who, what, and when. Same document, completely different analysis.
Why does switching modes clear history?
Each mode uses a different system prompt. If you kept the old history, the model would see answers formatted for the previous mode and might get confused about how to respond. Clearing history ensures the new mode starts with a clean slate.
Follow-up questions seem unrelated to your first question?
- Check that you are appending BOTH the user message and the model response to history. Missing either one breaks the conversation thread.
- Make sure you pass history (the list) to contents=, not message_text (a single string).
Why isn't my AI remembering earlier questions?
✔️ Awesome, I've got everything!
Great. Double check you have saved main.py and your conversation loop is working.
ⓧ I'd like to double check the full code
Here is the complete main.py at this point. Compare it to yours:
Your file has three sections. Scroll through and confirm each is present in order:
- Imports and client setup (import os through MODEL = "gemini-2.5-flash").
- The SYSTEM_PROMPTS dictionary with four modes.
- The load_document() and build_initial_message() helper functions.
- The main() function with the conversation loop.
- The if __name__ == "__main__": main() entry point at the very bottom.
Your Document Analyst now holds a real conversation. You can drill into details, switch analysis styles, and reset when you want a fresh start. You have built the same multi-turn chat pattern that powers every AI assistant on the market today.
Secret mission
Add Document Comparison Mode
Real-world AI applications often need to reason across multiple sources. In this mission, you will add a comparison mode that loads two documents and identifies differences between them. This is the same pattern used in contract review, code diffs, and research synthesis.
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 locally with a free-tier API key, so there are no ongoing costs.
Resources you used:
- The document-analyst project folder on your Desktop (contains main.py, .env, sample_document.txt, and the venv virtual environment).
- Your Gemini API key in Google AI Studio.
Keep everything running
No action needed. Choose this if you are still actively building or plan to reuse your Document Analyst setup for future AI projects.
- Your project folder stays on your Desktop, ready to use whenever you want.
- Your Gemini API key remains active on the free tier with no charges.
- The google-genai and python-dotenv packages stay installed inside your venv folder.
Pause - I'll come back to this later
Deactivate your virtual environment to free up your terminal. Your files stay on disk for when you return.
- Deactivate the virtual environment by running:
deactivate
- Close VS Code. When you come back, reopen the document-analyst folder and reactivate with venv\Scripts\activate (Windows) or source venv/bin/activate (macOS/Linux).
Delete - I don't want to use this again
Remove all project files and revoke your API key. This is a clean slate.
- Deactivate your virtual environment first:
deactivate
- Delete the project folder. On macOS/Linux, run:
cd ~/Desktop
rm -rf document-analyst
- On Windows (Command Prompt), run:
cd %USERPROFILE%\Desktop
rmdir /s /q document-analyst
- Delete your Gemini API key by going to the Google Cloud Console at console.cloud.google.com/apis/credentials.
- Select your API key from the list, then click Delete and confirm.
Nice Work!
Nice Work!
You just built a fully functional Document Analyst that turns any text file into a conversational knowledge base using the Gemini API. That is a real AI tool you can use every day.
You've learned how to:
- Built a Document Analyst that reads any text file and answers questions about it using the Gemini API.
- Used system instructions to control AI behaviour and prevent hallucination. The same model produces wildly different outputs based on how you instruct it.
- Implemented multi-turn conversations with chat memory so users can ask follow-up questions naturally.
- Secret Mission: Extended the tool to compare two documents, reasoning across multiple sources and citing specific differences.
You now have a reusable Python + Gemini setup that serves as the foundation for every project in your AI engineering journey.
Ready to quiz yourself?