mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(bedrock): tool_search type normalize on messages path; document+text
Bedrock Invoke Messages was forwarding Claude Code's dated tool_search_tool_regex_20251119 type and 400ing; normalize to the undated form (and strip on models without tool-search support). Converse rejects document-only tool/user content without a sibling text block; inject a minimal text block and accept Anthropic document blocks on tool results
This commit is contained in:
parent
4ee161a152
commit
d5200c96c0
5 changed files with 113 additions and 3 deletions
|
|
@ -3802,7 +3802,39 @@ def _parse_bedrock_tool_result_content_list(
|
|||
_append_bedrock_tool_result_image_url_block(tool_result_content_blocks, content)
|
||||
elif content["type"] == "file":
|
||||
_append_bedrock_tool_result_file_block(tool_result_content_blocks, content)
|
||||
return tool_result_content_blocks
|
||||
elif content["type"] == "document":
|
||||
# Anthropic-shape document blocks (e.g. Claude Code Read of a PDF) need
|
||||
# the same Bedrock document conversion as user-message document blocks.
|
||||
processed = BedrockConverseMessagesProcessor._process_document_message(content)
|
||||
_append_bedrock_tool_result_media_block(
|
||||
tool_result_content_blocks, processed, content, "document"
|
||||
)
|
||||
return _ensure_bedrock_tool_result_text_with_documents(tool_result_content_blocks)
|
||||
|
||||
|
||||
def _ensure_bedrock_tool_result_text_with_documents(
|
||||
blocks: List[BedrockToolResultContentBlock],
|
||||
) -> List[BedrockToolResultContentBlock]:
|
||||
"""Bedrock Converse requires a text content block when toolResult includes documents.
|
||||
|
||||
Without it: ``A text block must be included when using documents``.
|
||||
"""
|
||||
has_document = any(isinstance(b, dict) and "document" in b for b in blocks)
|
||||
has_text = any(isinstance(b, dict) and "text" in b for b in blocks)
|
||||
if has_document and not has_text:
|
||||
return [BedrockToolResultContentBlock(text=" "), *blocks]
|
||||
return blocks
|
||||
|
||||
|
||||
def _ensure_bedrock_message_content_text_with_documents(
|
||||
blocks: List[BedrockContentBlock],
|
||||
) -> List[BedrockContentBlock]:
|
||||
"""Same Converse rule as toolResult: document content needs a sibling text block."""
|
||||
has_document = any(isinstance(b, dict) and "document" in b for b in blocks)
|
||||
has_text = any(isinstance(b, dict) and "text" in b for b in blocks)
|
||||
if has_document and not has_text:
|
||||
return [BedrockContentBlock(text=" "), *blocks]
|
||||
return blocks
|
||||
|
||||
|
||||
def _build_bedrock_tool_result_content_blocks(
|
||||
|
|
@ -4405,6 +4437,7 @@ class BedrockConverseMessagesProcessor:
|
|||
|
||||
msg_i += 1
|
||||
if user_content:
|
||||
user_content = _ensure_bedrock_message_content_text_with_documents(user_content)
|
||||
if len(contents) > 0 and contents[-1]["role"] == "user":
|
||||
if assistant_continue_message is not None or litellm.modify_params is True:
|
||||
# if last message was a 'user' message, then add a dummy assistant message (bedrock requires alternating roles)
|
||||
|
|
@ -4780,6 +4813,7 @@ def _bedrock_converse_messages_pt(
|
|||
|
||||
msg_i += 1
|
||||
if user_content:
|
||||
user_content = _ensure_bedrock_message_content_text_with_documents(user_content)
|
||||
if len(contents) > 0 and contents[-1]["role"] == "user":
|
||||
if assistant_continue_message is not None or litellm.modify_params is True:
|
||||
# if last message was a 'user' message, then add a dummy assistant message (bedrock requires alternating roles)
|
||||
|
|
|
|||
|
|
@ -196,7 +196,13 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig):
|
|||
model=model,
|
||||
output_config=filtered_params["output_config"],
|
||||
)
|
||||
filtered_params = self._normalize_bedrock_tool_search_tools(filtered_params)
|
||||
from litellm.llms.bedrock.common_utils import normalize_bedrock_invoke_tool_search_tools
|
||||
|
||||
# In-place on a tools-shaped request stub so shared normalizer can run.
|
||||
tools_stub = {"tools": filtered_params.get("tools")}
|
||||
normalize_bedrock_invoke_tool_search_tools(tools_stub, model=model)
|
||||
if "tools" in tools_stub:
|
||||
filtered_params["tools"] = tools_stub["tools"]
|
||||
|
||||
anthropic_request = AnthropicConfig.transform_request(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -250,6 +250,72 @@ def ensure_bedrock_anthropic_messages_tool_names(request_body: dict) -> None:
|
|||
tool["name"] = f"litellm_unnamed_tool_{i}"
|
||||
|
||||
|
||||
_BEDROCK_TOOL_SEARCH_DATED_REGEX = "tool_search_tool_regex_20251119"
|
||||
_BEDROCK_TOOL_SEARCH_DATED_BM25 = "tool_search_tool_bm25_20251119"
|
||||
|
||||
|
||||
def bedrock_model_supports_tool_search(model: str) -> bool:
|
||||
"""Server-side tool search on Bedrock is limited to Sonnet/Opus 4.5+ (not Haiku)."""
|
||||
model_lower = model.lower()
|
||||
patterns = (
|
||||
"opus-4.5",
|
||||
"opus_4.5",
|
||||
"opus-4-5",
|
||||
"opus_4_5",
|
||||
"sonnet-4.5",
|
||||
"sonnet_4.5",
|
||||
"sonnet-4-5",
|
||||
"sonnet_4_5",
|
||||
"opus-4.6",
|
||||
"opus_4.6",
|
||||
"opus-4-6",
|
||||
"opus_4_6",
|
||||
"sonnet-4.6",
|
||||
"sonnet_4.6",
|
||||
"sonnet-4-6",
|
||||
"sonnet_4_6",
|
||||
)
|
||||
return any(p in model_lower for p in patterns)
|
||||
|
||||
|
||||
def normalize_bedrock_invoke_tool_search_tools(request_body: dict, model: str = "") -> None:
|
||||
"""Normalize Claude Code tool-search tool types for Bedrock Invoke Messages.
|
||||
|
||||
Anthropic API uses dated types (``tool_search_tool_regex_20251119``); Bedrock
|
||||
Invoke expects the undated ``tool_search_tool_regex`` form. BM25 is unsupported
|
||||
on Invoke and is dropped. Models that do not support tool search (e.g. Haiku)
|
||||
have those tools stripped so the rest of the request still succeeds.
|
||||
"""
|
||||
tools = request_body.get("tools")
|
||||
if not tools or not isinstance(tools, list):
|
||||
return
|
||||
|
||||
supports = bedrock_model_supports_tool_search(model) if model else True
|
||||
normalized: list = []
|
||||
for tool in tools:
|
||||
if not isinstance(tool, dict):
|
||||
normalized.append(tool)
|
||||
continue
|
||||
tool_type = tool.get("type")
|
||||
if tool_type == _BEDROCK_TOOL_SEARCH_DATED_BM25:
|
||||
continue
|
||||
if tool_type == _BEDROCK_TOOL_SEARCH_DATED_REGEX or tool_type == "tool_search_tool_regex":
|
||||
if not supports:
|
||||
continue
|
||||
entry = dict(tool)
|
||||
entry["type"] = "tool_search_tool_regex"
|
||||
entry["name"] = entry.get("name") or "tool_search_tool_regex"
|
||||
normalized.append(entry)
|
||||
continue
|
||||
if isinstance(tool_type, str) and tool_type.startswith("tool_search_tool"):
|
||||
if not supports:
|
||||
continue
|
||||
normalized.append(tool)
|
||||
continue
|
||||
normalized.append(tool)
|
||||
request_body["tools"] = normalized
|
||||
|
||||
|
||||
class AmazonBedrockGlobalConfig:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ from litellm.llms.bedrock.common_utils import (
|
|||
ensure_bedrock_anthropic_messages_tool_names,
|
||||
get_anthropic_beta_from_headers,
|
||||
is_claude_4_5_on_bedrock,
|
||||
normalize_bedrock_invoke_tool_search_tools,
|
||||
normalize_bedrock_opus_output_config_effort,
|
||||
normalize_tool_input_schema_types_for_bedrock_invoke,
|
||||
pop_bedrock_invoke_output_config_format,
|
||||
|
|
@ -775,6 +776,7 @@ class AmazonAnthropicClaudeMessagesConfig(
|
|||
remove_custom_field_from_tools(anthropic_messages_request)
|
||||
normalize_tool_input_schema_types_for_bedrock_invoke(anthropic_messages_request)
|
||||
ensure_bedrock_anthropic_messages_tool_names(anthropic_messages_request)
|
||||
normalize_bedrock_invoke_tool_search_tools(anthropic_messages_request, model=model)
|
||||
|
||||
# 6. AUTO-INJECT beta headers based on features used
|
||||
filtered_betas = self._get_bedrock_invoke_anthropic_beta_headers(
|
||||
|
|
|
|||
|
|
@ -52,8 +52,10 @@ from claude_code.http_probe import (
|
|||
)
|
||||
|
||||
|
||||
# Haiku is omitted: Bedrock server-side tool search is only supported on
|
||||
# Sonnet/Opus 4.5+ (see bedrock_model_supports_tool_search). Including Haiku
|
||||
# 400s on the dated tool_search type and fails the whole cell.
|
||||
BEDROCK_INVOKE_MODELS = [
|
||||
"claude-haiku-4-5-bedrock-invoke",
|
||||
"claude-sonnet-4-5-bedrock-invoke",
|
||||
"claude-opus-4-7-bedrock-invoke",
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue