mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
[Bedrock] add async_transform_request + convert document URL sources to base64
This commit is contained in:
parent
7744859606
commit
b778b66855
1 changed files with 164 additions and 2 deletions
|
|
@ -2,6 +2,14 @@ from typing import TYPE_CHECKING, Any, List, Optional
|
|||
|
||||
import httpx
|
||||
|
||||
from litellm.anthropic_beta_headers_manager import filter_and_transform_beta_headers
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
convert_to_anthropic_image_obj,
|
||||
)
|
||||
from litellm.litellm_core_utils.prompt_templates.image_handling import (
|
||||
async_convert_url_to_base64,
|
||||
convert_url_to_base64,
|
||||
)
|
||||
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
|
||||
from litellm.llms.bedrock.chat.invoke_transformations.base_invoke_transformation import (
|
||||
AmazonInvokeConfig,
|
||||
|
|
@ -113,6 +121,8 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig):
|
|||
if "anthropic_version" not in _anthropic_request:
|
||||
_anthropic_request["anthropic_version"] = self.anthropic_version
|
||||
|
||||
self._convert_document_url_sources_to_base64(_anthropic_request)
|
||||
|
||||
# Remove `custom` field from tools (Bedrock doesn't support it)
|
||||
# Claude Code sends `custom: {defer_loading: true}` on tool definitions,
|
||||
# which causes Bedrock to reject the request with "Extra inputs are not permitted"
|
||||
|
|
@ -144,11 +154,163 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig):
|
|||
|
||||
# Filter out beta headers that Bedrock Invoke doesn't support
|
||||
# Uses centralized configuration from anthropic_beta_headers_config.json
|
||||
beta_list = list(beta_set)
|
||||
_anthropic_request["anthropic_beta"] = beta_list
|
||||
beta_list = filter_and_transform_beta_headers(
|
||||
beta_headers=list(beta_set),
|
||||
provider="bedrock",
|
||||
)
|
||||
if beta_list:
|
||||
_anthropic_request["anthropic_beta"] = beta_list
|
||||
|
||||
return _anthropic_request
|
||||
|
||||
async def async_transform_request(
|
||||
self,
|
||||
model: str,
|
||||
messages: List[AllMessageValues],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
headers: dict,
|
||||
) -> dict:
|
||||
# Filter out AWS authentication parameters before passing to Anthropic transformation
|
||||
filtered_params = {
|
||||
k: v
|
||||
for k, v in optional_params.items()
|
||||
if k not in self.aws_authentication_params
|
||||
}
|
||||
filtered_params = self._normalize_bedrock_tool_search_tools(filtered_params)
|
||||
|
||||
_anthropic_request = AnthropicConfig.transform_request(
|
||||
self,
|
||||
model=model,
|
||||
messages=messages,
|
||||
optional_params=filtered_params,
|
||||
litellm_params=litellm_params,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
_anthropic_request.pop("model", None)
|
||||
_anthropic_request.pop("stream", None)
|
||||
_anthropic_request.pop("output_format", None)
|
||||
_anthropic_request.pop("output_config", None)
|
||||
if "anthropic_version" not in _anthropic_request:
|
||||
_anthropic_request["anthropic_version"] = self.anthropic_version
|
||||
|
||||
await self._async_convert_document_url_sources_to_base64(_anthropic_request)
|
||||
|
||||
remove_custom_field_from_tools(_anthropic_request)
|
||||
|
||||
tools = optional_params.get("tools")
|
||||
tool_search_used = self.is_tool_search_used(tools)
|
||||
programmatic_tool_calling_used = self.is_programmatic_tool_calling_used(tools)
|
||||
input_examples_used = self.is_input_examples_used(tools)
|
||||
|
||||
beta_set = set(get_anthropic_beta_from_headers(headers))
|
||||
auto_betas = self.get_anthropic_beta_list(
|
||||
model=model,
|
||||
optional_params=optional_params,
|
||||
computer_tool_used=self.is_computer_tool_used(tools),
|
||||
prompt_caching_set=False,
|
||||
file_id_used=self.is_file_id_used(messages),
|
||||
mcp_server_used=self.is_mcp_server_used(optional_params.get("mcp_servers")),
|
||||
)
|
||||
beta_set.update(auto_betas)
|
||||
|
||||
if tool_search_used and not (
|
||||
programmatic_tool_calling_used or input_examples_used
|
||||
):
|
||||
beta_set.discard(ANTHROPIC_TOOL_SEARCH_BETA_HEADER)
|
||||
if "opus-4" in model.lower() or "opus_4" in model.lower():
|
||||
beta_set.add("tool-search-tool-2025-10-19")
|
||||
|
||||
beta_list = filter_and_transform_beta_headers(
|
||||
beta_headers=list(beta_set),
|
||||
provider="bedrock",
|
||||
)
|
||||
if beta_list:
|
||||
_anthropic_request["anthropic_beta"] = beta_list
|
||||
|
||||
return _anthropic_request
|
||||
|
||||
def _convert_document_url_sources_to_base64(self, anthropic_request: dict) -> None:
|
||||
"""
|
||||
Bedrock Invoke does not accept document URL sources. Convert to base64 payloads.
|
||||
"""
|
||||
messages = anthropic_request.get("messages")
|
||||
if not isinstance(messages, list):
|
||||
return
|
||||
|
||||
for message in messages:
|
||||
if not isinstance(message, dict):
|
||||
continue
|
||||
content = message.get("content")
|
||||
if not isinstance(content, list):
|
||||
continue
|
||||
|
||||
for block in content:
|
||||
if not isinstance(block, dict) or block.get("type") != "document":
|
||||
continue
|
||||
source = block.get("source")
|
||||
if not isinstance(source, dict) or source.get("type") != "url":
|
||||
continue
|
||||
source_url = source.get("url")
|
||||
if not isinstance(source_url, str):
|
||||
continue
|
||||
|
||||
inferred_format: Optional[str] = None
|
||||
if source_url.lower().endswith(".pdf"):
|
||||
inferred_format = "application/pdf"
|
||||
base64_url = convert_url_to_base64(url=source_url)
|
||||
image_chunk = convert_to_anthropic_image_obj(
|
||||
openai_image_url=base64_url,
|
||||
format=inferred_format,
|
||||
)
|
||||
block["source"] = {
|
||||
"type": "base64",
|
||||
"media_type": image_chunk["media_type"],
|
||||
"data": image_chunk["data"],
|
||||
}
|
||||
|
||||
async def _async_convert_document_url_sources_to_base64(
|
||||
self, anthropic_request: dict
|
||||
) -> None:
|
||||
"""
|
||||
Async version of document URL conversion for async completion paths.
|
||||
"""
|
||||
messages = anthropic_request.get("messages")
|
||||
if not isinstance(messages, list):
|
||||
return
|
||||
|
||||
for message in messages:
|
||||
if not isinstance(message, dict):
|
||||
continue
|
||||
content = message.get("content")
|
||||
if not isinstance(content, list):
|
||||
continue
|
||||
|
||||
for block in content:
|
||||
if not isinstance(block, dict) or block.get("type") != "document":
|
||||
continue
|
||||
source = block.get("source")
|
||||
if not isinstance(source, dict) or source.get("type") != "url":
|
||||
continue
|
||||
source_url = source.get("url")
|
||||
if not isinstance(source_url, str):
|
||||
continue
|
||||
|
||||
inferred_format: Optional[str] = None
|
||||
if source_url.lower().endswith(".pdf"):
|
||||
inferred_format = "application/pdf"
|
||||
base64_url = await async_convert_url_to_base64(url=source_url)
|
||||
image_chunk = convert_to_anthropic_image_obj(
|
||||
openai_image_url=base64_url,
|
||||
format=inferred_format,
|
||||
)
|
||||
block["source"] = {
|
||||
"type": "base64",
|
||||
"media_type": image_chunk["media_type"],
|
||||
"data": image_chunk["data"],
|
||||
}
|
||||
|
||||
def _normalize_bedrock_tool_search_tools(self, optional_params: dict) -> dict:
|
||||
"""
|
||||
Convert tool search entries to the format supported by the Bedrock Invoke API.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue