mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(openai-responses): strip Anthropic cache_control from Responses API requests (#28431)
Squash-merged by litellm-agent from cwang-otto's PR.
This commit is contained in:
parent
b9b715157b
commit
6a13fed209
2 changed files with 129 additions and 1 deletions
|
|
@ -126,9 +126,21 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
litellm_params: GenericLiteLLMParams,
|
||||
headers: dict,
|
||||
) -> Dict:
|
||||
"""No transform applied since inputs are in OpenAI spec already"""
|
||||
"""Strip Anthropic-only `cache_control` markers before sending to OpenAI.
|
||||
|
||||
OpenAI's Responses API rejects unknown fields on input content blocks
|
||||
with HTTP 400 ("Unknown parameter: 'input[0].content[0].cache_control'").
|
||||
Chat Completions strips these in
|
||||
`remove_cache_control_flag_from_messages_and_tools`; mirror that here.
|
||||
"""
|
||||
|
||||
input = self._validate_input_param(input)
|
||||
tools = response_api_optional_request_params.get("tools")
|
||||
input, tools = self.remove_cache_control_flag_from_input_and_tools(
|
||||
model=model, input=input, tools=tools
|
||||
)
|
||||
if tools is not None:
|
||||
response_api_optional_request_params["tools"] = tools
|
||||
final_request_params = dict(
|
||||
ResponsesAPIRequestParams(
|
||||
model=model, input=input, **response_api_optional_request_params
|
||||
|
|
@ -137,6 +149,38 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
|
||||
return final_request_params
|
||||
|
||||
def remove_cache_control_flag_from_input_and_tools(
|
||||
self,
|
||||
model: str, # allows overrides to selectively run this
|
||||
input: Union[str, ResponseInputParam],
|
||||
tools: Optional[List[ALL_RESPONSES_API_TOOL_PARAMS]] = None,
|
||||
) -> Tuple[
|
||||
Union[str, ResponseInputParam],
|
||||
Optional[List[ALL_RESPONSES_API_TOOL_PARAMS]],
|
||||
]:
|
||||
"""Sibling of `remove_cache_control_flag_from_messages_and_tools` on
|
||||
the chat path. Strips Anthropic-only `cache_control` markers from
|
||||
Responses API input content blocks and tools.
|
||||
|
||||
`filter_value_from_dict` mutates each dict in place, so the same
|
||||
objects are returned.
|
||||
"""
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
filter_value_from_dict,
|
||||
)
|
||||
|
||||
if isinstance(input, list):
|
||||
for item in input:
|
||||
if isinstance(item, dict):
|
||||
filter_value_from_dict(cast(dict, item), "cache_control")
|
||||
|
||||
if tools is not None:
|
||||
for tool in tools:
|
||||
if isinstance(tool, dict):
|
||||
filter_value_from_dict(cast(dict, tool), "cache_control")
|
||||
|
||||
return input, tools
|
||||
|
||||
def _validate_input_param(
|
||||
self, input: Union[str, ResponseInputParam]
|
||||
) -> Union[str, ResponseInputParam]:
|
||||
|
|
|
|||
|
|
@ -86,6 +86,90 @@ class TestOpenAIResponsesAPIConfig:
|
|||
|
||||
self.validate_responses_api_request_params(result, expected_fields)
|
||||
|
||||
def test_transform_strips_cache_control_from_input_content_blocks(self):
|
||||
"""`cache_control` markers (Anthropic-only) must be stripped from
|
||||
Responses API input content blocks before sending to OpenAI.
|
||||
|
||||
OpenAI rejects unknown params on input content blocks with HTTP 400:
|
||||
"Unknown parameter: 'input[0].content[0].cache_control'"
|
||||
Chat Completions strips these via
|
||||
`remove_cache_control_flag_from_messages_and_tools`; the Responses
|
||||
path must do the same.
|
||||
"""
|
||||
input_with_cache_control = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "Hello",
|
||||
"cache_control": {"type": "ephemeral"},
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
result = self.config.transform_responses_api_request(
|
||||
model=self.model,
|
||||
input=input_with_cache_control,
|
||||
response_api_optional_request_params={},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "cache_control" not in result["input"][0]["content"][0]
|
||||
assert result["input"][0]["content"][0]["type"] == "input_text"
|
||||
assert result["input"][0]["content"][0]["text"] == "Hello"
|
||||
|
||||
def test_transform_strips_cache_control_from_tools(self):
|
||||
"""`cache_control` markers must also be stripped from tools for
|
||||
symmetry with the Chat Completions path. OpenAI currently accepts
|
||||
cache_control on tools silently but stripping keeps the wire payload
|
||||
clean and matches `remove_cache_control_flag_from_messages_and_tools`.
|
||||
"""
|
||||
tools_with_cache_control = [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "get_weather",
|
||||
"description": "Get the weather",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"city": {"type": "string"}},
|
||||
},
|
||||
"cache_control": {"type": "ephemeral"},
|
||||
}
|
||||
]
|
||||
|
||||
result = self.config.transform_responses_api_request(
|
||||
model=self.model,
|
||||
input="hi",
|
||||
response_api_optional_request_params={"tools": tools_with_cache_control},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "cache_control" not in result["tools"][0]
|
||||
assert result["tools"][0]["name"] == "get_weather"
|
||||
|
||||
def test_transform_preserves_input_without_cache_control(self):
|
||||
"""Inputs without cache_control must pass through unmodified."""
|
||||
input_clean = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [{"type": "input_text", "text": "Hello"}],
|
||||
}
|
||||
]
|
||||
|
||||
result = self.config.transform_responses_api_request(
|
||||
model=self.model,
|
||||
input=input_clean,
|
||||
response_api_optional_request_params={},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert result["input"] == input_clean
|
||||
|
||||
def test_transform_streaming_response(self):
|
||||
"""Test streaming response transformation"""
|
||||
# Test with a text delta event
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue