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:
cwang-otto 2026-05-21 10:55:28 +08:00 committed by Sameer Kankute
parent b9b715157b
commit 6a13fed209
No known key found for this signature in database
2 changed files with 129 additions and 1 deletions

View file

@ -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]:

View file

@ -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