Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions litellm/responses/main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import asyncio
import contextvars
from collections.abc import Coroutine, Generator, Iterable, Mapping
from collections.abc import Coroutine, Generator, Iterable, Mapping, Sequence
from contextlib import contextmanager
from dataclasses import dataclass
from functools import partial
from types import MappingProxyType
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
from typing import TYPE_CHECKING, Any, Final, Literal, NoReturn, Optional, TypeAlias, cast

import httpx
Expand All @@ -15,7 +16,7 @@
from litellm.completion_extras.litellm_responses_transformation.transformation import (
LiteLLMResponsesTransformationHandler,
)
from litellm.constants import request_timeout
from litellm.constants import DEFAULT_CHAT_COMPLETION_PARAM_VALUES, request_timeout
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
from litellm.integrations.anthropic_cache_control_hook import CARRY_UNMATCHED_MESSAGE_POINTS
from litellm.litellm_core_utils.asyncify import run_async_function
from litellm.litellm_core_utils.core_helpers import normalize_drop_params
Expand Down Expand Up @@ -52,6 +53,7 @@
from litellm.secret_managers.main import get_secret_str
from litellm.types.responses.main import *
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import all_litellm_params
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
from litellm.utils import (
ProviderConfigManager,
client,
Expand Down Expand Up @@ -408,6 +410,25 @@ def _bridges_to_chat_completions(
return responses_api_provider_config is None or use_chat_completions_api is True


def _bridge_kwargs(
kwargs: Mapping[str, object],
responses_api_provider_config: BaseResponsesAPIConfig | None,
allowed_openai_params: Sequence[str] | None,
) -> Mapping[str, object]:
if responses_api_provider_config is None:
return kwargs
forwarded_keys: Final = frozenset(
(
*litellm.OPENAI_CHAT_COMPLETION_PARAMS,
*DEFAULT_CHAT_COMPLETION_PARAM_VALUES,
*all_litellm_params,
*GenericLiteLLMParams.model_fields,
*(allowed_openai_params or ()),
)
)
return MappingProxyType({key: value for key, value in kwargs.items() if key in forwarded_keys})
Comment thread
greptile-apps[bot] marked this conversation as resolved.


_ResponsesCompatibilityFailure: TypeAlias = Literal["encrypted_task_unsupported"]


Expand Down Expand Up @@ -1281,6 +1302,7 @@ def responses(
return _file_search_dispatch

if _bridges_to_chat_completions(responses_api_provider_config, use_chat_completions_api):
bridge_kwargs: Final = _bridge_kwargs(kwargs, responses_api_provider_config, allowed_openai_params)
return litellm_completion_transformation_handler.response_api_handler(
model=model,
input=input,
Expand All @@ -1292,7 +1314,7 @@ def responses(
extra_body=extra_body,
timeout=timeout if timeout is not None else request_timeout,
allowed_openai_params=allowed_openai_params,
**kwargs,
**bridge_kwargs,
)

# Get optional parameters for the responses API
Expand Down
109 changes: 109 additions & 0 deletions tests/test_litellm/responses/test_responses_api_bridge_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
calls so routed requests do not hit a custom api_base /v1/responses endpoint.
"""

import json
from importlib import import_module
from typing import Final
from unittest.mock import MagicMock, patch

import httpx
import pytest
import respx

import litellm
from litellm.llms.custom_httpx.http_handler import HTTPHandler
Expand Down Expand Up @@ -189,6 +191,113 @@ async def test_allowed_openai_params_forwarded_through_bridge(
"reasoning_effort"
]

@pytest.mark.parametrize(
("model", "upstream_url", "use_chat_completions_api", "allowed_openai_params", "expected_chat_template_kwargs"),
[
pytest.param(
"openai/my-custom-model",
"https://api.openai.com/v1/chat/completions",
True,
None,
None,
id="native-config-drops-unknown-param",
),
pytest.param(
"openai/my-custom-model",
"https://api.openai.com/v1/chat/completions",
True,
["chat_template_kwargs"],
{"thinking": True},
id="native-config-keeps-allowed-param",
),
pytest.param(
"together_ai/my-custom-model",
"https://api.together.ai/v1/chat/completions",
False,
None,
{"thinking": True},
id="no-native-config-keeps-passthrough",
),
],
)
def test_bridge_forwards_same_params_as_native_dispatch(
self,
model: str,
upstream_url: str,
use_chat_completions_api: bool,
allowed_openai_params: list[str] | None,
expected_chat_template_kwargs: dict[str, bool] | None,
respx_mock: respx.MockRouter,
):
upstream: Final = respx_mock.post(upstream_url).mock(
return_value=httpx.Response(
status_code=200,
json={
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"model": "my-custom-model",
"choices": [
{"index": 0, "message": {"role": "assistant", "content": "Answer"}, "finish_reason": "stop"}
],
"usage": {"prompt_tokens": 9, "completion_tokens": 1, "total_tokens": 10},
},
)
)

response: Final = litellm.responses(
model=model,
input="Hello",
use_chat_completions_api=use_chat_completions_api,
allowed_openai_params=allowed_openai_params,
chat_template_kwargs={"thinking": True},
drop_params=True,
api_key="fake-provider-api-key",
num_retries=0,
)

assert upstream.call_count == 1
request_body: Final = json.loads(upstream.calls[0].request.read())
assert request_body.get("chat_template_kwargs") == expected_chat_template_kwargs
assert request_body["messages"] == [{"role": "user", "content": "Hello"}]
assert response.output[0].content[0].text == "Answer"

def test_bridge_keeps_deployment_credentials_while_dropping_unknown_params(self, respx_mock: respx.MockRouter):
upstream: Final = respx_mock.post(
"https://example-resource.openai.azure.com/openai/deployments/my-deployment/chat/completions",
params={"api-version": "2024-10-21"},
).mock(
return_value=httpx.Response(
status_code=200,
json={
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"model": "my-deployment",
"choices": [
{"index": 0, "message": {"role": "assistant", "content": "Answer"}, "finish_reason": "stop"}
],
"usage": {"prompt_tokens": 9, "completion_tokens": 1, "total_tokens": 10},
},
)
)

litellm.responses(
model="azure/my-deployment",
input="Hello",
use_chat_completions_api=True,
api_base="https://example-resource.openai.azure.com",
api_version="2024-10-21",
azure_ad_token="fake-azure-ad-token",
chat_template_kwargs={"thinking": True},
num_retries=0,
)

assert upstream.call_count == 1
request: Final = upstream.calls[0].request
assert request.headers["authorization"] == "Bearer fake-azure-ad-token"
assert "chat_template_kwargs" not in json.loads(request.read())

@patch.object(import_module("litellm.responses.file_search.emulated_handler"), "_call_aresponses")
@patch.object(
import_module("litellm.responses.main").ProviderConfigManager, "get_provider_responses_api_config"
Expand Down
Loading