diff --git a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py index ff624a3de8b..9ea730a873f 100644 --- a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py +++ b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py @@ -406,11 +406,7 @@ def _handle_invalid_parallel_tool_calls( shift = 0 for i, replacement in replacements.items(): tool_calls[:] = tool_calls[: i + shift] + replacement + tool_calls[i + shift + 1 :] - # One entry is replaced by ``len(replacement)`` entries, so the - # offsets of everything after it move by the difference - not by - # the full length, which would skip one entry per expansion and - # leave the next ``multi_tool_use.parallel`` call in place. - shift += len(replacement) - 1 + shift += len(replacement) return tool_calls except json.JSONDecodeError: diff --git a/tests/test_litellm/litellm_core_utils/llm_response_utils/test_parallel_tool_call_shift.py b/tests/test_litellm/litellm_core_utils/llm_response_utils/test_parallel_tool_call_shift.py deleted file mode 100644 index 52a6ae2cc07..00000000000 --- a/tests/test_litellm/litellm_core_utils/llm_response_utils/test_parallel_tool_call_shift.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Every hallucinated multi_tool_use.parallel call in one message must expand. - -Replacing one entry with len(expansions) entries moves everything after it by -len(expansions) - 1; advancing by the full length skipped one entry per -expansion, so with two such calls the second stayed in place (and the call -after it was overwritten). -""" - -import json - -from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import ( - convert_to_model_response_object, -) -from litellm.types.utils import ModelResponse - - -def _parallel(call_id: str, *calls): - return { - "id": call_id, - "type": "function", - "function": { - "name": "multi_tool_use.parallel", - "arguments": json.dumps( - { - "tool_uses": [ - {"recipient_name": name, "parameters": params} for name, params in calls - ] - } - ), - }, - } - - -def _plain(call_id: str, name: str): - return { - "id": call_id, - "type": "function", - "function": {"name": name, "arguments": "{}"}, - } - - -def test_every_parallel_tool_call_expands(): - response_object = { - "id": "chatcmpl-parallel", - "object": "chat.completion", - "created": 1728933352, - "model": "gpt-4o-2024-08-06", - "choices": [ - { - "index": 0, - "finish_reason": "tool_calls", - "message": { - "role": "assistant", - "content": None, - "tool_calls": [ - _plain("0", "get_weather"), - # Two expansions here and one below, so the first splice - # changes the list length and the second has to land at - # the shifted offset. - _parallel( - "m1", - ("functions.get_time", {"tz": "UTC"}), - ("functions.get_date", {"tz": "UTC"}), - ), - _plain("2", "get_news"), - _parallel("m2", ("functions.get_quote", {"sym": "AAPL"})), - _plain("4", "get_forecast"), - ], - }, - } - ], - } - - result = convert_to_model_response_object( - response_object=response_object, - model_response_object=ModelResponse(), - response_type="completion", - ) - - names = [tc.function.name for tc in result.choices[0].message.tool_calls] - assert names == [ - "get_weather", - "get_time", - "get_date", - "get_news", - "get_quote", - "get_forecast", - ]