Repository: BerriAI/litellm

Issue:
[Bug]: Gemini function call arguments escape non-ASCII characters as Unicode sequences
### What happened?


Function call arguments should preserve non-ASCII characters in their original form, matching OpenAI's behavior:

```
{
  "function": {
    "name": "welcome_message",
    "arguments": "{\"action\":\"iPhoneやiPadなど、Apple製品についてどのようなことにお困りですか？\",\"nudge\":\"Apple製品について、テクニカルサポートに今すぐ相談可能です。\"}"
  }
}
```

## Actual Behavior

Non-ASCII characters are escaped as Unicode sequences:
```
{
  "function": {
    "name": "welcome_message",
    "arguments": "{\"action\": \"iPhone\\u3084iPad\\u306a\\u3069\\u3001Apple\\u88fd\\u54c1\\u306b\\u3064\\u3044\\u3066\\u3069\\u306e\\u3088\\u3046\\u306a\\u3053\\u3068\\u3067\\u304a\\u56f0\\u308a\\u3067\\u3059\\u304b\\uff1f\", \"nudge\": \"Apple\\u88fd\\u54c1\\u306b\\u3064\\u3044\\u3066\\u3001\\u30c6\\u30af\\u30cb\\u30ab\\u30eb\\u30b5\\u30dd\\u30fc\\u30c8\\u306b\\u4eca\\u3059\\u3050\\u76f8\\u8ac7\\u53ef\\u80fd\\u3067\\u3059\\u3002\"}"
  }
}
```

## Steps to Reproduce

1. Make a function calling request to Gemini with non-ASCII characters in the function arguments:

```
import litellm

response = litellm.completion(
    model="google/gemini-2.5-flash",
    messages=[
        {
            "role": "system",
            "content": "You are a helpful assistant that creates Japanese messages."
        },
        {
            "role": "user",
            "content": "apple technology"
        }
    ],
    tools=[{
        "type": "function",
        "function": {
            "name": "welcome_message",
            "description": "Creates copy for Japanese site",
            "parameters": {
                "type": "object",
                "properties": {
                    "action": {
                        "type": "string",
                        "description": "Action text in Japanese"
                    },
                    "nudge": {
                        "type": "string",
                        "description": "Nudge text in Japanese"
                    }
                },
                "required": ["action", "nudge"]
            }
        }
    }],
    tool_choice={
        "type": "function",
        "function": {"name": "welcome_message"}
    }
)
```

### Check the function arguments
```
function_call = response.choices[0].message.tool_calls[0].function
print(function_call.arguments)  # Will contain \u3084 instead of や2. Observe that the `arguments` field contains escaped Unicode sequences instead of the original Japanese characters.
```


### Relevant log output

```shell

```

### Are you a ML Ops Team?

No

### What LiteLLM version are you on ?

v1.77.7

### Twitter / LinkedIn details

_No response_

Discussion:

Fix (patch):
diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py
index b8370d5fef25..67b7fa749a70 100644
--- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py
+++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py
@@ -1022,7 +1022,7 @@ def _transform_parts(
             if "functionCall" in part:
                 _function_chunk = ChatCompletionToolCallFunctionChunk(
                     name=part["functionCall"]["name"],
-                    arguments=json.dumps(part["functionCall"]["args"]),
+                    arguments=json.dumps(part["functionCall"]["args"], ensure_ascii=False),
                 )
                 if is_function_call is True:
                     function = _function_chunk


Test changes:
diff --git a/tests/llm_translation/test_gemini.py b/tests/llm_translation/test_gemini.py
index f4100d9e8ac1..ab9aab892700 100644
--- a/tests/llm_translation/test_gemini.py
+++ b/tests/llm_translation/test_gemini.py
@@ -1129,3 +1129,72 @@ def test_gemini_embedding():
     )
     print("response: ", response)
     assert response is not None
+
+
+def test_gemini_function_args_preserve_unicode():
+    """
+    Test for Issue #16533: Gemini function call arguments should preserve non-ASCII characters
+    https://github.com/BerriAI/litellm/issues/16533
+
+    Before fix: "や" becomes "\u3084"
+    After fix: "や" stays as "や"
+    """
+    from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import VertexGeminiConfig
+
+    # Test Japanese characters
+    parts = [
+        {
+            "functionCall": {
+                "name": "send_message",
+                "args": {
+                    "message": "やあ",  # Japanese "hello"
+                    "recipient": "たけし"  # Japanese name
+                }
+            }
+        }
+    ]
+
+    function, tools, _ = VertexGeminiConfig._transform_parts(
+        parts=parts,
+        cumulative_tool_call_idx=0,
+        is_function_call=False
+    )
+
+    arguments_str = tools[0]['function']['arguments']
+    parsed_args = json.loads(arguments_str)
+
+    # Verify characters are preserved
+    assert parsed_args["message"] == "やあ", "Japanese characters should be preserved"
+    assert parsed_args["recipient"] == "たけし", "Japanese characters should be preserved"
+
+    # Verify no Unicode escape sequences in raw string
+    assert "\\u" not in arguments_str, "Should not contain Unicode escape sequences"
+    assert "やあ" in arguments_str, "Original Japanese characters should be in the string"
+    assert "たけし" in arguments_str, "Original Japanese characters should be in the string"
+
+    # Test Spanish characters
+    parts_spanish = [
+        {
+            "functionCall": {
+                "name": "send_message",
+                "args": {
+                    "message": "¡Hola! ¿Cómo estás?",
+                    "recipient": "José"
+                }
+            }
+        }
+    ]
+
+    function, tools, _ = VertexGeminiConfig._transform_parts(
+        parts=parts_spanish,
+        cumulative_tool_call_idx=0,
+        is_function_call=False
+    )
+
+    arguments_str = tools[0]['function']['arguments']
+    parsed_args = json.loads(arguments_str)
+
+    assert parsed_args["message"] == "¡Hola! ¿Cómo estás?"
+    assert parsed_args["recipient"] == "José"
+    assert "\\u" not in arguments_str
+    assert "José" in arguments_str