mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-09 00:26:01 +00:00
Merge pull request #4522 from andrewmjc/matchingToolMessageSpec
[Bug Fix] Use OpenAI Tool Response Spec When Converting To Gemini/VertexAI Tool Response
This commit is contained in:
@@ -1026,16 +1026,17 @@ def convert_to_gemini_tool_call_invoke(
|
||||
|
||||
def convert_to_gemini_tool_call_result(
|
||||
message: dict,
|
||||
last_message_with_tool_calls: dict|None,
|
||||
) -> litellm.types.llms.vertex_ai.PartType:
|
||||
"""
|
||||
OpenAI message with a tool result looks like:
|
||||
{
|
||||
"tool_call_id": "tool_1",
|
||||
"role": "tool",
|
||||
"name": "get_current_weather",
|
||||
"role": "tool",
|
||||
"content": "function result goes here",
|
||||
},
|
||||
|
||||
# NOTE: Function messages have been deprecated
|
||||
OpenAI message with a function call result looks like:
|
||||
{
|
||||
"role": "function",
|
||||
@@ -1044,7 +1045,19 @@ def convert_to_gemini_tool_call_result(
|
||||
}
|
||||
"""
|
||||
content = message.get("content", "")
|
||||
name = message.get("name", "")
|
||||
name = ""
|
||||
|
||||
# Recover name from last message with tool calls
|
||||
if last_message_with_tool_calls:
|
||||
tools = last_message_with_tool_calls.get("tool_calls", [])
|
||||
msg_tool_call_id = message.get("tool_call_id", None)
|
||||
for tool in tools:
|
||||
prev_tool_call_id = tool.get("id", None)
|
||||
if msg_tool_call_id and prev_tool_call_id and msg_tool_call_id == prev_tool_call_id:
|
||||
name = tool.get("function", {}).get("name", "")
|
||||
|
||||
if not name:
|
||||
raise Exception("Missing corresponding tool call for tool response message")
|
||||
|
||||
# We can't determine from openai message format whether it's a successful or
|
||||
# error call result so default to the successful result template
|
||||
|
||||
@@ -328,6 +328,8 @@ def _gemini_convert_messages_with_history(messages: list) -> List[ContentType]:
|
||||
user_message_types = {"user", "system"}
|
||||
contents: List[ContentType] = []
|
||||
|
||||
last_message_with_tool_calls = None
|
||||
|
||||
msg_i = 0
|
||||
try:
|
||||
while msg_i < len(messages):
|
||||
@@ -383,6 +385,7 @@ def _gemini_convert_messages_with_history(messages: list) -> List[ContentType]:
|
||||
messages[msg_i]["tool_calls"]
|
||||
)
|
||||
)
|
||||
last_message_with_tool_calls = messages[msg_i]
|
||||
else:
|
||||
assistant_text = (
|
||||
messages[msg_i].get("content") or ""
|
||||
@@ -397,7 +400,7 @@ def _gemini_convert_messages_with_history(messages: list) -> List[ContentType]:
|
||||
|
||||
## APPEND TOOL CALL MESSAGES ##
|
||||
if msg_i < len(messages) and messages[msg_i]["role"] == "tool":
|
||||
_part = convert_to_gemini_tool_call_result(messages[msg_i])
|
||||
_part = convert_to_gemini_tool_call_result(messages[msg_i], last_message_with_tool_calls)
|
||||
contents.append(ContentType(parts=[_part])) # type: ignore
|
||||
msg_i += 1
|
||||
if msg_i == init_msg_i: # prevent infinite loops
|
||||
|
||||
@@ -1159,8 +1159,7 @@ async def test_gemini_pro_function_calling(provider, sync_mode):
|
||||
# The result of the tool call is added to the history
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_123",
|
||||
"name": "get_weather",
|
||||
"tool_call_id": "call_123",
|
||||
"content": "27 degrees celsius and clear in San Francisco, CA",
|
||||
},
|
||||
# Now the assistant can reply with the result of the tool call.
|
||||
@@ -1382,6 +1381,52 @@ async def test_vertexai_aembedding():
|
||||
except Exception as e:
|
||||
pytest.fail(f"Error occurred: {e}")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
def test_tool_name_conversion():
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "Your name is Litellm Bot, you are a helpful assistant",
|
||||
},
|
||||
# User asks for their name and weather in San Francisco
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello, what is your name and can you tell me the weather?",
|
||||
},
|
||||
# Assistant replies with a tool call
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_123",
|
||||
"type": "function",
|
||||
"index": 0,
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"arguments": '{"location":"San Francisco, CA"}',
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
# The result of the tool call is added to the history
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_123",
|
||||
"content": "27 degrees celsius and clear in San Francisco, CA",
|
||||
},
|
||||
# Now the assistant can reply with the result of the tool call.
|
||||
]
|
||||
|
||||
translated_messages = _gemini_convert_messages_with_history(messages=messages)
|
||||
|
||||
print(f"\n\ntranslated_messages: {translated_messages}\ntranslated_messages")
|
||||
|
||||
# assert that the last tool response has the corresponding tool name
|
||||
assert (
|
||||
translated_messages[-1]["parts"][0]["function_response"]["name"] == "get_weather"
|
||||
)
|
||||
|
||||
|
||||
# Extra gemini Vision tests for completion + stream, async, async + stream
|
||||
# if we run into issues with gemini, we will also add these to our ci/cd pipeline
|
||||
@@ -1531,7 +1576,6 @@ def test_prompt_factory():
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_123",
|
||||
"name": "get_weather",
|
||||
"content": "27 degrees celsius and clear in San Francisco, CA",
|
||||
},
|
||||
# Now the assistant can reply with the result of the tool call.
|
||||
@@ -1541,7 +1585,6 @@ def test_prompt_factory():
|
||||
|
||||
print(f"\n\ntranslated_messages: {translated_messages}\ntranslated_messages")
|
||||
|
||||
|
||||
def test_prompt_factory_nested():
|
||||
messages = [
|
||||
{"role": "user", "content": [{"type": "text", "text": "hi"}]},
|
||||
@@ -1563,4 +1606,4 @@ def test_prompt_factory_nested():
|
||||
assert "text" in message["parts"][0], "Missing 'text' from 'parts'"
|
||||
assert isinstance(
|
||||
message["parts"][0]["text"], str
|
||||
), "'text' value not a string."
|
||||
), "'text' value not a string."
|
||||
Reference in New Issue
Block a user