Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion litellm/litellm_core_utils/prompt_templates/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4408,7 +4408,7 @@ def _bedrock_tools_pt(tools: List) -> List[BedrockToolBlock]:
]
"""
"""
Bedrock toolConfig looks like:
Bedrock toolConfig looks like:
"tools": [
{
"toolSpec": {
Expand Down Expand Up @@ -4436,6 +4436,7 @@ def _bedrock_tools_pt(tools: List) -> List[BedrockToolBlock]:

tool_block_list: List[BedrockToolBlock] = []
for tool in tools:
# Handle regular function tools
parameters = tool.get("function", {}).get(
"parameters", {"type": "object", "properties": {}}
)
Expand Down
71 changes: 66 additions & 5 deletions litellm/llms/bedrock/chat/converse_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,39 @@ def _is_nova_lite_2_model(self, model: str) -> bool:
# Check if the model is specifically Nova Lite 2
return "nova-2-lite" in model_without_region

def _map_web_search_options(
self,
web_search_options: dict,
model: str
) -> Optional[BedrockToolBlock]:
"""
Map web_search_options to Nova grounding systemTool.

Nova grounding (web search) is only supported on Amazon Nova models.
Returns None for non-Nova models.

Args:
web_search_options: The web_search_options dict from the request
model: The model identifier string

Returns:
BedrockToolBlock with systemTool for Nova models, None otherwise

Reference: https://docs.aws.amazon.com/nova/latest/userguide/grounding.html
"""
# Only Nova models support nova_grounding
# Model strings can be like: "amazon.nova-pro-v1:0", "us.amazon.nova-pro-v1:0", etc.
if "nova" not in model.lower():
verbose_logger.debug(
f"web_search_options passed but model {model} is not a Nova model. "
"Nova grounding is only supported on Amazon Nova models."
)
return None

# Nova doesn't support search_context_size or user_location params
# (unlike Anthropic), so we just enable grounding with no options
return BedrockToolBlock(systemTool={"name": "nova_grounding"})

def _transform_reasoning_effort_to_reasoning_config(
self, reasoning_effort: str
) -> dict:
Expand Down Expand Up @@ -438,6 +471,10 @@ def get_supported_openai_params(self, model: str) -> List[str]:
):
supported_params.append("tools")

# Nova models support web_search_options (mapped to nova_grounding systemTool)
if base_model.startswith("amazon.nova"):
supported_params.append("web_search_options")

if litellm.utils.supports_tool_choice(
model=model, custom_llm_provider=self.custom_llm_provider
) or litellm.utils.supports_tool_choice(
Expand Down Expand Up @@ -730,6 +767,13 @@ def map_openai_params(
if bedrock_tier in ("default", "flex", "priority"):
optional_params["serviceTier"] = {"type": bedrock_tier}

if param == "web_search_options" and value and isinstance(value, dict):
grounding_tool = self._map_web_search_options(value, model)
if grounding_tool is not None:
optional_params = self._add_tools_to_optional_params(
optional_params=optional_params, tools=[grounding_tool]
)

# Only update thinking tokens for non-GPT-OSS models and non-Nova-Lite-2 models
# Nova Lite 2 handles token budgeting differently through reasoningConfig
if "gpt-oss" not in model and not self._is_nova_lite_2_model(model):
Expand Down Expand Up @@ -1388,20 +1432,23 @@ def _translate_message_content(self, content_blocks: List[ContentBlock]) -> Tupl
str,
List[ChatCompletionToolCallChunk],
Optional[List[BedrockConverseReasoningContentBlock]],
Optional[List[CitationsContentBlock]],
]:
"""
Translate the message content to a string and a list of tool calls and reasoning content blocks
Translate the message content to a string and a list of tool calls, reasoning content blocks, and citations.

Returns:
content_str: str
tools: List[ChatCompletionToolCallChunk]
reasoningContentBlocks: Optional[List[BedrockConverseReasoningContentBlock]]
citationsContentBlocks: Optional[List[CitationsContentBlock]] - Citations from Nova grounding
"""
content_str = ""
tools: List[ChatCompletionToolCallChunk] = []
reasoningContentBlocks: Optional[List[BedrockConverseReasoningContentBlock]] = (
None
)
citationsContentBlocks: Optional[List[CitationsContentBlock]] = None
for idx, content in enumerate(content_blocks):
"""
- Content is either a tool response or text
Expand Down Expand Up @@ -1446,8 +1493,13 @@ def _translate_message_content(self, content_blocks: List[ContentBlock]) -> Tupl
if reasoningContentBlocks is None:
reasoningContentBlocks = []
reasoningContentBlocks.append(content["reasoningContent"])
# Handle Nova grounding citations content
if "citationsContent" in content:
if citationsContentBlocks is None:
citationsContentBlocks = []
citationsContentBlocks.append(content["citationsContent"])

return content_str, tools, reasoningContentBlocks
return content_str, tools, reasoningContentBlocks, citationsContentBlocks

def _transform_response(
self,
Expand Down Expand Up @@ -1525,18 +1577,27 @@ def _transform_response(
reasoningContentBlocks: Optional[List[BedrockConverseReasoningContentBlock]] = (
None
)
citationsContentBlocks: Optional[List[CitationsContentBlock]] = None

if message is not None:
(
content_str,
tools,
reasoningContentBlocks,
citationsContentBlocks,
) = self._translate_message_content(message["content"])

# Initialize provider_specific_fields if we have any special content blocks
provider_specific_fields: dict = {}
if reasoningContentBlocks is not None:
provider_specific_fields["reasoningContentBlocks"] = reasoningContentBlocks
if citationsContentBlocks is not None:
provider_specific_fields["citationsContent"] = citationsContentBlocks

if provider_specific_fields:
chat_completion_message["provider_specific_fields"] = provider_specific_fields

if reasoningContentBlocks is not None:
chat_completion_message["provider_specific_fields"] = {
"reasoningContentBlocks": reasoningContentBlocks,
}
chat_completion_message["reasoning_content"] = (
self._transform_reasoning_content(reasoningContentBlocks)
)
Expand Down
5 changes: 5 additions & 0 deletions litellm/llms/bedrock/chat/invoke_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,11 @@ def _handle_converse_delta_event(
reasoning_content = (
"" # set to non-empty string to ensure consistency with Anthropic
)
elif "citationsContent" in delta_obj:
# Handle Nova grounding citations in streaming responses
provider_specific_fields = {
"citationsContent": delta_obj["citationsContent"],
}
return (
text,
tool_use,
Expand Down
85 changes: 85 additions & 0 deletions litellm/types/llms/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,67 @@ class GuardrailConverseContentBlock(TypedDict, total=False):
text: GuardrailConverseTextBlock


class CitationWebLocationBlock(TypedDict, total=False):
"""
Web location block for Nova grounding citations.
Contains the URL and domain from web search results.

Reference: https://docs.aws.amazon.com/nova/latest/userguide/grounding.html
"""

url: str
domain: str


class CitationLocationBlock(TypedDict, total=False):
"""
Location block containing the web location for a citation.
"""

web: CitationWebLocationBlock


class CitationReferenceBlock(TypedDict, total=False):
"""
Citation reference block containing a single citation with its location.

Each citation contains:
- location.web.url: The URL of the source
- location.web.domain: The domain of the source
"""

location: CitationLocationBlock


class CitationsContentBlock(TypedDict, total=False):
"""
Citations content block returned by Nova grounding (web search) tool.

When Nova grounding is enabled via systemTool, the model may return
citationsContent blocks containing web search citation references.

Reference: https://docs.aws.amazon.com/nova/latest/userguide/grounding.html

Example response structure:
{
"citationsContent": {
"citations": [
{
"location": {
"web": {
"url": "https://example.com/article",
"domain": "example.com"
}
}
}
]
}
}
"""

citations: List[CitationReferenceBlock]


class ContentBlock(TypedDict, total=False):
text: str
image: ImageBlock
Expand All @@ -103,6 +164,7 @@ class ContentBlock(TypedDict, total=False):
cachePoint: CachePointBlock
reasoningContent: BedrockConverseReasoningContentBlock
guardContent: GuardrailConverseContentBlock
citationsContent: CitationsContentBlock


class MessageBlock(TypedDict):
Expand Down Expand Up @@ -159,8 +221,24 @@ class ToolSpecBlock(TypedDict, total=False):
description: str


class SystemToolBlock(TypedDict, total=False):
"""
System tool block for Nova grounding and other built-in tools.

Example:
{
"systemTool": {
"name": "nova_grounding"
}
}
"""

name: Required[str]


class ToolBlock(TypedDict, total=False):
toolSpec: Optional[ToolSpecBlock]
systemTool: Optional[SystemToolBlock]
cachePoint: Optional[CachePointBlock]


Expand Down Expand Up @@ -210,11 +288,13 @@ class ContentBlockStartEvent(TypedDict, total=False):
class ContentBlockDeltaEvent(TypedDict, total=False):
"""
Either 'text' or 'toolUse' will be specified for Converse API streaming response.
May also include 'citationsContent' when Nova grounding is enabled.
"""

text: str
toolUse: ToolBlockDeltaEvent
reasoningContent: BedrockConverseReasoningContentBlockDelta
citationsContent: CitationsContentBlock


class PerformanceConfigBlock(TypedDict):
Expand Down Expand Up @@ -879,3 +959,8 @@ class BedrockGetBatchResponse(TypedDict, total=False):
outputDataConfig: BedrockOutputDataConfig
timeoutDurationInHours: Optional[int]
clientRequestToken: Optional[str]

class BedrockToolBlock(TypedDict, total=False):
toolSpec: Optional[ToolSpecBlock]
systemTool: Optional[SystemToolBlock] # For Nova grounding
cachePoint: Optional[CachePointBlock]
Loading
Loading