Gemini ignores tool responses when processing PDF

There seems to be a regression when using the Gemini to process PDF files and analyzing the content with the help of tools. Gemini ignores the responses from the tools and claims that the queries came up empty. Processing an image works. Using the tools with just a text prompts works as well. This seems to be broken across all Gemini models. I’ll attach a repro below.

Gemini simply ignores the function_response Parts of the messages sent to the API when previously a PDF was processed. If no file is processed or an image is processed instead, things work as expected.

The files image_with_names.jpg and pdf_with_names.pdf are just very simple images or pdf files containing a list of names (Nils, Kirsten, Eve, Frank, George).

The bug (processing with PDF):

python example.py --pdf pdf_with_names.pdf

…[output abbreviated]…

[FINAL ANSWER]:
I received no results from the tool for the following queries: Nils, Kirsten, Eve, Frank, George, Harry.

Here are the first names found in the document and their corresponding last names:

  • Nils: unknown
  • Kirsten: unknown
  • Eve: unknown
  • Frank: unknown
  • George: unknown
  • Harry: unknown

Correct behavior (when processing image or text only):

python example.py --image image_with_names.jpg

…[output abbreviated]…

[FINAL ANSWER]:
Here are the last names for the first names you provided:
Nils: New York
Kirsten: Seattle
Eve: Chicago
Frank: Los Angeles
George: Miami
I received no results for the first name Harry and the last name is unknown.

python example.py

…[output abbreviated]…

[FINAL ANSWER]:
Nils: New York
Kirsten: Seattle
Eve: Chicago
Frank: Los Angeles
George: Miami
For the first name Harry, I received no results from the tool.
Harry: unknown

Example repro:

"""
Minimal test to verify Gemini API with thinking mode + tool use.
This tests whether Gemini can properly receive and interpret function call results.

Test modes:
  --pdf      : Include a PDF file (mimics PDF analyzer exactly)
  --image    : Include an image file (mimics image analyzer exactly)
"""
import sys
import os
from google import genai
from google.genai import types

# Test with different models
#MODEL_NAME = "gemini-3-pro-preview" 
MODEL_NAME = "gemini-2.5-pro" 
#MODEL_NAME = "gemini-2.5-flash" 

API_KEY = '<insert your API key here>'

# Parse command line args
TEST_MODE = "simple"
PDF_PATH = None
IMAGE_PATH = None
if len(sys.argv) > 1:
    if len(sys.argv) < 2 or len(sys.argv) > 3 or (sys.argv[1] != "--pdf" and sys.argv[1] != "--image"):
        print("Usage: --pdf <path_to_pdf> | --image <path_to_image>")
        print("Example: python  --pdf /path/to/document.pdf | --image /path/to/image.jpg")
        sys.exit(1)
    if sys.argv[1] == "--pdf":
        TEST_MODE = "pdf"
        PDF_PATH = sys.argv[2]
    if sys.argv[1] == "--image":
        TEST_MODE = "image"
        IMAGE_PATH = sys.argv[2]

def lookup_last_names(query: str) -> dict:
    """Look up last name in a database based on first name.
    
    Args:
        query: First name.
    """
    # We provide some unique answers to spot hallucinations.
    # Gemini sometimes makes up results.
    print(f"\n[Tool Execution] lookup_last_names called with query='{query}'")    
    if query == "Eve":
        return {"last_name": "Chicago"}
    elif query == "Frank":
        return {"last_name": "Los Angeles"}
    elif query == "George":
        return {"last_name": "Miami"}
    elif query == "Nils":
        return {"last_name": "New York"}
    elif query == "Kirsten":
        return {"last_name": "Seattle"}
    # We'll not return results for Harry.
    return {"last_name": "Unknown"}

def main():
    print(f"Test mode: {TEST_MODE}")
    print("=" * 60)
    
    client = genai.Client(api_key=API_KEY)

    # Define tools - mimics PDF analyzer's two search functions
    function_declarations = [
        types.FunctionDeclaration(
            name="lookup_last_names",
            description="Look up last name in a database based on first name.",
            parameters=types.Schema(
                type=types.Type.OBJECT,
                properties={
                    "query": types.Schema(
                        type=types.Type.STRING,
                        description="The search query"
                    )
                },
                required=["query"]
            )
        )
    ]
    
    search_tool = types.Tool(function_declarations=function_declarations)

    # Config with thinking + tools
    config = types.GenerateContentConfig(
        tools=[search_tool],
        thinking_config=types.ThinkingConfig(include_thoughts=True)
    )

    common_prompt = """Extract a list of first names and use the tools to look up the last 
        names in the database. If you receive no results from the tool, you MUST explicitly state that you received no results.
        List all the first names found and the last names in your final response.
        Important, if you received no results from the tool, you MUST explicitly state that you received no results
        and state 'unknown' as the last name."""

    if TEST_MODE == "pdf":
        prompt = "Analyze this PDF document. " + common_prompt
        
        # Upload the PDF file
        print(f"Uploading PDF: {PDF_PATH}")
        uploaded_file = client.files.upload(file=PDF_PATH)
        print(f"Uploaded: {uploaded_file.name}")
        
        messages = [
            types.Content(
                role="user",
                parts=[
                    types.Part.from_text(text=prompt),
                    types.Part.from_uri(file_uri=uploaded_file.uri, mime_type='application/pdf'),
                ]
            )
        ]
    elif TEST_MODE == "image":
        prompt = "Analyze this image. " + common_prompt

        # Upload the image file
        print(f"Uploading image: {IMAGE_PATH}")
        uploaded_file = client.files.upload(file=IMAGE_PATH)
        print(f"Uploaded: {uploaded_file.name}")
        
        messages = [
            types.Content(
                role="user",
                parts=[
                    types.Part.from_text(text=prompt),
                    types.Part.from_uri(file_uri=uploaded_file.uri, mime_type='image/jpeg'),
                ]
            )
        ]
    else:
        prompt = "Please consider this sentence: 'First Names: Nils, Kirsten, Eve, Frank, George, and Harry'. " + common_prompt

        messages = [
            types.Content(role="user", parts=[types.Part.from_text(text=prompt)])
        ]

    print(f"User Query: {prompt}\n")
    print("=" * 60)

    try:
        # First call
        print("\n--- Call 1: Sending initial request ---")
        response = client.models.generate_content(
            model=MODEL_NAME,
            contents=messages,
            config=config
        )

        iteration = 0
        max_iterations = 5

        while iteration < max_iterations:
            iteration += 1
            print(f"\n--- Iteration {iteration} ---")

            # Print thoughts
            for candidate in response.candidates:
                for part in candidate.content.parts:
                    if getattr(part, 'thought', None):
                        print(f"\n[THOUGHT]:\n{part.text[:500]}..." if len(part.text) > 500 else f"\n[THOUGHT]:\n{part.text}")

            # Check for function calls
            function_calls = []
            for candidate in response.candidates:
                for part in candidate.content.parts:
                    if getattr(part, 'function_call', None):
                        function_calls.append(part.function_call)

            if not function_calls:
                # No more tools, print final answer
                print("\n[NO FUNCTION CALLS - Final Response]")
                for candidate in response.candidates:
                    for part in candidate.content.parts:
                        if getattr(part, 'text', None) and not getattr(part, 'thought', None):
                            print(f"\n[FINAL ANSWER]:\n{part.text}")
                break

            # Execute tools and build responses
            print(f"\n[FUNCTION CALLS DETECTED]: {len(function_calls)}")
            
            # Add model's response to history (INCLUDING thoughts)
            messages.append(response.candidates[0].content)
            print(f"[DEBUG] Added model content to messages. Role: {response.candidates[0].content.role}")

            tool_outputs = []
            for call in function_calls:
                fn_name = call.name
                fn_args = call.args
                print(f"[EXECUTING] {fn_name}({fn_args})")

                if fn_name == "lookup_last_names":
                    result = lookup_last_names(**fn_args)
                else:
                    result = {"error": f"Unknown function: {fn_name}"}

                print(f"[RESULT] {result}")

                # Create function response - test both formats
                tool_outputs.append(
                    types.Part.from_function_response(
                        name=fn_name,
                        response={"result": result}  # Wrapped in {"result": ...}
                    )
                )

            # Add tool outputs with role="tool"
            tool_content = types.Content(role="tool", parts=tool_outputs)
            messages.append(tool_content)
            print(f"[DEBUG] Added tool content to messages. Role: tool")
            print(f"[DEBUG] Total messages: {len(messages)}")

            # Show what we're sending
            print("\n[DEBUG] Message history:")
            for i, msg in enumerate(messages):
                role = getattr(msg, 'role', 'unknown')
                num_parts = len(msg.parts) if hasattr(msg, 'parts') else 0
                print(f"  [{i}] role={role}, parts={num_parts}")

            # Send back to Gemini
            print("\n--- Sending tool results back to Gemini ---")
            response = client.models.generate_content(
                model=MODEL_NAME,
                contents=messages,
                config=config
            )

        if iteration >= max_iterations:
            print(f"\n[WARNING] Reached max iterations ({max_iterations})")

    except Exception as e:
        import traceback
        print(f"\n[ERROR] {e}")
        traceback.print_exc()
    
    finally:
        # Cleanup uploaded PDF if any
        if TEST_MODE == "pdf" and 'uploaded_file' in dir():
            try:
                client.files.delete(name=uploaded_file.name)
                print(f"\n[CLEANUP] Deleted uploaded file: {uploaded_file.name}")
            except Exception as e:
                print(f"\n[CLEANUP WARNING] Could not delete file: {e}")

if __name__ == "__main__":
    main()
3 Likes

I’m facing the same issue - any update yet?

1 Like

We’re facing the same issue. When we use the Vertex API, it seems to be working, but we were struggling with the Gemini API.

1 Like

same issue, it keeps trying the same tool call again and again for me

I have managed to get around this.

  1. Send in the PDF
  2. Gemini does a tool call
  3. When you send the tool call response back - don’t resend the PDF.

In fact once it has been initially sent, I never send it back in on subsequent requests, if I do I get the error you describe. It seems to remember the result of scanning the PDF.

This works on 2.5 flash, 3 pro preview and 3 flash.

Unfortunately, it doesn’t remember anymore, you can try by asking it to generate a summary after a few dummy function call, it’s complete hallucination.

I’m facing the same issue myself, is there any update from Google?

Hello,

Thank you for sharing the code. We attempted to reproduce the issue using the code snippet provided in the following post:

However, we were unable to reproduce the error; the model successfully called the tool in all three cases (image, PDF, and text). If you are still experiencing this problem, please let us know and provide further details (prompt, code snippet, model, model configuration, etc.) to assist us in reproducing the issue.

Thanks for flagging this and sorry about the delay. We’re working on a fix here, it should be rolled out soon, I will update this thread when it’s live.

Hi again, this should be fixed now, please give it another go and let us know if the issue persists.

Yes, it seems to be working for me now. Thanks!