In this tutorial, we are going to build a Retrieval-Augmented Generation (RAG) application. You heard it right "RAG".

Usually, building a RAG app requires a lot of components: chunking algorithm, an embedding model, and a vector database. Today, we delete that stack. We are going to replace complex components with one API call.

Today, we skip all of that. Relax!

We will use Google Gemini File Search API. It handles parsing, chunking, embedding, and retrieval automatically. We will combine this with Streamlit to build a fast-setup, functional web interface in pure Python.

What is Gemini File Search? Gemini File Search is a managed RAG (Retrieval-Augmented Generation) service by Google. Unlike traditional RAG pipelines, it handles document parsing, text chunking, embedding generation, and vector retrieval automatically, allowing developers to build "Chat with your Data" apps without managing a vector database.
Read more in official site: >> Gemini File Search <<


What We Are Building


We will build GenAI Knowledge Assistant, an app where you can:

  1. Upload documents (PDF) to a Knowledge Base.
  2. Index them into a private Brain (Gemini File Search Store).
  3. Chat with your documents and get accurate answers with citations.

The app will have a clean, multi-page structure:

  • Home page: Introduction.
  • Knowledge Base page: For uploading files.
  • Chat pat: For asking questions.


Prerequisites

Before we start, make sure you have:
  1. Python 3.9+ installed.
  2. A Gemini API Key. Get one here: Google AI Studio.


Directory structure:


gemini-file-search-streamlit-app/
├── .env
├── app.py                  # Home Page
├── services/
│   └── gemini.py           # Our backend logic
└── pages/
    ├── Knowledage_Base.py  # Upload Page
    └── Chat.py             # Chat Page


Step 1: Configure the Python Environment and Install Google GenAI SDK


First, let's set up our workspace. Open your terminal:

# bash
# 1. Create a folder
mkdir gemini-file-search-streamlit-app
cd gemini-file-search-streamlit-app
# 2. Create a virtual environment
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
# 3. Install dependencies
pip install google-genai streamlit python-dotenv

Create a file named `.env` to store your secrets:

# .env
GEMINI_API_KEY=your_api_key
GEMINI_MODEL_NAME=your_desired_model_name
Streamlit is an open-source Python library that transforms data scripts into shareable web applications in minutes. Unlike traditional web frameworks (like Flask or Django), Streamlit is designed specifically for data scientists and AI engineers, allowing you to build interactive frontends without writing a single line of HTML, CSS, or JavaScript.
Why use Streamlit for RAG Apps?
  • Pure Python: You define your entire UI (buttons, chat inputs, sidebars) using standard Python code.
  • Built-in Chat Elements: It provides native components like st.chat_message and st.chat_input designed specifically for LLM interfaces.
  • Session State Management: It automatically handles complex state (like remembering your chat history) with a simple dictionary-like API.


Step 2: Implement the Backend Connecting to Gemini File Search API


We need a helper file to handle the heavy lifting with Gemini. Create a folder named services and inside it create gemini.py. This keeps our code organized.

2.1 Connecting to Gemini

# python
# services/gemini.py
import os
import time
from google import genai
from google.genai import types
def get_gemini_client():
    """Initializes and returns a Gemini client."""
    api_key = os.getenv("GEMINI_API_KEY")
    if not api_key:
        raise ValueError("Gemini API key not found in environment variables.")
    
    client = genai.Client(api_key=api_key)
    return client

2.2 The "Brain" (File Search Store)

Think of a File Search Store as a folder in the cloud where Gemini reads files. We need a function to find our store or create a new one.
# python
# services/gemini.py (continued)
def get_or_create_file_search_store(client, display_name="Tutorial App"):
    """Finds or creates a file search store."""
    stores = client.file_search_stores.list()
    
    for store in stores:
        if store.display_name == display_name:
            print(f"Found existing files search store: {store.name}")
            return store
    print(f"Creating new files search store: {display_name}")
    new_store = client.file_search_stores.create(
        config=types.CreateFileSearchStoreConfig(display_name=display_name)
    )
    return new_store

2.3 Uploading & Indexing Files

