mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
Merge pull request #50462 from BerriAI/litellm_backport_messages_reject_background_lts_1_108_x
fix(anthropic): backport background rejection on the /v1/messages Responses bridge to lts/1.108.x
This commit is contained in:
commit
984bc6878f
4 changed files with 95 additions and 14 deletions
|
|
@ -16,7 +16,7 @@ from litellm.integrations.anthropic_cache_control_hook import (
|
|||
)
|
||||
from litellm.litellm_core_utils.core_helpers import as_str_mapping, iter_items, normalize_drop_params
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import cache_control_as_prompt_cache_breakpoint
|
||||
from litellm.litellm_core_utils.tool_fields import drops_mcp_servers, tools_without_mcp_servers
|
||||
from litellm.litellm_core_utils.tool_fields import drop_policy, drops_mcp_servers, tools_without_mcp_servers
|
||||
from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator
|
||||
from litellm.types.integrations.custom_logger import CONVERTED_STREAM_KEYS
|
||||
from litellm.types.llms.anthropic import (
|
||||
|
|
@ -60,6 +60,33 @@ def _forwarded_kwargs(extra_kwargs: Mapping[str, object] | None) -> Mapping[str,
|
|||
return extra_kwargs or {}
|
||||
|
||||
|
||||
def _requests_background(forwarded_kwargs: Mapping[str, object]) -> bool:
|
||||
extra_body: Final = as_str_mapping(forwarded_kwargs.get("extra_body")) or {}
|
||||
return any(
|
||||
value is not None and value is not False
|
||||
for value in (forwarded_kwargs.get("background"), extra_body.get("background"))
|
||||
)
|
||||
|
||||
|
||||
def _without_background(extra_body: Mapping[str, object]) -> Mapping[str, object]:
|
||||
return {key: value for key, value in extra_body.items() if key != "background"}
|
||||
|
||||
|
||||
def _reject_background_mode(model: str, forwarded_kwargs: Mapping[str, object]) -> None:
|
||||
if not _requests_background(forwarded_kwargs) or drop_policy(forwarded_kwargs).drops("background"):
|
||||
return
|
||||
provider: Final = forwarded_kwargs.get("custom_llm_provider")
|
||||
raise litellm.UnsupportedParamsError(
|
||||
model=model,
|
||||
llm_provider=provider if isinstance(provider, str) else None,
|
||||
message=(
|
||||
f"/v1/messages does not support parameters: ['background'], for model={model}. "
|
||||
"A background run answers with a response id the Anthropic Messages API has no way to retrieve. "
|
||||
"To drop it, set `litellm.drop_params=True` or for proxy:\n\n`litellm_settings:\n drop_params: true`\n"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _provider_returns_encrypted_reasoning(model: str, custom_llm_provider: object) -> bool:
|
||||
provider: Final = (
|
||||
custom_llm_provider if isinstance(custom_llm_provider, str) else litellm.get_llm_provider(model=model)[1]
|
||||
|
|
@ -147,6 +174,7 @@ def _build_responses_kwargs(
|
|||
Build the kwargs dict to pass directly to litellm.responses() / litellm.aresponses().
|
||||
"""
|
||||
forwarded_kwargs: Final = _forwarded_kwargs(extra_kwargs)
|
||||
_reject_background_mode(model, forwarded_kwargs)
|
||||
target_provider: Final = forwarded_kwargs.get("custom_llm_provider")
|
||||
request_tools: Final = tools_without_mcp_servers(
|
||||
forwarded_kwargs.get("mcp_servers"),
|
||||
|
|
@ -227,7 +255,7 @@ def _build_responses_kwargs(
|
|||
responses_kwargs["stream"] = True
|
||||
|
||||
# Forward litellm-specific kwargs (api_key, api_base, logging obj, etc.)
|
||||
excluded: Final = CONVERTED_STREAM_KEYS | {"anthropic_messages", "mcp_servers"}
|
||||
excluded: Final = CONVERTED_STREAM_KEYS | {"anthropic_messages", "background", "mcp_servers"}
|
||||
for key, value in forwarded_kwargs.items():
|
||||
if key == "litellm_logging_obj" and value is not None:
|
||||
from litellm.litellm_core_utils.litellm_logging import (
|
||||
|
|
@ -241,6 +269,8 @@ def _build_responses_kwargs(
|
|||
# Responses API result back to a ModelResponse for the row.
|
||||
setattr(value, "call_type", CallTypes.anthropic_messages.value)
|
||||
responses_kwargs[key] = value
|
||||
elif key == "extra_body" and (extra_body := as_str_mapping(value)) is not None:
|
||||
responses_kwargs[key] = _without_background(extra_body)
|
||||
elif key not in excluded and key not in responses_kwargs and value is not None:
|
||||
responses_kwargs[key] = value
|
||||
|
||||
|
|
|
|||
|
|
@ -562,6 +562,69 @@ def test_client_cache_control_changes_nothing_for_a_deployment_that_does_not_rea
|
|||
assert marked == unmarked
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"background_request",
|
||||
[
|
||||
pytest.param({"background": True}, id="background"),
|
||||
pytest.param({"extra_body": {"background": True}}, id="extra-body-background"),
|
||||
pytest.param({"extra_body": {"background": "true"}}, id="extra-body-background-string"),
|
||||
pytest.param({"background": 0}, id="background-zero"),
|
||||
],
|
||||
)
|
||||
def test_background_answers_400_without_drop_params(background_request: Mapping[str, object]) -> None:
|
||||
with pytest.raises(litellm.UnsupportedParamsError) as rejected:
|
||||
_build_responses_kwargs(
|
||||
model="openai/gpt-5.6",
|
||||
extra_kwargs={**OPENAI_API, **background_request},
|
||||
max_tokens=1024,
|
||||
messages=MESSAGES,
|
||||
)
|
||||
|
||||
assert rejected.value.status_code == 400
|
||||
assert "['background']" in rejected.value.message
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("drop_setting", "global_drop_params"),
|
||||
[
|
||||
pytest.param({"drop_params": True}, False, id="drop_params"),
|
||||
pytest.param({"additional_drop_params": ["background"]}, False, id="additional_drop_params"),
|
||||
pytest.param({}, True, id="litellm.drop_params"),
|
||||
],
|
||||
)
|
||||
def test_dropped_background_never_reaches_the_responses_request(
|
||||
drop_setting: Mapping[str, object], global_drop_params: bool, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.setattr(litellm, "drop_params", global_drop_params)
|
||||
responses_kwargs: Final = _build_responses_kwargs(
|
||||
model="openai/gpt-5.6",
|
||||
extra_kwargs={
|
||||
**OPENAI_API,
|
||||
**drop_setting,
|
||||
"background": True,
|
||||
"extra_body": {"background": True, "service_tier": "priority"},
|
||||
},
|
||||
max_tokens=1024,
|
||||
messages=MESSAGES,
|
||||
)
|
||||
|
||||
assert "background" not in responses_kwargs
|
||||
assert responses_kwargs["extra_body"] == {"service_tier": "priority"}
|
||||
assert responses_kwargs["max_output_tokens"] == 1024
|
||||
|
||||
|
||||
def test_background_false_still_succeeds_without_reaching_the_responses_request() -> None:
|
||||
responses_kwargs: Final = _build_responses_kwargs(
|
||||
model="openai/gpt-5.6",
|
||||
extra_kwargs={**OPENAI_API, "background": False, "extra_body": {"background": False}},
|
||||
max_tokens=1024,
|
||||
messages=MESSAGES,
|
||||
)
|
||||
|
||||
assert "background" not in responses_kwargs
|
||||
assert responses_kwargs["extra_body"] == {}
|
||||
|
||||
|
||||
REASONING_MODEL: Final = "gpt-bridge-reasoning-cost"
|
||||
REASONING_RATES: Final = {
|
||||
"input_cost_per_token": 2e-06,
|
||||
|
|
|
|||
|
|
@ -4171,17 +4171,6 @@ _THINKING_HISTORY: Final = (
|
|||
)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ScriptedBedrockInvoke:
|
||||
replies: tuple[tuple[int, Mapping[str, object]], ...]
|
||||
bodies: tuple[dict[str, object], ...] = ()
|
||||
|
||||
def __call__(self, request: httpx.Request) -> httpx.Response:
|
||||
self.bodies = (*self.bodies, REASONING_BODY_ADAPTER.validate_json(request.content))
|
||||
status_code, body = self.replies[len(self.bodies) - 1]
|
||||
return httpx.Response(status_code, json=body)
|
||||
|
||||
|
||||
async def _send_bedrock_messages(
|
||||
model: str,
|
||||
model_info: Mapping[str, object] | None,
|
||||
|
|
|
|||
|
|
@ -1288,7 +1288,6 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
|
|||
"supports_native_streaming": {"type": "boolean"},
|
||||
"supports_image_size": {"type": "boolean"},
|
||||
"supports_native_structured_output": {"type": "boolean"},
|
||||
"supports_bedrock_native_structured_output": {"type": "boolean"},
|
||||
"use_openai_responses_path": {"type": "boolean"},
|
||||
"off_peak_pricing": {
|
||||
"type": "object",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue