googleapis / googleapis/python-genai
INVALID_ARGUMENT when combining Function Calling with retrieval, affecting both manual and automatic function execution
- Dominant language
- Python
- Stars
- 4k
- Forks
- 1k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 40
Description
Function calling together with a Retrieval tool (for example VertexRagStore) gives an error when sending the follow up request with the function calling results. This can bypassed when doing manual function handling by not providing the retrieval tool in the follow up request. However, automatic function calling is also affected, but it seems it could be a quick fix on the SDK side.
#### Environment details
- Programming language: Python
- OS: MacOS
- Language runtime version: Python 3.13
- Package version: 1.30.0
#### Steps to reproduce
```python
#!/usr/bin/env python3
"""
Minimal example demonstrating that combining function calling with Vertex AI RAG
retrieval tool doesn't work properly in Google GenAI.
This script requires:
- google-genai library installed
- Valid GCP credentials configured
- A Vertex AI RAG corpus created (update RAG_CORPUS_RESOURCE_NAME)
"""
import google.genai as genai
from google.genai.types import (
Content, Part, Tool, Retrieval,
VertexRagStore, VertexRagStoreRagResource
)
# Configuration - UPDATE THESE VALUES
PROJECT_ID = os.environ["GCP_PROJECT_ID"] # Your GCP project ID
LOCATION = "europe-west4" # Your GCP location
MODEL_NAME = "gemini-2.5-flash" # Model that supports both tools
VERTEX_RAG_CORPUS = os.environ["VERTEX_RAG_CORPUS"]
RAG_CORPUS_RESOURCE_NAME = f"projects/{PROJECT_ID}/locations/{LOCATION}/ragCorpora/{VERTEX_RAG_CORPUS}"
ALL_PRINTS = []
def print_this(text):
ALL_PRINTS.append(text)
print(text)
# Initialize the client
print_this("Initializing GenAI client...")
client = genai.Client(
vertexai=True,
project=PROJECT_ID,
location=LOCATION
)
# Define a simple function for function calling
def calculate_mortgage_interest(
principal: float,
annual_interest_rate: float,
loan_term_years: int,
extra_payment_annual: float
) -> dict:
"""Calculate mortgage interest with extra payments."""
monthly_rate = annual_interest_rate / 100 / 12
total_months = loan_term_years * 12
if monthly_rate > 0:
monthly_payment = principal * (
monthly_rate * (1 + monthly_rate)**total_months
) / ((1 + monthly_rate)**total_months - 1)
else:
monthly_payment = principal / total_months
# Simplified calculation for demo
total_interest = (monthly_payment * total_months) - principal
return {
"monthly_payment": round(monthly_payment, 2),
"total_interest": round(total_interest, 2),
"message": "Calculation completed successfully"
}
# Create the function declaration
function_declaration = {
"name": "calculate_mortgage_interest",
"description": "Calculate mortgage interest payments",
"parameters": {
"type": "object",
"properties": {
"principal": {
"type": "number",
"description": "Initial loan amount"
},
"annual_interest_rate": {
"type": "number",
"description": "Annual interest rate as percentage"
},
"loan_term_years": {
"type": "integer",
"description": "Loan term in years"
},
"extra_payment_annual": {
"type": "number",
"description": "Extra annual payment amount"
}
},
"required": ["principal", "annual_interest_rate", "loan_term_years", "extra_payment_annual"]
}
}
if __name__ == "__main__":
# Create the RAG retrieval tool
print_this("\nCreating RAG retrieval tool...")
rag_resource = VertexRagStoreRagResource(rag_corpus=RAG_CORPUS_RESOURCE_NAME)
rag_store = VertexRagStore(rag_resources=[rag_resource])
rag_tool = Tool(retrieval=Retrieval(vertex_rag_store=rag_store))
# Create the function calling tool
print_this("Creating function calling tool...")
function_tool = Tool(function_declarations=[function_declaration])
# Test 1: Function calling alone with follow-up (works)
print_this("\n" + "="*60)
print_this("TEST 1: Function calling ONLY with follow-up (should work)")
print_this("="*60)
try:
# Initial request
messages = [Content(
role="user",
parts=[Part.from_text(text="Calculate mortgage interest for a $300,000 loan at 4.5% for 30 years with $0 extra payment")]
)]
response = client.models.generate_content(
model=MODEL_NAME,
contents=messages,
config={
"tools": [function_tool],
"system_instruction": "You are a helpful assistant that can calculate mortgage payments."
}
)
# Handle function call if present
if response.candidates[0].content.parts:
messages.append(response.candidates[0].content) # Add model response
for part in response.candidates[0].content.parts:
if hasattr(part, 'function_call') and part.function_call:
print_this(f"✓ Function call detected: {part.function_call.name}")
print_this(f" Args: {part.function_call.args}")
# Execute the function
result = calculate_mortgage_interest(**part.function_call.args)
print_this(f" Result: {result}")
# Add function response to messages
messages.append(Content(
parts=[Part.from_function_response(
name=part.function_call.name,
response=result
)]
))
# Send follow-up with function result
print_this("\n Sending follow-up with function result...")
follow_up_response = client.models.generate_content(
model=MODEL_NAME,
contents=messages,
config={
"tools": [function_tool], # Same tool config
"system_instruction": "You are a helpful assistant that can calculate mortgage payments."
}
)
if follow_up_response.candidates[0].content.parts:
for follow_part in follow_up_response.candidates[0].content.parts:
if hasattr(follow_part, 'text') and follow_part.text:
print_this(f" Follow-up response: {follow_part.text[:200]}...")
elif hasattr(part, 'text') and part.text:
print_this(f"Text response: {part.text}")
print_this("✓ TEST 1 PASSED: Function calling with follow-up works alone")
except Exception as e:
print_this(f"✗ TEST 1 FAILED: {e}")
# Test 2: RAG retrieval alone (works)
print_this("\n" + "="*60)
print_this("TEST 2: RAG retrieval ONLY (should work)")
print_this("="*60)
try:
response = client.models.generate_content(
model=MODEL_NAME,
contents=[Content(
role="user",
parts=[Part.from_text(text="What information do you have in your knowledge base?")]
)],
config={
"tools": [rag_tool],
"system_instruction": "You are a helpful assistant with access to a knowledge base."
}
)
if response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text:
print_this(f"✓ RAG Response received: {part.text[:200]}...")
print_this("✓ TEST 2 PASSED: RAG retrieval works alone")
except Exception as e:
print_this(f"✗ TEST 2 FAILED: {e}")
# Test 3: Function calling + RAG with follow-up (FAILS on follow-up)
print_this("\n" + "="*60)
print_this("TEST 3: Function calling + RAG with follow-up (fails on follow-up)")
print_this("="*60)
try:
# Initial request with both tools
messages = [Content(
role="user",
parts=[Part.from_text(text="Calculate mortgage for $500,000 at 4% for 30 years with $10000 extra annual payment")]
)]
print_this("Initial request with both tools...")
response = client.models.generate_content(
model=MODEL_NAME,
contents=messages,
config={
"tools": [function_tool, rag_tool], # Both tools
"system_instruction": "You are a helpful assistant with calculation abilities and access to a knowledge base."
}
)
# Handle function call and prepare follow-up
if response.candidates[0].content.parts:
messages.append(response.candidates[0].content) # Add model response
for part in response.candidates[0].content.parts:
if hasattr(part, 'function_call') and part.function_call:
print_this(f"✓ Initial function call detected: {part.function_call.name}")
print_this(f" Args: {part.function_call.args}")
# Execute the function
result = calculate_mortgage_interest(**part.function_call.args)
print_this(f" Result: {result}")
# Add function response to messages
messages.append(Content(
parts=[Part.from_function_response(
name=part.function_call.name,
response=result
)]
))
# This is where it FAILS - sending follow-up with function result when RAG is enabled
print_this("\n Sending follow-up with function result (THIS WILL LIKELY FAIL)...")
follow_up_response = client.models.generate_content(
model=MODEL_NAME,
contents=messages,
config={
"tools": [function_tool, rag_tool], # Both tools still present
"system_instruction": "You are a helpful assistant with calculation abilities and access to a knowledge base."
}
)
if follow_up_response.candidates[0].content.parts:
for follow_part in follow_up_response.candidates[0].content.parts:
if hasattr(follow_part, 'text') and follow_part.text:
print_this(f" Follow-up response: {follow_part.text[:200]}...")
print_this("✓ TEST 3 PASSED: Both tools work together even with follow-up!")
except Exception as e:
print_this(f"✗ TEST 3 FAILED on follow-up: {e}")
print_this("\nThis demonstrates the issue: The follow-up request fails when")
print_this("sending function results back with RAG tool still enabled.")
print_this("Vertex AI tries to run retrieval on the function response turn,")
print_this("which causes INVALID_ARGUMENT errors.")
# Test 4: Both tools initially, but follow-up with function only (should work)
print_this("\n" + "="*60)
print_this("TEST 4: Both tools initially, function-only follow-up (should work)")
print_this("="*60)
try:
# Initial request with both tools
messages = [Content(
role="user",
parts=[Part.from_text(text="Calculate mortgage for $350,000 at 3.5% for 15 years with $2000 extra annual payment")]
)]
print_this("Initial request with both tools...")
response = client.models.generate_content(
model=MODEL_NAME,
contents=messages,
config={
"tools": [function_tool, rag_tool], # Both tools initially
"system_instruction": "You are a helpful assistant with calculation abilities and access to a knowledge base."
}
)
# Handle function call and prepare follow-up
if response.candidates[0].content.parts:
messages.append(response.candidates[0].content) # Add model response
for part in response.candidates[0].content.parts:
if hasattr(part, 'function_call') and part.function_call:
print_this(f"✓ Initial function call detected: {part.function_call.name}")
print_this(f" Args: {part.function_call.args}")
# Execute the function
result = calculate_mortgage_interest(**part.function_call.args)
print_this(f" Result: {result}")
# Add function response to messages
messages.append(Content(
parts=[Part.from_function_response(
name=part.function_call.name,
response=result
)]
))
# Follow-up with ONLY function tool (no RAG) - this should work
print_this("\n Sending follow-up with function tool ONLY (no RAG)...")
follow_up_response = client.models.generate_content(
model=MODEL_NAME,
contents=messages,
config={
"tools": [function_tool], # ONLY function tool in follow-up
"system_instruction": "You are a helpful assistant that can calculate mortgage payments."
}
)
if follow_up_response.candidates[0].content.parts:
for follow_part in follow_up_response.candidates[0].content.parts:
if hasattr(follow_part, 'text') and follow_part.text:
print_this(f" Follow-up response: {follow_part.text[:200]}...")
print_this("✓ TEST 4 PASSED: Removing RAG tool in follow-up works!")
except Exception as e:
print_this(f"✗ TEST 4 FAILED: {e}")
# Test 5: Automatic function execution with both tools (likely fails)
print_this("\n" + "="*60)
print_this("TEST 5: Automatic function execution with RAG (likely fails)")
print_this("="*60)
try:
print_this("Request with automatic function execution and RAG...")
response = client.models.generate_content(
model=MODEL_NAME,
contents=[Content(
role="user",
parts=[Part.from_text(text="Calculate mortgage for $600,000 at 6% for 30 years with $15000 extra annual payment and also check your knowledge base")]
)],
config={
"tools": [calculate_mortgage_interest, rag_tool], # Both tools with automatic execution
"system_instruction": "You are a helpful assistant with calculation abilities and access to a knowledge base."
}
)
# Check response
if response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text:
print_this(f"Response: {part.text[:200]}...")
elif hasattr(part, 'function_call') and part.function_call:
print_this(f"Function was called: {part.function_call.name}")
print_this("✓ TEST 5 PASSED: Automatic function execution with RAG works!")
except Exception as e:
print_this(f"✗ TEST 5 FAILED: {e}")
print_this("\nThis demonstrates that even automatic function execution")
print_this("fails when combined with RAG retrieval tools.")
print_this("\n" + "="*60)
print_this("SUMMARY:")
# Summarize results
with open(__file__, "r") as f:
FILE_CONTENT = f.read()
response = client.models.generate_content(
model=MODEL_NAME,
contents=[Content(
role="user",
parts=[
Part.from_text(text="Analyze the following python script logs and summarize what is wrong with the genai library"),
Part.from_text(text=FILE_CONTENT),
Part.from_text(text="\n".join(ALL_PRINTS)),
]
)],
config={
"system_instruction": "You are an expert debugger."
}
)
print(response.candidates[0].content.parts[0].text)
```
This generates the following output:
```
Initializing GenAI client...
Creating RAG retrieval tool...
Creating function calling tool...
============================================================
TEST 1: Function calling ONLY with follow-up (should work)
============================================================
✓ Function call detected: calculate_mortgage_interest
Args: {'extra_payment_annual': 0, 'loan_term_years': 30, 'annual_interest_rate': 4.5, 'principal': 300000}
Result: {'monthly_payment': 1520.06, 'total_interest': 247220.13, 'message': 'Calculation completed successfully'}
Sending follow-up with function result...
Follow-up response: The total interest paid will be $247,220.13, and the monthly payment will be $1,520.06....
✓ TEST 1 PASSED: Function calling with follow-up works alone
============================================================
TEST 2: RAG retrieval ONLY (should work)
============================================================
✓ RAG Response received: Based on the provided sources, my knowledge base contains information about:
* This framework classifies individuals into four...
✓ TEST 2 PASSED: RAG retrieval works alone
============================================================
TEST 3: Function calling + RAG with follow-up (fails on follow-up)
============================================================
Initial request with both tools...
✓ Initial function call detected: calculate_mortgage_interest
Args: {'principal': 500000, 'extra_payment_annual': 10000, 'loan_term_years': 30, 'annual_interest_rate': 4}
Result: {'monthly_payment': 2387.08, 'total_interest': 359347.53, 'message': 'Calculation completed successfully'}
Sending follow-up with function result (THIS WILL LIKELY FAIL)...
✗ TEST 3 FAILED on follow-up: 400 INVALID_ARGUMENT. {'error': {'code': 400, 'message': 'Request contains an invalid argument.', 'status': 'INVALID_ARGUMENT'}}
This demonstrates the issue: The follow-up request fails when
sending function results back with RAG tool still enabled.
Vertex AI tries to run retrieval on the function response turn,
which causes INVALID_ARGUMENT errors.
============================================================
TEST 4: Both tools initially, function-only follow-up (should work)
============================================================
Initial request with both tools...
✓ Initial function call detected: calculate_mortgage_interest
Args: {'principal': 350000, 'extra_payment_annual': 2000, 'loan_term_years': 15, 'annual_interest_rate': 3.5}
Result: {'monthly_payment': 2502.09, 'total_interest': 100376.0, 'message': 'Calculation completed successfully'}
Sending follow-up with function tool ONLY (no RAG)...
Follow-up response: Your monthly payment will be $2502.09, with a total interest of $100376....
✓ TEST 4 PASSED: Removing RAG tool in follow-up works!
============================================================
TEST 5: Automatic function execution with RAG (likely fails)
============================================================
Request with automatic function execution and RAG...
✗ TEST 5 FAILED: 400 INVALID_ARGUMENT. {'error': {'code': 400, 'message': 'Request contains an invalid argument.', 'status': 'INVALID_ARGUMENT'}}
This demonstrates that even automatic function execution
fails when combined with RAG retrieval tools.
```
============================================================
SUMMARY:
The provided Python script and its execution logs highlight a critical issue within the `google-genai` library (and potentially the underlying Vertex AI API it interfaces with) regarding the simultaneous use of Function Calling and Vertex AI RAG Retrieval tools.
Here's a summary of what is wrong:
1. **Incompatibility in Multi-Turn Interactions (Manual Function Execution):**
* **The Problem (Test 3 Failure):** When both Function Calling tools and RAG Retrieval tools are enabled in the `config["tools"]` parameter, and the user attempts to send a `Content` part containing a `from_function_response` (i.e., feeding the result of a function call back to the model), the `generate_content` request fails with a `400 INVALID_ARGUMENT` error.
* The script's analysis suggests that Vertex AI incorrectly attempts to apply retrieval logic to the function response turn, which leads to this error.
* This means a natural multi-turn conversation flow where the model invokes a function, the function is executed, and its result is fed back to the model for a subsequent response *breaks* if RAG is also active for that turn.
2. **Incompatibility with Automatic Function Execution:**
* **The Problem (Test 5 Failure):** Even when using the `google-genai` library's automatic function execution feature (where you pass the Python function callable directly to `config["tools"]`), combining it with the RAG tool causes the initial `generate_content` call to fail immediately with a `400 INVALID_ARGUMENT` error.
* This indicates that the underlying conflict between RAG and function processing is not limited to manual multi-turn handling but also affects the library's built-in automatic tool execution mechanism.
**In essence, the core issue is that the `google-genai` library/Vertex AI API cannot properly handle requests where both Vertex AI RAG Retrieval tools and Function Calling tools (especially for function response processing or automatic execution) are simultaneously present in the `tools` configuration.**
**Workaround (Identified in Test 4 Success):**
The script successfully demonstrates a workaround:
* If you need to perform a function call within a conversation that *also* has RAG capabilities, you must **explicitly remove the RAG tool from the `config["tools"]` list** when sending the `from_function_response` (the function's output) back to the model. Once the function execution turn is complete and you want the model to resume a normal text response, you can re-include the RAG tool if necessary.
This limitation significantly impacts the ability to build sophisticated agents that can seamlessly leverage both external knowledge bases (via RAG) and external capabilities (via function calling) in a single, fluid interaction.
Contributor guide
Assessment
This issue has not been assessed yet.