This is the magic part. We upload a file to Gemini and then import it into our store.
# python
# services/gemini.py (continued)
def upload_and_index_file(client, file_path, mime_type, store_name):
    """Uploads a file and indexes it into the File Search Store."""
    
    # 1. Upload the file to Gemini's temporary storage
    print(f"Uploading {file_path}...")
    gemini_file = client.files.upload(
        file=file_path,
        config=types.UploadFileConfig(
            display_name=os.path.basename(file_path),
            mime_type=mime_type
        )
    )
    
    # Wait for processing (important!)
    while gemini_file.state.name == "PROCESSING":
        print("Processing file upon upload...")
        time.sleep(1)
        gemini_file = client.files.get(name=gemini_file.name)
    if gemini_file.state.name != "ACTIVE":
        raise Exception(f"File upload failed: {gemini_file.state.name}")
    
    # 2. Import the file into the Store (The Indexing Step)
    print("Indexing into store...")
    import_op = client.file_search_stores.import_file(
        file_search_store_name=store_name,
        file_name=gemini_file.name
    )
    
    # 3. Poll for completion (CRITICAL STEP!)
    # Indexing is async. We must wait for it to finish.
    while True:
        if import_op.done:
            break
        time.sleep(1)
        import_op = client.operations.get(import_op)
        
    if import_op.error:
        raise Exception(f"Indexing failed: {import_op.error}")
        
    return gemini_file


Step 3: Initialize the Frontend: Structuring a Multipage Streamlit App


We will use Streamlit's Multipage App feature. It's very simple: any script inside a `pages/` folder becomes a new page in the sidebar.

3.1 The Home Page (`app.py`)

# python
# app.py
import streamlit as st
from dotenv import load_dotenv
load_dotenv()
st.set_page_config(
  page_title="GenAI Knowledge Assistant",
  page_icon="📚",
  layout="wide"
)
st.title("GenAI Knowledge Assistant")
st.subheader("Learn how to build Streamlit AI applications")
st.markdown("""
### How to use this app:
# 1. Go to the Knowledge Base page upload your documents.
# 2. Navigate to the Chat page to interact with your documents.
""")
st.info("Select a page from the sidebar to get started!")


Step 4: Build the Knowledge Base for Uploading and Indexing PDFs


Create `pages/Knowledage_Base.py`. This is where users upload files.

# python
# pages/Knowledage_Base.py
import streamlit as st
import os
from services import gemini
st.set_page_config(page_title="Knowledge Base", page_icon="📂")
st.header("Knowledge Base")
st.write("Upload PDF files to build your Brain.")
# Initialize Client
try:
  client = gemini.get_gemini_client()
except ValueError as e:
  st.error(str(e))
  st.stop()
# Connect to the Store (Once per session for speed)
if "store" not in st.session_state:
  with st.spinner("Connecting to Brain..."):
    st.session_state.store = gemini.get_or_create_file_search_store(client, display_name="Tutorial App")
    st.success(f"Connected to: {st.session_state.store.display_name}")
# File Uploader
uploaded_file = st.file_uploader("Upload a PDF", type=["pdf"])
if uploaded_file and st.button("Upload & Index"):
  with st.spinner("Processing..."):
    # Save locally first (gemini needs a path)
    with open(uploaded_file.name, "wb") as f:
      f.write(uploaded_file.getbuffer())
        
    try:
      # Use our service function
      gemini.upload_and_index_file(
          client, 
          uploaded_file.name, 
          "application/pdf", 
          st.session_state.store.name
      )
      st.success(f"✅ Successfully indexed: {uploaded_file.name}")
    except Exception as e:
      st.error(f"Error: {e}")
    finally:
      # Cleanup local file
      if os.path.exists(uploaded_file.name):
          os.remove(uploaded_file.name)


Step 5: Develop the Chat Interface for Querying Documents with RAG and Citations


Create `pages/Chat.py`. This is the core experience.

5.1 Helper for Citations

First, let's add a small helper to `services/gemini.py` to get readable file names for citations.

# python
# Add this to services/gemini.py
def get_file_id_map(client):
    """Returns a dictionary mapping File IDs to Display Names."""
    all_files = client.files.list()
    file_map = {}
    for f in all_files:
        # Map 'files/123' -> Display Name
        file_map[f.name] = f.display_name
        # Map '123' -> Display Name (because Gemini sometimes returns just the ID)
        file_map[f.name.split("/")[-1]] = f.display_name
    return file_map

5.2 The Chat Logic

Now write `pages/Chat.py`.

# python
# pages/Chat.py
import streamlit as st
import os
from google.genai import types
from services import gemini
st.set_page_config(page_title="Smart Chat", page_icon="💬")
st.header("💬 Chat with your Docs")
# Ensure Client and Store exist
try:
  client = gemini.get_gemini_client()
