Google File Search Basic
Create a Gemini File Search store, upload a document, and query it with citations.
The source snapshot treats a completed upload operation as success without checking its error, and cleanup is not guaranteed on failures. Use the current adaptation below. It validates local inputs before creating stores, checks indexing errors, and attempts cleanup of its own stores in finally.
"""
Google File Search Basic
========================
Cookbook example for `google/gemini/file_search_basic.py`.
"""
from pathlib import Path
from agno.agent import Agent
from agno.models.google import Gemini
# ---------------------------------------------------------------------------
# Create Agent
# ---------------------------------------------------------------------------
# Create Gemini model
model = Gemini(id="gemini-3.7-flash")
# Create agent with the model
agent = Agent(model=model, markdown=True)
print("Creating File Search store...")
store = model.create_file_search_store(display_name="Basic Demo Store")
print(f"[OK] Created store: {store.name}")
print("\nUploading file to store...")
# Upload a file directly to the File Search store
operation = model.upload_to_file_search_store(
file_path=Path(__file__).parent / "documents" / "sample.txt",
store_name=store.name,
display_name="Sample Document",
)
# Wait for upload to complete
print("Waiting for upload to complete...")
completed_op = model.wait_for_operation(operation)
print("[OK] Upload completed")
# Configure model to use File Search
model.file_search_store_names = [store.name]
# Query the documents
print("\nQuerying documents...")
run = agent.run(
"Can you tell me about the content in the uploaded document? Specifically, what are the main safety guidelines mentioned?"
)
print(f"\nResponse:\n{run.content}")
# Extract and display citations
print("\n" + "=" * 50)
if run.citations and run.citations.raw:
print("Citations:")
print("=" * 50)
# Access grounding metadata directly from citations
grounding_metadata = run.citations.raw.get("grounding_metadata", {})
chunks = grounding_metadata.get("grounding_chunks", []) or []
sources = set()
for chunk in chunks:
if isinstance(chunk, dict):
retrieved_context = chunk.get("retrieved_context")
if isinstance(retrieved_context, dict):
title = retrieved_context.get("title", "Unknown")
sources.add(title)
if sources:
print(f"\nSources ({len(sources)}):")
for i, source in enumerate(sorted(sources), 1):
print(f" [{i}] {source}")
print(f"\nDetailed Citations ({len(chunks)}):")
for i, chunk in enumerate(chunks, 1):
if isinstance(chunk, dict):
retrieved_context = chunk.get("retrieved_context")
if isinstance(retrieved_context, dict):
print(f"\n [{i}] {retrieved_context.get('title', 'Unknown')}")
if retrieved_context.get("uri"):
print(f" URI: {retrieved_context['uri']}")
print(" Type: file_search")
if retrieved_context.get("text"):
text = retrieved_context["text"]
if len(text) > 200:
text = text[:200] + "..."
print(f" Text: {text}")
else:
print("Citations metadata found but no File Search sources detected")
else:
print("No citations found in response")
# Cleanup
print("\n" + "=" * 50)
print("Cleaning up...")
model.delete_file_search_store(store.name)
print("[OK] Store deleted")
# ---------------------------------------------------------------------------
# Run Agent
# ---------------------------------------------------------------------------
if __name__ == "__main__":
passCurrent adaptation
File Search stores persist until deleted, subject to embedding-model lifecycle limits. They are separate from the Files API's expiring uploads. These demo scripts create their own stores and use force=True to remove their documents during cleanup. Do not substitute a shared production store name. If cleanup fails, use the reported store name to delete the demo resource after resolving the error.
The operation's done state is not proof of successful indexing: inspect completed.error before querying. Citations depend on the generated answer; an empty citation field is possible.
Save this adaptation as file_search_basic_current.py. The original block above remains the pinned cookbook source.
from pathlib import Path
from agno.agent import Agent
from agno.models.google import Gemini
path = Path("cookbook/90_models/google/gemini/documents/sample.txt")
if not path.is_file():
raise FileNotFoundError(path)
model = Gemini(id="gemini-3.7-flash")
store = model.create_file_search_store(display_name="Basic Demo Store")
try:
operation = model.upload_to_file_search_store(file_path=path, store_name=store.name)
completed = model.wait_for_operation(operation)
if completed.error:
raise RuntimeError(f"Indexing failed: {completed.error}")
model.file_search_store_names = [store.name]
result = Agent(model=model, markdown=True).run("What safety guidelines are documented?")
print(result.content)
print(result.citations)
finally:
model.delete_file_search_store(store.name, force=True)Run the Example
Set up your virtual environment
uv venv --python 3.12
source .venv/bin/activateInstall dependencies
uv pip install -U agno google-genaiExport your Google API key
export GOOGLE_API_KEY="your_google_api_key_here"Clone Agno
Clone the pinned Agno source and run the remaining commands from its root:
git clone https://github.com/agno-agi/agno.git
cd agno
git checkout 8f36eaf2d18e91afa7b327eec66a3cd3685dcb87Prepare demo documents
The required text files are not supplied by the repository. Save this as prepare_documents.py at its root and run python prepare_documents.py. It leaves existing files intact.
from pathlib import Path
folder = Path("cookbook/90_models/google/gemini/documents")
folder.mkdir(parents=True, exist_ok=True)
path = folder / "sample.txt"
if not path.exists():
path.write_text('Safety: Disconnect power before maintenance. Wear eye protection. Keep emergency exits clear.', encoding="utf-8")Run the example
Save the current adaptation as file_search_basic_current.py at the repository root, then run:
python file_search_basic_current.pyFull source: cookbook/90_models/google/gemini/file_search_basic.py