mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix: use safe orjson imports and fix Black formatting for CI
Replace hard orjson removal with try/except safe imports that preserve perf when orjson is available. Revert transformation.py to response.json() matching main. Fix test assertions to use safe_dumps() for orjson-agnostic JSON comparison. Apply Black formatting to sidecar files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
288bd225c2
commit
748ea4ece9
7 changed files with 494 additions and 405 deletions
|
|
@ -68,7 +68,11 @@ class LiteLLMSidecarTransport(httpx.AsyncBaseTransport):
|
|||
provider_base = f"{parsed.scheme}://{parsed.host}"
|
||||
if parsed.port and parsed.port not in (80, 443):
|
||||
provider_base += f":{parsed.port}"
|
||||
path = parsed.raw_path.decode("ascii") if isinstance(parsed.raw_path, bytes) else str(parsed.raw_path)
|
||||
path = (
|
||||
parsed.raw_path.decode("ascii")
|
||||
if isinstance(parsed.raw_path, bytes)
|
||||
else str(parsed.raw_path)
|
||||
)
|
||||
|
||||
# Extract auth header if present
|
||||
api_key = ""
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
OpenAI-like chat completion transformation
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union
|
||||
|
||||
import httpx
|
||||
|
|
@ -95,7 +94,7 @@ class OpenAILikeChatConfig(OpenAIGPTConfig):
|
|||
custom_llm_provider: Optional[str],
|
||||
base_model: Optional[str],
|
||||
) -> ModelResponse:
|
||||
response_json = json.loads(response.content)
|
||||
response_json = response.json()
|
||||
logging_obj.post_call(
|
||||
input=messages,
|
||||
api_key="",
|
||||
|
|
|
|||
|
|
@ -946,16 +946,22 @@ async def proxy_startup_event(app: FastAPI): # noqa: PLR0915
|
|||
shared_aiohttp_session = await _initialize_shared_aiohttp_session()
|
||||
|
||||
## Initialize Rust sidecar client (optional, for high-perf forwarding)
|
||||
_use_sidecar = os.environ.get("USE_SIDECAR", "").lower() == "true" or general_settings.get("use_sidecar", False)
|
||||
_use_sidecar = os.environ.get(
|
||||
"USE_SIDECAR", ""
|
||||
).lower() == "true" or general_settings.get("use_sidecar", False)
|
||||
if _use_sidecar:
|
||||
# Ensure env var is set so AsyncHTTPHandler._should_use_sidecar_transport() picks it up
|
||||
os.environ["USE_SIDECAR"] = "true"
|
||||
|
||||
from litellm.proxy.sidecar_client import init_sidecar_client
|
||||
|
||||
_sidecar_port = int(os.environ.get("SIDECAR_PORT", general_settings.get("sidecar_port", 8787)))
|
||||
_sidecar_port = int(
|
||||
os.environ.get("SIDECAR_PORT", general_settings.get("sidecar_port", 8787))
|
||||
)
|
||||
os.environ.setdefault("SIDECAR_PORT", str(_sidecar_port))
|
||||
_sidecar_binary = os.environ.get("SIDECAR_BINARY", general_settings.get("sidecar_binary", ""))
|
||||
_sidecar_binary = os.environ.get(
|
||||
"SIDECAR_BINARY", general_settings.get("sidecar_binary", "")
|
||||
)
|
||||
await init_sidecar_client(
|
||||
port=_sidecar_port,
|
||||
binary=_sidecar_binary or None,
|
||||
|
|
|
|||
|
|
@ -58,9 +58,7 @@ class SidecarClient:
|
|||
|
||||
self._healthy = await self._check_health()
|
||||
if self._healthy:
|
||||
verbose_proxy_logger.info(
|
||||
f"Sidecar client connected to {self.sidecar_url}"
|
||||
)
|
||||
verbose_proxy_logger.info(f"Sidecar client connected to {self.sidecar_url}")
|
||||
else:
|
||||
verbose_proxy_logger.warning(
|
||||
f"Sidecar not available at {self.sidecar_url}, will use fallback"
|
||||
|
|
@ -144,9 +142,7 @@ class SidecarClient:
|
|||
self._process.terminate()
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.get_event_loop().run_in_executor(
|
||||
None, self._process.wait
|
||||
),
|
||||
asyncio.get_event_loop().run_in_executor(None, self._process.wait),
|
||||
timeout=5,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from litellm.integrations._types.open_inference import (
|
|||
)
|
||||
from litellm.integrations.arize.arize import ArizeLogger
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from litellm.types.utils import Choices, StandardCallbackDynamicParams
|
||||
|
||||
|
||||
|
|
@ -88,7 +89,7 @@ def test_arize_set_attributes():
|
|||
|
||||
# Metadata attached to the span
|
||||
span.set_attribute.assert_any_call(
|
||||
SpanAttributes.METADATA, json.dumps({"key_1": "value_1", "key_2": None})
|
||||
SpanAttributes.METADATA, safe_dumps({"key_1": "value_1", "key_2": None})
|
||||
)
|
||||
|
||||
# Basic LLM information
|
||||
|
|
@ -147,7 +148,7 @@ def test_arize_set_attributes():
|
|||
|
||||
# Invocation parameters
|
||||
span.set_attribute.assert_any_call(
|
||||
SpanAttributes.LLM_INVOCATION_PARAMETERS, '{"user": "test_user"}'
|
||||
SpanAttributes.LLM_INVOCATION_PARAMETERS, safe_dumps({"user": "test_user"})
|
||||
)
|
||||
|
||||
# User ID
|
||||
|
|
@ -178,10 +179,20 @@ def test_arize_set_attributes_responses_api():
|
|||
Verifies that multiple output types are correctly handled.
|
||||
"""
|
||||
from unittest.mock import MagicMock
|
||||
from litellm.types.llms.openai import ResponsesAPIResponse, ResponseAPIUsage, OutputTokensDetails
|
||||
from openai.types.responses import ResponseReasoningItem, ResponseOutputMessage, ResponseOutputText
|
||||
|
||||
from openai.types.responses import (
|
||||
ResponseOutputMessage,
|
||||
ResponseOutputText,
|
||||
ResponseReasoningItem,
|
||||
)
|
||||
from openai.types.responses.response_reasoning_item import Summary
|
||||
|
||||
from litellm.types.llms.openai import (
|
||||
OutputTokensDetails,
|
||||
ResponseAPIUsage,
|
||||
ResponsesAPIResponse,
|
||||
)
|
||||
|
||||
span = MagicMock() # Mocked tracing span to test attribute setting
|
||||
|
||||
# Construct kwargs to simulate a real LLM request scenario
|
||||
|
|
@ -212,11 +223,8 @@ def test_arize_set_attributes_responses_api():
|
|||
id="reasoning-001",
|
||||
type="reasoning",
|
||||
summary=[
|
||||
Summary(
|
||||
text="First, I need to analyze...",
|
||||
type="summary_text"
|
||||
)
|
||||
]
|
||||
Summary(text="First, I need to analyze...", type="summary_text")
|
||||
],
|
||||
),
|
||||
ResponseOutputMessage(
|
||||
id="msg-001",
|
||||
|
|
@ -229,17 +237,15 @@ def test_arize_set_attributes_responses_api():
|
|||
text="The answer is 42",
|
||||
type="output_text",
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
usage=ResponseAPIUsage(
|
||||
input_tokens=120,
|
||||
output_tokens=250,
|
||||
total_tokens=370,
|
||||
output_tokens_details=OutputTokensDetails(
|
||||
reasoning_tokens=180
|
||||
)
|
||||
)
|
||||
output_tokens_details=OutputTokensDetails(reasoning_tokens=180),
|
||||
),
|
||||
)
|
||||
|
||||
ArizeLogger.set_arize_attributes(span, kwargs, response_obj)
|
||||
|
|
@ -247,21 +253,18 @@ def test_arize_set_attributes_responses_api():
|
|||
# Verify reasoning summary was set (index 0)
|
||||
span.set_attribute.assert_any_call(
|
||||
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_REASONING_SUMMARY}",
|
||||
"First, I need to analyze..."
|
||||
"First, I need to analyze...",
|
||||
)
|
||||
|
||||
# Verify message content was set (index 1)
|
||||
span.set_attribute.assert_any_call(
|
||||
SpanAttributes.OUTPUT_VALUE,
|
||||
"The answer is 42"
|
||||
)
|
||||
span.set_attribute.assert_any_call(SpanAttributes.OUTPUT_VALUE, "The answer is 42")
|
||||
span.set_attribute.assert_any_call(
|
||||
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.1.{MessageAttributes.MESSAGE_CONTENT}",
|
||||
"The answer is 42"
|
||||
"The answer is 42",
|
||||
)
|
||||
span.set_attribute.assert_any_call(
|
||||
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.1.{MessageAttributes.MESSAGE_ROLE}",
|
||||
"assistant"
|
||||
"assistant",
|
||||
)
|
||||
|
||||
# Verify token counts including reasoning tokens
|
||||
|
|
@ -335,42 +338,34 @@ def test_construct_dynamic_arize_headers():
|
|||
|
||||
# Test with all parameters present
|
||||
dynamic_params_full = StandardCallbackDynamicParams(
|
||||
arize_api_key="test_api_key",
|
||||
arize_space_id="test_space_id"
|
||||
arize_api_key="test_api_key", arize_space_id="test_space_id"
|
||||
)
|
||||
arize_logger = ArizeLogger()
|
||||
|
||||
|
||||
headers = arize_logger.construct_dynamic_otel_headers(dynamic_params_full)
|
||||
expected_headers = {
|
||||
"api_key": "test_api_key",
|
||||
"arize-space-id": "test_space_id"
|
||||
}
|
||||
expected_headers = {"api_key": "test_api_key", "arize-space-id": "test_space_id"}
|
||||
assert headers == expected_headers
|
||||
|
||||
|
||||
# Test with only space_id
|
||||
dynamic_params_space_id_only = StandardCallbackDynamicParams(
|
||||
arize_space_id="test_space_id"
|
||||
)
|
||||
|
||||
|
||||
headers = arize_logger.construct_dynamic_otel_headers(dynamic_params_space_id_only)
|
||||
expected_headers = {
|
||||
"arize-space-id": "test_space_id"
|
||||
}
|
||||
expected_headers = {"arize-space-id": "test_space_id"}
|
||||
assert headers == expected_headers
|
||||
|
||||
|
||||
# Test with empty parameters dict
|
||||
dynamic_params_empty = StandardCallbackDynamicParams()
|
||||
|
||||
|
||||
headers = arize_logger.construct_dynamic_otel_headers(dynamic_params_empty)
|
||||
assert headers == {}
|
||||
|
||||
# test with space key and api key
|
||||
dynamic_params_space_key_and_api_key = StandardCallbackDynamicParams(
|
||||
arize_space_key="test_space_key",
|
||||
arize_api_key="test_api_key"
|
||||
arize_space_key="test_space_key", arize_api_key="test_api_key"
|
||||
)
|
||||
headers = arize_logger.construct_dynamic_otel_headers(dynamic_params_space_key_and_api_key)
|
||||
expected_headers = {
|
||||
"arize-space-id": "test_space_key",
|
||||
"api_key": "test_api_key"
|
||||
}
|
||||
headers = arize_logger.construct_dynamic_otel_headers(
|
||||
dynamic_params_space_key_and_api_key
|
||||
)
|
||||
expected_headers = {"arize-space-id": "test_space_key", "api_key": "test_api_key"}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from unittest.mock import AsyncMock, MagicMock
|
|||
|
||||
import pytest
|
||||
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from litellm.proxy.agent_endpoints.agent_registry import AgentRegistry
|
||||
|
||||
|
||||
|
|
@ -110,5 +111,5 @@ async def test_update_agent_in_db_preserves_explicit_static_headers_and_extra_he
|
|||
call_kwargs = mock_update.call_args.kwargs
|
||||
update_data = call_kwargs["data"]
|
||||
|
||||
assert update_data["static_headers"] == '{"Authorization": "Bearer xyz"}'
|
||||
assert update_data["static_headers"] == safe_dumps({"Authorization": "Bearer xyz"})
|
||||
assert update_data["extra_headers"] == ["X-Custom-Header"]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue