mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
Merge 8c59d24d78 into 72228db5c6
This commit is contained in:
commit
1538043d29
2 changed files with 45 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
31
tests/unit/llms/custom_httpx/test_raw_body_surrogates.py
Normal file
31
tests/unit/llms/custom_httpx/test_raw_body_surrogates.py
Normal 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")
|
||||
Loading…
Add table
Reference in a new issue