Skip to content

Conversation

@paxiaatucsdedu
Copy link
Member

Problem

When using Google Gemini models with tool calling, AIMessages with empty content (content="") cause a TransientServiceError (HTTP 500) due to a NullPointerException in the OCI backend when converting messages to Google's format.

Issue link: #78

Solution

Check for empty content in AIMessages with tool calls before processing, and provide a minimal placeholder (.) to prevent null serialization. This follows the same pattern used by the Cohere provider.

Testing

Tested with google.gemini-2.5-flash model using AIMessage with empty content and tool calls - error is now resolved.

Model response:
content='The weather in Rome is currently sunny with a temperature of 20 degrees Celsius.' additional_kwargs={'finish_reason': 'stop'} response_metadata={'finish_reason': 'stop'} id='run--68c2e374-d8eb-43be-aec2-de2815789dc4-0'

Adds a check for empty message content before processing in GenericProvider, returning a default text when content is missing.
@oracle-contributor-agreement oracle-contributor-agreement bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Dec 10, 2025
@paxiaatucsdedu
Copy link
Member Author

@luigisaetta Does this resolve your issue?

# Issue 78 fix: Check if original content is empty BEFORE processing
# to prevent NullPointerException in OCI backend
else:
content = [self.oci_chat_message_text_content(text=".")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so there has to be an arbitrary character in the the text?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I tried to use an empty space as the content, but empty space does not work.

@luigisaetta
Copy link
Member

Should this PR be merged with: #81?
They seems to address similar issues (support for Gemini calls with tools)?

@paxiaatucsdedu
Copy link
Member Author

Should this PR be merged with: #81? They seems to address similar issues (support for Gemini calls with tools)?

Gemini tool calls work with non-empty content field.

@fede-kamel
Copy link
Contributor

Hey @paxiaatucsdedu, thanks for working on this fix! I wanted to share some test results that might be helpful.

I ran integration tests to check the full tool calling flow with Gemini, and found that the empty content fix alone may not be enough for the complete agentic loop.

Test Evidence

Test 1: GenericProvider (current approach) - FAILS

============================================================
Testing GenericProvider with google.gemini-2.5-flash
============================================================

Step 1: Getting tool calls from Gemini...
Response: tool_calls=[{'name': 'get_weather', 'args': {'city': 'Rome'}, 'id': None, 'type': 'tool_call'}]
Tool calls: [{'name': 'get_weather', 'args': {'city': 'Rome'}, 'id': None, 'type': 'tool_call'}]

Step 2: Sending tool result back to Gemini...
Messages being sent: ['HumanMessage', 'AIMessage', 'ToolMessage']

Expected failure after 0.0s:
Error type: ServiceError
Error: {'status': 400, 'message': "Missing required parameter: 'messages[1].toolCalls[0].id'."}

✓ Confirmed: GenericProvider fails for Gemini tool loop
   Root cause: OCI cannot properly handle tool messages for Gemini models
PASSED

Test 2: GeminiProvider (PR #81 approach) - SUCCEEDS

============================================================
Testing GeminiProvider with google.gemini-2.5-flash
============================================================

Step 1: Getting tool calls from Gemini...
Response: tool_calls=[{'name': 'get_weather', 'args': {'city': 'Rome'}, 'id': None, 'type': 'tool_call'}]
Tool calls: [{'name': 'get_weather', 'args': {'city': 'Rome'}, 'id': None, 'type': 'tool_call'}]

Step 2: Sending tool result back to Gemini...
Messages being sent: ['HumanMessage', 'AIMessage', 'ToolMessage']

Success after 0.7s!
Final response: The weather in Rome is sunny, 22°C.

✓ Confirmed: GeminiProvider works for Gemini tool loop
PASSED

======================== 2 passed in 3.98s ========================

Issues Found

  1. Gemini returns tool calls with id: None (no tool_call_id)
  2. The ToolMessage type doesn't translate correctly to Gemini's native format
  3. The AssistantMessage.tool_calls field causes issues with OCI's Gemini translation

Suggestion

In PR #81, I worked around these OCI translation limitations by converting tool-related messages to regular user/assistant messages. This has 38 passing integration tests including real agent workflows.

Would it make sense to combine our efforts? Happy to discuss!

@paxiaatucsdedu
Copy link
Member Author

Hi @fede-kamel, thank you for testing it. I tested with the test python script from this issue description. Gemini works with no problem as long as the content is not empty, and the Gemini model response is very similar to the grok-4 model (Grok-4 is mentioned in the issue description as working properly).

This is the Jupyter notebook I used for testing with PR #79
issue_78.ipynb

@fede-kamel
Copy link
Contributor

Here's the minimal test code to reproduce:

from langchain_core.messages import HumanMessage, ToolMessage
from langchain_core.tools import tool
from langchain_oci.chat_models import ChatOCIGenAI

@tool
def get_weather(city: str) -> str:
    """Get weather for a city."""
    return f"Sunny, 22°C in {city}"

# Create LLM with tools
llm = ChatOCIGenAI(model_id="google.gemini-2.5-flash", ...)
llm_with_tools = llm.bind_tools([get_weather])

# Step 1: Get tool calls (works)
messages = [HumanMessage(content="What's the weather in Rome?")]
response = llm_with_tools.invoke(messages)
# Returns: tool_calls=[{'name': 'get_weather', 'args': {'city': 'Rome'}, 'id': None}]

# Step 2: Send tool result back (fails)
messages.append(response)
messages.append(ToolMessage(
    content="Sunny, 22°C in Rome",
    tool_call_id="call_get_weather",  # Note: Gemini returned id=None
    name="get_weather",
))
final = llm_with_tools.invoke(messages)  # 💥 400 error

The error occurs because OCI can't translate ToolMessage + AIMessage.tool_calls for Gemini models.

@fede-kamel
Copy link
Contributor

Hi @paxiaatucsdedu! Thanks for sharing the notebook - it helped me understand your test setup better.

I did some additional testing and found something interesting. The notebook test works because it uses a manually constructed conversation history with a hardcoded tool_call_id:

# From notebook - REQUEST 2
AIMessage(content="", tool_calls=[{"id": "call_ed2635c686f449eea25915b2", ...}])
ToolMessage(content="<tool_response!>", tool_call_id="call_ed2635c686f449eea25915b2")

However, when I tested the real agentic flow (asking Gemini, getting its response, executing tool, sending result back), I found that Gemini actually returns id: None:

[STEP 1] User asks: 'What is the weather in Rome?'

Gemini response:
  tool_calls: [{'name': 'get_weather', 'args': {'city': 'Rome'}, 'id': None}]
                                                                  ^^^^^^^^
                                                                  Gemini returns None!

[STEP 2] Execute tool → Result: 'Sunny, 22°C in Rome'

[STEP 3] Send tool result back to Gemini...
  ⚠️ Problem: tool_call_id is None!

[RESULT] ✗ FAILED
  Error: "Missing required parameter 'toolCalls[0].id'"

So the issue is:

Scenario tool_call_id Result
Notebook (hardcoded) "call_ed2635..." ✅ Works
Real Gemini response None ❌ Fails

Your empty content fix is valid and needed (Test 1→2 in my earlier tests), but there's this additional id: None issue that also needs to be addressed for the full agentic loop to work.

Happy to share the test script if you'd like to reproduce this on your end!

@fede-kamel
Copy link
Contributor

And just to show how PR #81 addresses this - the GeminiProvider works around these OCI translation limitations by converting tool messages to regular messages:

# Instead of sending ToolMessage (which OCI can't translate for Gemini)
# GeminiProvider converts it to a UserMessage:

ToolMessage(content="Sunny, 22°C") 
    → UserMessage(content="Function get_weather returned: Sunny, 22°C")

# And AIMessage with tool_calls (where id=None is a problem)
# becomes a regular AssistantMessage:

AIMessage(content="", tool_calls=[{id: None, name: "get_weather", ...}])
    → AssistantMessage(content="I'll call get_weather with arguments: {\"city\": \"Rome\"}")

This bypasses both issues:

  1. ✅ No empty content problem (converted to descriptive text)
  2. ✅ No id: None problem (tool_calls field is not sent to OCI)

Same test with GeminiProvider:

[STEP 1] Gemini returns tool_calls with id=None ← same as before
[STEP 2] Execute tool ← same as before  
[STEP 3] Send result back → ✅ Success!
         Final response: "The weather in Rome is sunny, 22°C."

Both PRs could work together - your fix handles the empty content edge case, and the GeminiProvider handles the message format translation. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OCA Verified All contributors have signed the Oracle Contributor Agreement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants