diff --git a/litellm/llms/sap/chat/models.py b/litellm/llms/sap/chat/models.py index f2b31d4f9b0..ddfa26b82f4 100644 --- a/litellm/llms/sap/chat/models.py +++ b/litellm/llms/sap/chat/models.py @@ -76,6 +76,14 @@ class FunctionObj(LiteLLMBaseModel): name: str arguments: str + @model_validator(mode="before") + @classmethod + def default_arguments(cls, value: object) -> object: + fields: Final = as_str_mapping(value) + if fields is None or "arguments" in fields: + return value + return {**fields, "arguments": ""} + class FunctionTool(LiteLLMBaseModel): description: str = "" diff --git a/tests/unit/llms/sap/chat/test_sap_transformation.py b/tests/unit/llms/sap/chat/test_sap_transformation.py index 41e75b7bfc7..0e430299b7e 100644 --- a/tests/unit/llms/sap/chat/test_sap_transformation.py +++ b/tests/unit/llms/sap/chat/test_sap_transformation.py @@ -1,7 +1,61 @@ import warnings +from copy import deepcopy +from typing import Final + import pytest from pydantic import ValidationError +from litellm.llms.sap.chat.transformation import GenAIHubOrchestrationConfig +from litellm.types.llms.openai import AllMessageValues, ChatCompletionToolCallFunctionChunk + + +@pytest.mark.parametrize("stream", [False, True]) +@pytest.mark.parametrize("arguments", [None, "", '{"city":"Paris"}']) +def test_replayed_tool_call_arguments_survive_request_serialization( + arguments: str | None, stream: bool +) -> None: + function: Final[ChatCompletionToolCallFunctionChunk] = ( + {"name": "get_weather"} + if arguments is None + else {"name": "get_weather", "arguments": arguments} + ) + messages: Final[list[AllMessageValues]] = [ + {"role": "user", "content": "Check the weather"}, + { + "role": "assistant", + "tool_calls": [ + {"id": "call_weather", "type": "function", "function": function} + ], + }, + {"role": "tool", "tool_call_id": "call_weather", "content": "Sunny"}, + ] + original: Final = deepcopy(messages) + body: Final = GenAIHubOrchestrationConfig().transform_request( + model="anthropic--claude-4.6-sonnet", + messages=messages, + optional_params={ + "stream": stream, + "fallback_sap_modules": [ + {"model": "sap/anthropic--claude-4.6-sonnet", "messages": messages} + ], + }, + litellm_params={}, + headers={}, + ) + expected: Final = { + **original[1], + "tool_calls": [{ + "id": "call_weather", + "type": "function", + "function": {"name": "get_weather", "arguments": arguments or ""}, + }], + } + assert messages == original + assert [ + module["prompt_templating"]["prompt"]["template"] + for module in body["config"]["modules"] + ] == [[original[0], expected, original[2]]] * 2 + class TestSAPTransformationIntegration: """Integration tests for SAP transformation."""