except Exception as e:
  st.error("Please configure your API key first.")
  st.stop()
if "store" not in st.session_state:
  st.warning("Please go to the **Knowledge Base** page to connect to your store first.")
  st.stop()
# Chat History
if "chat_history" not in st.session_state:
  st.session_state.chat_history = []
# Display History
for msg in st.session_state.chat_history:
  with st.chat_message(msg["role"]):
      st.markdown(msg["content"])
# User Input
if prompt := st.chat_input("Ask a question..."):
  # 1. Show User Message
  st.session_state.chat_history.append({"role": "user", "content": prompt})
  with st.chat_message("user"):
    st.markdown(prompt)
  
  # 2. Generate Response
  with st.chat_message("assistant"):
    message_placeholder = st.empty()
    message_placeholder.markdown("Thinking...")
    
    try:
      # Configure the Tool
      tool = types.Tool(
        file_search=types.FileSearch(
          file_search_store_names=[st.session_state.store.name]
        )
      )
      
      # Call Gemini
      model_name = os.getenv("GEMINI_MODEL_NAME", "gemini-2.5-flash")
      response = client.models.generate_content(
        model=model_name,
        contents=prompt,
        config=types.GenerateContentConfig(
          tools=[tool],
          temperature=0.3
        )
      )
      
      full_response = response.text
      
      # 3. Handle Citations
      citations = []
      if response.candidates[0].grounding_metadata:
        metadata = response.candidates[0].grounding_metadata
        file_map = gemini.get_file_id_map(client) # Get fresh map
        
        unique_sources = set()
        if metadata.grounding_chunks:
          for chunk in metadata.grounding_chunks:
            if chunk.retrieved_context:
              file_title = chunk.retrieved_context.title
              # Try to find readable name
              doc_name = file_map.get(file_title, "Unknown Document")
              # Fallback if map lookup fails (e.g. using IDs directly)
              if doc_name == "Unknown Document":
                # Sometimes title IS the name if not mapped, or we can try to find by ID
                # For simplicity in tutorial, we accept what we find
                pass
            
              if doc_name not in unique_sources:
                unique_sources.add(doc_name)
                citations.append(doc_name)
      
      if citations:
        full_response += "\n\n**Sources:**\n" + "\n".join([f"- {c}" for c in citations])
      
      message_placeholder.markdown(full_response)
      st.session_state.chat_history.append({"role": "assistant", "content": full_response})
        
    except Exception as e:
      st.error(f"Error: {e}")


Step 6: Execute and Validate: Running the Application Locally


Now that we have built our backend, Knowledge Base, and Chat Interface. It is time to bring it all together.

6.1 Start the Streamlit Server

Open your terminal. Make sure you are still inside your project folder and your virtual environment is active.

Run the following command:

# bash
# Start venv
source venv/bin/activate
 
# Start app
streamlit run app.py

6.2 What to Expect

  1. Terminal Output: You should see a success message indicating the server is running, typically at http://localhost:8501.
  2. Browser Launch: Streamlit will automatically open your default web browser to the Gemini RAG Assistant home page.

6.3 Validating the Workflow

Follow this exact sequence to ensure all components are working:

  1. Check API Connection:
    • Navigate to the Knowledge Base page using the sidebar.
    • Look for the success message: Connected to: Tutorial App (or your custom store name). This confirms your services/gemini.py script is correctly reading your .env file.
  2. Upload a Document:
    • Upload a PDF (e.g., an invoice, a resume, or a technical manual).
    • Click "Upload & Index".
    • Watch your terminal: You should see the print statements referencing files.
    • Wait for the green success banner: ✅ Successfully indexed: [filename].
  3. Test the Chat:
    • Switch to the Chat page.
    • Ask a question specifically related to the PDF you just uploaded.
    • Verify Citations: Ensure the response includes the Sources section at the bottom, listing the file you uploaded. This confirms the RAG pipeline is retrieving data correctly.



Conclusion


Congratulations! You have just built a robust, multi-page Smart Document Search application.

Key Takeaways:

  1. Gemini File Search API is a complete RAG pipeline in a box.
  2. Streamlit Pages allow you to build complex app structures easily without messy state management.
  3. No Vector DB Needed: We skipped setting up Chroma/Pinecone entirely.


Get the Complete Source Code


You can find the fully functional source code for this Gemini RAG Assistant on GitHub. 

View the Repository on >> GitHub <<

If you found this tutorial helpful, please give the repo a Star!