mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-25 05:54:53 +00:00
Merge ea0898fae2 into 970a9c0868
This commit is contained in:
commit
a5ed09609e
9 changed files with 89 additions and 46 deletions
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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()]}),
|
||||
(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue