This commit is contained in:
yujonglee 2026-10-19 03:15:17 +00:00 • committed by GitHub
commit a5ed09609e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 89 additions and 46 deletions

View file

@ -65,7 +65,6 @@ def _public_request(
_FEATURES: Final[detectors.Detectors] = MappingProxyType(
{
**detectors.COMMON,
Feature.DROP_PARAMS: detectors.drop_params,
Feature.MODIFY_PARAMS: detectors.modify_params,
Feature.STREAMING: detectors.streaming,
Feature.PROMPT_MANAGEMENT: detectors.prompt_management,
@ -81,7 +80,6 @@ _FEATURES: Final[detectors.Detectors] = MappingProxyType(
Feature.CACHE_CONTROL_MARKS: detectors.cache_control_marks,
Feature.ANTHROPIC_TOOL_SEARCH: detectors.anthropic_tool_search,
Feature.TOOL_SCHEMA_REPAIR: detectors.tool_schema_repair,
Feature.MODEL_PARAM_GATING: detectors.model_param_gating,
Feature.WORKLOAD_IDENTITY: detectors.openai_workload_identity,
Feature.CLIENT_HEADERS: detectors.client_headers,
}

View file

@ -1623,12 +1623,14 @@ def responses(
# Get optional parameters for the responses API
request_drop_params: Final = kwargs.get("drop_params")
responses_api_request_params: Final[dict] = ResponsesAPIRequestUtils.get_optional_params_responses_api(
model=model,
responses_api_provider_config=responses_api_provider_config,
response_api_optional_params=response_api_optional_params,
allowed_openai_params=allowed_openai_params,
drop_params=normalize_drop_params(request_drop_params),
responses_api_request_params: Final[Mapping[str, object]] = (
ResponsesAPIRequestUtils.get_optional_params_responses_api(
model=model,
responses_api_provider_config=responses_api_provider_config,
response_api_optional_params=response_api_optional_params,
allowed_openai_params=allowed_openai_params,
drop_params=normalize_drop_params(request_drop_params),
)
)
litellm_logging_obj.update_from_kwargs(
@ -1670,7 +1672,7 @@ def responses(
model=model,
input=input,
responses_api_provider_config=responses_api_provider_config,
response_api_optional_request_params=responses_api_request_params,
response_api_optional_request_params=dict(responses_api_request_params),
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=litellm_logging_obj,
@ -2473,12 +2475,14 @@ def compact_responses(
# Get optional parameters for the responses API
request_drop_params: Final = kwargs.get("drop_params")
responses_api_request_params: Final[dict] = ResponsesAPIRequestUtils.get_optional_params_responses_api(
model=model,
responses_api_provider_config=responses_api_provider_config,
response_api_optional_params=response_api_optional_params,
allowed_openai_params=None,
drop_params=normalize_drop_params(request_drop_params),
responses_api_request_params: Final[Mapping[str, object]] = (
ResponsesAPIRequestUtils.get_optional_params_responses_api(
model=model,
responses_api_provider_config=responses_api_provider_config,
response_api_optional_params=response_api_optional_params,
allowed_openai_params=None,
drop_params=normalize_drop_params(request_drop_params),
)
)
# Pre Call logging
@ -2503,7 +2507,7 @@ def compact_responses(
model=model,
input=input,
responses_api_provider_config=responses_api_provider_config,
response_api_optional_request_params=responses_api_request_params,
response_api_optional_request_params=dict(responses_api_request_params),
litellm_params=litellm_params,
logging_obj=litellm_logging_obj,
custom_llm_provider=custom_llm_provider,

View file

@ -202,7 +202,7 @@ class ResponsesAPIRequestUtils:
response_api_optional_params: ResponsesAPIOptionalRequestParams,
allowed_openai_params: list[str] | None = None,
drop_params: bool | None = None,
) -> dict:
) -> Mapping[str, object]:
"""
Get optional parameters for the responses API.

View file

@ -178,7 +178,6 @@ MESSAGES_PYTHON_FEATURES: Final = COMMON_PYTHON_FEATURES | {
}
RESPONSES_PYTHON_FEATURES: Final = COMMON_PYTHON_FEATURES | {
Feature.DROP_PARAMS,
Feature.MODIFY_PARAMS,
Feature.STREAMING,
Feature.PROMPT_MANAGEMENT,
@ -194,7 +193,6 @@ RESPONSES_PYTHON_FEATURES: Final = COMMON_PYTHON_FEATURES | {
Feature.CACHE_CONTROL_MARKS,
Feature.ANTHROPIC_TOOL_SEARCH,
Feature.TOOL_SCHEMA_REPAIR,
Feature.MODEL_PARAM_GATING,
Feature.WORKLOAD_IDENTITY,
Feature.CLIENT_HEADERS,
Feature.DEPLOYMENT_HOOK,

View file

@ -19,7 +19,7 @@ if TYPE_CHECKING:
from openai.types.responses import ResponseInputParam
from litellm.responses.mcp.litellm_proxy_mcp_handler import ToolParam
from litellm.types.llms.openai import ALL_RESPONSES_API_TOOL_PARAMS, ResponsesAPIOptionalRequestParams
from litellm.types.llms.openai import ALL_RESPONSES_API_TOOL_PARAMS
Detector: TypeAlias = Callable[[NativeCall], bool]
Detectors: TypeAlias = Mapping[Feature, Detector]
@ -240,21 +240,6 @@ def tool_schema_repair(request: NativeCall) -> bool:
_PARAMS: Final = TypeAdapter(dict[str, object])
def model_param_gating(request: NativeCall) -> bool:
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
from litellm.rust_bridge.host.routes.responses import PARAMETERS
params: Final = {name: request.resolved[name] for name in PARAMETERS if request.resolved.get(name) is not None}
mapped: Final = _PARAMS.validate_python(
OpenAIResponsesAPIConfig().map_openai_params( # pyright: ignore[reportUnknownMemberType] # returns a bare dict
cast("ResponsesAPIOptionalRequestParams", params), # cast-ok: the handler passes these kwargs
_openai_model(request),
drop_params=True,
)
)
return mapped != params
_CHAT_UNGATED: Final = frozenset(
{
"model",

View file

@ -1,18 +1,50 @@
from __future__ import annotations
from collections.abc import Mapping
from collections.abc import Mapping, Sequence
from typing import Final
import litellm
from litellm import get_llm_provider
from litellm.completion_extras.litellm_responses_transformation.transformation import (
LiteLLMResponsesTransformationHandler,
)
from litellm.litellm_core_utils.core_helpers import normalize_drop_params
from litellm.responses.utils import ResponsesAPIRequestUtils
from litellm.rust_bridge import failures
from litellm.rust_bridge.public_call import known_params, optional_str
from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams, ResponsesAPIResponse
PARAMETERS: Final = tuple(ResponsesAPIOptionalRequestParams.__annotations__)
from litellm.rust_bridge.public_call import optional_str
from litellm.types.llms.openai import ResponsesAPIResponse
from litellm.utils import ProviderConfigManager
def body_params(request: Mapping[str, object]) -> Mapping[str, object]:
return known_params(PARAMETERS, request)
model, provider, _, _ = get_llm_provider(
model=str(request["model"]), custom_llm_provider=optional_str(request.get("custom_llm_provider"))
)
reasoning: Final = request.get("reasoning")
reasoning_effort: Final = request.get("reasoning_effort")
requested: Final = ResponsesAPIRequestUtils.get_requested_response_api_optional_param(
{
**{name: value for name, value in request.items() if name != "reasoning_effort"},
"custom_llm_provider": provider,
"reasoning": (
LiteLLMResponsesTransformationHandler().map_reasoning_effort(reasoning_effort)
if reasoning is None and reasoning_effort is not None
else reasoning
),
}
)
config: Final = ProviderConfigManager.get_provider_responses_api_config(provider=provider, model=model)
if config is None:
return dict(requested)
allowed: Final = request.get("allowed_openai_params")
allowed_values: Final[Sequence[object] | None] = allowed if isinstance(allowed, list) else None
return ResponsesAPIRequestUtils.get_optional_params_responses_api(
model=model,
responses_api_provider_config=config,
response_api_optional_params=requested,
allowed_openai_params=[str(name) for name in allowed_values] if allowed_values is not None else None,
drop_params=normalize_drop_params(request.get("drop_params")),
)
def connection_defaults(provider: str) -> tuple[str | None, str | None]:

View file

@ -276,7 +276,6 @@ ENCODED_ID: Final = ResponsesAPIRequestUtils._build_encrypted_item_id("deploymen
(Feature.TIMEOUT_VARIANTS, {"request_timeout": 5}, {}),
(Feature.AGENTIC_LOOP_HOOK, {}, {"callbacks": [_Agentic()]}),
(Feature.TLS_POLICY, {}, {"ssl_security_level": "DEFAULT@SECLEVEL=1"}),
(Feature.DROP_PARAMS, {}, {"drop_params": True}),
(Feature.STREAMING, {"stream": True}, {}),
(Feature.PROMPT_MANAGEMENT, {"prompt_id": "welcome"}, {}),
(Feature.TEXT_FORMAT, {"text_format": dict}, {}),
@ -302,7 +301,6 @@ ENCODED_ID: Final = ResponsesAPIRequestUtils._build_encrypted_item_id("deploymen
{},
),
(Feature.TOOL_SCHEMA_REPAIR, {"tools": [{"type": "function", "name": "f", "parameters": "{}"}]}, {}),
(Feature.MODEL_PARAM_GATING, {"max_output_tokens": 1}, {}),
(Feature.CLIENT_HEADERS, {"headers": {"x-tenant": "a"}}, {}),
(Feature.DEPLOYMENT_HOOK, {}, {"callbacks": [_Deployment()]}),
(

View file

@ -5,6 +5,7 @@ import pytest
from pydantic import TypeAdapter
import litellm
from litellm.llms.openai.responses.transformation import OPENAI_RESPONSES_API_MIN_MAX_OUTPUT_TOKENS
from litellm.responses.utils import ResponsesAPIRequestUtils
from litellm.types.llms.openai import ResponsesAPIResponse
from tests.test_litellm_rust.responses.data import RESPONSES_MODEL, RESPONSES_TEXT, call
@ -52,3 +53,32 @@ async def test_continuation_ids_are_decoded_before_they_reach_the_wire(
await call(server, asynchronous=asynchronous, previous_response_id=previous)
assert _OBJECT.validate_python(server.requests[0].body)["previous_response_id"] == original
@pytest.mark.asyncio
async def test_additional_drop_params_are_dropped_natively(server: RecordingServer) -> None:
await call(server, user="end-user", max_output_tokens=16, additional_drop_params=["user"])
assert_served_natively(server)
body: Final = _OBJECT.validate_python(server.requests[0].body)
assert "user" not in body
assert body["max_output_tokens"] == 16
@pytest.mark.asyncio
async def test_reasoning_effort_reaches_the_wire_as_reasoning(server: RecordingServer) -> None:
await call(server, reasoning_effort="low")
assert_served_natively(server)
body: Final = _OBJECT.validate_python(server.requests[0].body)
assert body["reasoning"] == {"effort": "low"}
assert "reasoning_effort" not in body
@pytest.mark.asyncio
async def test_sub_minimum_max_output_tokens_is_raised_like_python(server: RecordingServer) -> None:
await call(server, max_output_tokens=OPENAI_RESPONSES_API_MIN_MAX_OUTPUT_TOKENS - 1)
assert_served_natively(server)
body: Final = _OBJECT.validate_python(server.requests[0].body)
assert body["max_output_tokens"] == OPENAI_RESPONSES_API_MIN_MAX_OUTPUT_TOKENS

View file

@ -122,8 +122,6 @@ def _wrap_nested_level(value: object, depth: int) -> object:
(detectors.tool_schema_repair, {"tools": [function_tool({"type": "string", "pattern": r"\p{L}"})]}, True),
(detectors.tool_schema_repair, {"tools": [function_tool({"type": "object"})]}, False),
(detectors.tool_schema_repair, {}, False),
(detectors.model_param_gating, {"max_output_tokens": 1}, True),
(detectors.model_param_gating, {"max_output_tokens": 1024}, False),
(detectors.cache_control_injection, {"enable_prompt_caching": True}, True),
(detectors.cache_control_injection, {"cache_control_injection_points": [{"location": "message"}]}, True),
(detectors.cache_control_injection, {"cache_control_injection_points": []}, False),
@ -212,8 +210,8 @@ def test_input_rewrite_preserves_caller_input(input_value: object, expected: boo
@pytest.mark.parametrize(
("detector", "setting", "value", "kwargs"),
(
(detectors.drop_params, "drop_params", True, {}),
(detectors.modify_params, "modify_params", True, {}),
(detectors.drop_params, "drop_params", True, {}),
(detectors.retries_fallbacks, "num_retries", 3, {}),
(detectors.cache_control_injection, "enable_anthropic_prompt_caching", True, {}),
(detectors.stream_cost_injection, "include_cost_in_streaming_usage", True, {"stream": True}),