This commit is contained in:
Jaeyeon Kim(김재연) 2026-11-07 05:54:58 +00:00 • committed by GitHub
commit 1538043d29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 45 additions and 1 deletions

View file

@ -2,6 +2,7 @@ import asyncio
import concurrent.futures
import inspect
import os
import re
import socket
import ssl
import sys
@ -272,6 +273,18 @@ _STREAMING_ERROR_BODY_READ_EXECUTOR: Final = concurrent.futures.ThreadPoolExecut
)
_SURROGATE_RE: Final = re.compile(r"[\ud800-\udfff]")
def _repair_surrogates(value: str) -> str:
"""Repair malformed UTF-16 text without copying ordinary Unicode strings."""
if not _SURROGATE_RE.search(value):
return value
# Preserve valid surrogate pairs as their scalar value; replace only lone
# surrogates. Byte payloads and caller-owned mappings are never rewritten.
return value.encode("utf-16-le", errors="surrogatepass").decode("utf-16-le", errors="replace")
def _prepare_request_data_and_content(
data: dict | str | bytes | None = None,
content: _RequestContent | None = None,
@ -309,7 +322,7 @@ def _prepare_request_data_and_content(
# dict/Mapping stays in data= parameter
request_data = data
return request_data, request_content
return request_data, _repair_surrogates(request_content) if isinstance(request_content, str) else request_content
# Cache for SSL contexts to avoid creating duplicate contexts with the same configuration

View file

@ -0,0 +1,31 @@
import httpx
import pytest
from litellm.llms.custom_httpx.http_handler import _prepare_request_data_and_content
@pytest.mark.parametrize("argument", ["data", "content"])
@pytest.mark.parametrize(
("raw", "expected"),
[("hi\ud800!", "hi\ufffd!"), ("\udc00", "\ufffd"), ("\ud83d\ude00", "😀"), ("漢字😀", "漢字😀")],
)
def test_raw_text_can_be_encoded_as_utf8(argument, raw, expected):
data, content = _prepare_request_data_and_content(**{argument: raw})
request = httpx.Request("POST", "http://localhost/", data=data, content=content)
assert request.content == expected.encode()
def test_valid_text_and_bytes_keep_identity():
text = "valid 😀"
payload = b"\xed\xa0\x80"
assert _prepare_request_data_and_content(data=text) == (None, text)
assert _prepare_request_data_and_content(content=text)[1] is text
assert _prepare_request_data_and_content(data=payload)[1] is payload
def test_explicit_content_wins_without_mutating_form_fields():
fields = {"field": "value"}
data, content = _prepare_request_data_and_content(data=fields, content="\ud800")
assert data is fields
assert content == "\ufffd"
assert _prepare_request_data_and_content(data="ignored\ud800", content=b"chosen") == (None, b"chosen")