Google Image Input File Upload

Upload an image through the Gemini Files API and combine it with web search.

The source uses the legacy upload SDK and passes its File object to Image.content, which accepts bytes. Use the current adaptation below instead of running the source snapshot unchanged.

image_input_file_upload.py
"""
Google Image Input File Upload
==============================

Cookbook example for `google/gemini/image_input_file_upload.py`.
"""

from pathlib import Path

from agno.agent import Agent
from agno.media import Image
from agno.models.google import Gemini
from agno.tools.websearch import WebSearchTools
from google.generativeai import upload_file
from google.generativeai.types import file_types

# ---------------------------------------------------------------------------
# Create Agent
# ---------------------------------------------------------------------------

agent = Agent(
    model=Gemini(id="gemini-3.7-flash"),
    tools=[WebSearchTools()],
    markdown=True,
)
# Please download the image using
# wget https://upload.wikimedia.org/wikipedia/commons/b/bf/Krakow_-_Kosciol_Mariacki.jpg
image_path = Path(__file__).parent.joinpath("Krakow_-_Kosciol_Mariacki.jpg")
image_file: file_types.File = upload_file(image_path)
print(f"Uploaded image: {image_file}")

agent.print_response(
    "Tell me about this image and give me the latest news about it.",
    images=[Image(content=image_file)],
    stream=True,
)

# ---------------------------------------------------------------------------
# Run Agent
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    pass

Current adaptation

Place Krakow_-_Kosciol_Mariacki.jpg beside this script. You can set GOOGLE_FILE_NAME to a previously uploaded file's exact files/... name to reuse it; otherwise this example uploads and removes its own file. Both queries complete before cleanup.

Save this helper as google_files.py beside the runnable example. It accepts a local file or an existing Files API name, waits up to five minutes for ACTIVE, and rejects failed or incomplete uploads. It deletes only files it uploaded itself; an existing file remains owned by its caller. Developer API uploads otherwise expire after 48 hours.

google_files.py
from contextlib import contextmanager
from pathlib import Path
from time import monotonic, sleep


@contextmanager
def ready_file(client, path: Path, existing_name: str | None = None):
    existing_name = existing_name or None
    if existing_name is None and not path.is_file():
        raise FileNotFoundError(path)

    uploaded = (
        client.files.get(name=existing_name)
        if existing_name
        else client.files.upload(file=path)
    )
    owned_name = uploaded.name if existing_name is None else None
    try:
        deadline = monotonic() + 300
        while True:
            state = uploaded.state.name if uploaded.state else None
            if state == "ACTIVE":
                if not uploaded.uri or not uploaded.mime_type:
                    raise RuntimeError("Active file has no URI or MIME type")
                yield uploaded
                return
            if state == "FAILED":
                raise RuntimeError(f"File processing failed: {uploaded.name}")
            if state != "PROCESSING" or not uploaded.name:
                raise RuntimeError(f"Unexpected file state: {state}")
            if monotonic() >= deadline:
                raise TimeoutError("File processing exceeded five minutes")
            sleep(2)
            uploaded = client.files.get(name=uploaded.name)
    finally:
        if owned_name:
            client.files.delete(name=owned_name)
query_uploaded_image.py
from os import environ
from pathlib import Path

from agno.agent import Agent
from agno.db.in_memory import InMemoryDb
from agno.media import File
from agno.models.google import Gemini
from agno.tools.websearch import WebSearchTools
from google_files import ready_file

model = Gemini(id="gemini-3.7-flash")
agent = Agent(
    model=model,
    db=InMemoryDb(),
    add_history_to_context=True,
    tools=[WebSearchTools()],
    markdown=True,
)
path = Path(__file__).parent / "Krakow_-_Kosciol_Mariacki.jpg"
with ready_file(model.get_client(), path, environ.get("GOOGLE_FILE_NAME")) as uploaded:
    agent.print_response(
        "Describe this image and search for related news.",
        files=[File(external=uploaded)],
        stream=True,
    )
    agent.print_response("Summarize the main point from our conversation.")

File(external=...) passes the uploaded file URI and MIME type. InMemoryDb retains this conversation only within the process. File access must remain valid while history containing its URI is reused.

Run the Example

Set up your virtual environment

uv venv --python 3.12
source .venv/bin/activate

Install dependencies

uv pip install -U agno ddgs google-genai

Export your Google API key

export GOOGLE_API_KEY="your_google_api_key_here"

Run the example

Save the current adaptation as query_uploaded_image.py and any helper beside it, then run:

python query_uploaded_image.py

Full source: cookbook/90_models/google/gemini/image_input_file_upload.py