mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
fix(bedrock): map Bedrock's 200 error body to a 400 on the Anthropic invoke /v1/messages route
This commit is contained in:
parent
ab02277d2d
commit
e11c0d9a9c
4 changed files with 75 additions and 8 deletions
|
|
@ -48,6 +48,7 @@ from litellm.types.utils import GenericStreamingChunk as GChunk
|
|||
from ..common_utils import (
|
||||
BedrockError,
|
||||
BedrockEventStreamResponseDict,
|
||||
bedrock_answered_json,
|
||||
bedrock_error_in_success_body,
|
||||
bedrock_event_stream_header,
|
||||
bedrock_event_stream_response,
|
||||
|
|
@ -193,10 +194,6 @@ def response_format_schema_from_request(data: str, json_mode: bool | None) -> Re
|
|||
return JsonModeRequest.model_validate(body).tool_schema()
|
||||
|
||||
|
||||
def _answered_json(response: httpx.Response) -> bool:
|
||||
return "application/json" in (response.headers.get("content-type") or "")
|
||||
|
||||
|
||||
def _stream_decoder(
|
||||
bedrock_invoke_provider: litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL | None,
|
||||
*,
|
||||
|
|
@ -264,7 +261,7 @@ async def make_call(
|
|||
response=response,
|
||||
)
|
||||
|
||||
if _answered_json(response):
|
||||
if bedrock_answered_json(response):
|
||||
await response.aread()
|
||||
success_body_error: Final = bedrock_error_in_success_body(response)
|
||||
if success_body_error is not None:
|
||||
|
|
@ -366,7 +363,7 @@ def make_sync_call(
|
|||
response=response,
|
||||
)
|
||||
|
||||
if _answered_json(response):
|
||||
if bedrock_answered_json(response):
|
||||
response.read()
|
||||
success_body_error: Final = bedrock_error_in_success_body(response)
|
||||
if success_body_error is not None:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import json
|
|||
import os
|
||||
import re
|
||||
import warnings
|
||||
from collections.abc import Iterator, Mapping, Sequence
|
||||
from collections.abc import AsyncIterator, Iterator, Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Any, Final, Literal, TypeAlias
|
||||
|
|
@ -147,6 +147,22 @@ def bedrock_error_in_success_body(response: httpx.Response) -> BedrockError | No
|
|||
)
|
||||
|
||||
|
||||
def bedrock_answered_json(response: httpx.Response) -> bool:
|
||||
return "application/json" in (response.headers.get("content-type") or "")
|
||||
|
||||
|
||||
async def _json_body_or_bedrock_error(response: httpx.Response) -> AsyncIterator[bytes]:
|
||||
body: Final = await response.aread()
|
||||
error: Final = bedrock_error_in_success_body(response)
|
||||
if error is not None:
|
||||
raise error
|
||||
yield body
|
||||
|
||||
|
||||
def bedrock_event_stream_bytes(response: httpx.Response) -> AsyncIterator[bytes]:
|
||||
return _json_body_or_bedrock_error(response) if bedrock_answered_json(response) else response.aiter_bytes()
|
||||
|
||||
|
||||
_BEDROCK_AWS_AUTH_PARAMETER_KEYS: Final[tuple[str, ...]] = (*AWS_AUTH_PARAM_KEYS, "aws_region_name")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ from litellm.llms.bedrock.chat.invoke_transformations.base_invoke_transformation
|
|||
from litellm.llms.bedrock.common_utils import (
|
||||
BedrockError,
|
||||
apply_bedrock_invoke_structured_output,
|
||||
bedrock_error_in_success_body,
|
||||
bedrock_event_stream_bytes,
|
||||
bedrock_supports_tool_search,
|
||||
ensure_bedrock_anthropic_messages_tool_names,
|
||||
get_anthropic_beta_from_headers,
|
||||
|
|
@ -62,6 +64,7 @@ from litellm.types.llms.anthropic import (
|
|||
ANTHROPIC_FINE_GRAINED_TOOL_STREAMING_BETA_HEADER,
|
||||
ANTHROPIC_TOOL_SEARCH_BETA_HEADER,
|
||||
)
|
||||
from litellm.types.llms.anthropic_messages.anthropic_response import AnthropicMessagesResponse
|
||||
from litellm.types.llms.bedrock import BedrockInvokeAnthropicMessagesRequest
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
|
@ -868,6 +871,17 @@ class AmazonAnthropicClaudeMessagesConfig(
|
|||
|
||||
return anthropic_messages_request
|
||||
|
||||
def transform_anthropic_messages_response(
|
||||
self,
|
||||
model: str,
|
||||
raw_response: httpx.Response,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
) -> AnthropicMessagesResponse:
|
||||
success_body_error: Final = bedrock_error_in_success_body(raw_response)
|
||||
if success_body_error is not None:
|
||||
raise success_body_error
|
||||
return super().transform_anthropic_messages_response(model, raw_response, logging_obj)
|
||||
|
||||
def get_async_streaming_response_iterator(
|
||||
self,
|
||||
model: str,
|
||||
|
|
@ -879,7 +893,7 @@ class AmazonAnthropicClaudeMessagesConfig(
|
|||
model=model,
|
||||
)
|
||||
completion_stream: Final = aws_decoder.aiter_bytes(
|
||||
httpx_response.aiter_bytes(), response_headers=httpx_response.headers
|
||||
bedrock_event_stream_bytes(httpx_response), response_headers=httpx_response.headers
|
||||
)
|
||||
# Convert decoded Bedrock events to Server-Sent Events expected by Anthropic clients.
|
||||
return self.bedrock_sse_wrapper(
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ from litellm.llms.anthropic.messages.native.mid_conversation_system import (
|
|||
as_system_content_blocks,
|
||||
)
|
||||
from litellm.llms.bedrock.common_utils import (
|
||||
BedrockError,
|
||||
ensure_bedrock_anthropic_messages_tool_names,
|
||||
normalize_custom_field_on_tools,
|
||||
normalize_tool_input_schema_types_for_bedrock_invoke,
|
||||
|
|
@ -4553,3 +4554,42 @@ async def test_bedrock_chat_fallback_to_anthropic_sends_the_tool_bedrock_cleaned
|
|||
|
||||
(body,) = upstream.anthropic_bodies
|
||||
assert body["tools"] == [{"name": "get_weather", "input_schema": schema, **sent_extras}]
|
||||
|
||||
|
||||
_UNKNOWN_OPERATION_BODY: Final = {
|
||||
"Output": {"__type": "com.amazon.coral.service#UnknownOperationException"},
|
||||
"Version": "1.0",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("local_model_cost_map")
|
||||
@pytest.mark.parametrize(
|
||||
("stream", "raised"),
|
||||
[(False, litellm.BadRequestError), (True, BedrockError)],
|
||||
ids=["non-stream", "stream"],
|
||||
)
|
||||
async def test_bedrock_messages_raise_a_400_when_bedrock_answers_200_with_an_unknown_operation_body(
|
||||
stream: bool,
|
||||
raised: type[litellm.BadRequestError] | type[BedrockError],
|
||||
) -> None:
|
||||
bedrock: Final = ScriptedBedrockInvoke(replies=((200, _UNKNOWN_OPERATION_BODY),))
|
||||
|
||||
async def _call_and_drain() -> None:
|
||||
response: Final = await litellm.anthropic_messages(
|
||||
model="bedrock/invoke/anthropic/us.anthropic.claude-haiku-5-5",
|
||||
messages=[{"role": "user", "content": "Say hi in three words"}],
|
||||
max_tokens=20,
|
||||
stream=stream,
|
||||
aws_access_key_id="test-access-key",
|
||||
aws_secret_access_key="test-secret-key",
|
||||
aws_region_name="us-east-1",
|
||||
client=AsyncHTTPHandler(transport=httpx.MockTransport(bedrock)),
|
||||
)
|
||||
if isinstance(response, AsyncIterator):
|
||||
_ = [chunk async for chunk in response]
|
||||
|
||||
with pytest.raises(raised) as error:
|
||||
await _call_and_drain()
|
||||
|
||||
assert error.value.status_code == 400
|
||||
assert "UnknownOperationException" in str(error.value)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue