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()