From 367c48e8156f3f5ec1ad9a96d633a86c242e52e6 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 27 Apr 2026 09:31:47 +0530 Subject: [PATCH] Fix black --- .../prompt_templates/factory.py | 1119 +++++------------ litellm/llms/predibase/chat/transformation.py | 39 +- .../guardrail_hooks/xecguard/xecguard.py | 59 +- 3 files changed, 315 insertions(+), 902 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 1dfa6d11fb5..fbc2c8fdaa7 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -104,9 +104,7 @@ def map_system_message_pt(messages: list) -> list: if i < len(messages) - 1: # Not the last message next_m = messages[i + 1] next_role = next_m["role"] - if ( - next_role == "user" or next_role == "assistant" - ): # Next message is a user or assistant message + if next_role == "user" or next_role == "assistant": # Next message is a user or assistant message # Merge system prompt into the next message next_m["content"] = m["content"] + " " + next_m["content"] elif next_role == "system": # Next message is a system message @@ -186,9 +184,7 @@ def convert_to_ollama_image(openai_image_url: str): ) -def _handle_ollama_system_message( - messages: list, prompt: str, msg_i: int -) -> Tuple[str, int]: +def _handle_ollama_system_message(messages: list, prompt: str, msg_i: int) -> Tuple[str, int]: system_content_str = "" ## MERGE CONSECUTIVE SYSTEM CONTENT ## while msg_i < len(messages) and messages[msg_i]["role"] == "system": @@ -234,9 +230,7 @@ def ollama_pt( if user_content_str: prompt += f"### User:\n{user_content_str}\n\n" - system_content_str, msg_i = _handle_ollama_system_message( - messages, prompt, msg_i - ) + system_content_str, msg_i = _handle_ollama_system_message(messages, prompt, msg_i) if system_content_str: prompt += f"### System:\n{system_content_str}\n\n" @@ -265,9 +259,7 @@ def ollama_pt( ) if ollama_tool_calls: - assistant_content_str += ( - f"Tool Calls: {json.dumps(ollama_tool_calls, indent=2)}" - ) + assistant_content_str += f"Tool Calls: {json.dumps(ollama_tool_calls, indent=2)}" msg_i += 1 @@ -314,11 +306,7 @@ def falcon_instruct_pt(messages): if message["role"] == "system": prompt += message["content"] else: - prompt += ( - message["role"] - + ":" - + message["content"].replace("\r\n", "\n").replace("\n\n", "\n") - ) + prompt += message["role"] + ":" + message["content"].replace("\r\n", "\n").replace("\n\n", "\n") prompt += "\n\n" return prompt @@ -376,9 +364,7 @@ def phind_codellama_pt(messages): return prompt -def _render_chat_template( - env, chat_template: str, bos_token: str, eos_token: str, messages: list -) -> str: +def _render_chat_template(env, chat_template: str, bos_token: str, eos_token: str, messages: list) -> str: """ Shared template rendering logic for both sync and async hf_chat_template @@ -426,9 +412,7 @@ def _render_chat_template( try: for message in messages: if message["role"] == "system": - reformatted_messages.append( - {"role": "user", "content": message["content"]} - ) + reformatted_messages.append({"role": "user", "content": message["content"]}) else: reformatted_messages.append(message) rendered_text = template.render( @@ -443,20 +427,13 @@ def _render_chat_template( new_messages = [] for i in range(len(reformatted_messages) - 1): new_messages.append(reformatted_messages[i]) - if ( - reformatted_messages[i]["role"] - == reformatted_messages[i + 1]["role"] - ): + if reformatted_messages[i]["role"] == reformatted_messages[i + 1]["role"]: if reformatted_messages[i]["role"] == "user": - new_messages.append( - {"role": "assistant", "content": ""} - ) + new_messages.append({"role": "assistant", "content": ""}) else: new_messages.append({"role": "user", "content": ""}) new_messages.append(reformatted_messages[-1]) - rendered_text = template.render( - bos_token=bos_token, eos_token=eos_token, messages=new_messages - ) + rendered_text = template.render(bos_token=bos_token, eos_token=eos_token, messages=new_messages) return rendered_text except Exception as e: @@ -496,12 +473,8 @@ async def _afetch_and_extract_template( and "chat_template" in tokenizer_config["tokenizer"] ): tokenizer_data: dict = tokenizer_config["tokenizer"] # type: ignore - bos_token = _extract_token_value( - token_value=tokenizer_data.get("bos_token") - ) - eos_token = _extract_token_value( - token_value=tokenizer_data.get("eos_token") - ) + bos_token = _extract_token_value(token_value=tokenizer_data.get("bos_token")) + eos_token = _extract_token_value(token_value=tokenizer_data.get("eos_token")) chat_template = tokenizer_data["chat_template"] else: # Fallback: Try to fetch chat template from separate .jinja file @@ -515,12 +488,8 @@ async def _afetch_and_extract_template( and isinstance(tokenizer_config["tokenizer"], dict) ): tokenizer_data: dict = tokenizer_config["tokenizer"] # type: ignore - bos_token = _extract_token_value( - token_value=tokenizer_data.get("bos_token") - ) - eos_token = _extract_token_value( - token_value=tokenizer_data.get("eos_token") - ) + bos_token = _extract_token_value(token_value=tokenizer_data.get("bos_token")) + eos_token = _extract_token_value(token_value=tokenizer_data.get("eos_token")) else: raise Exception("No chat template found") @@ -558,12 +527,8 @@ def _fetch_and_extract_template( and "chat_template" in tokenizer_config["tokenizer"] ): tokenizer_data: dict = tokenizer_config["tokenizer"] # type: ignore - bos_token = _extract_token_value( - token_value=tokenizer_data.get("bos_token") - ) - eos_token = _extract_token_value( - token_value=tokenizer_data.get("eos_token") - ) + bos_token = _extract_token_value(token_value=tokenizer_data.get("bos_token")) + eos_token = _extract_token_value(token_value=tokenizer_data.get("eos_token")) chat_template = tokenizer_data["chat_template"] else: # Fallback: Try to fetch chat template from separate .jinja file @@ -577,21 +542,15 @@ def _fetch_and_extract_template( and isinstance(tokenizer_config["tokenizer"], dict) ): tokenizer_data: dict = tokenizer_config["tokenizer"] # type: ignore - bos_token = _extract_token_value( - token_value=tokenizer_data.get("bos_token") - ) - eos_token = _extract_token_value( - token_value=tokenizer_data.get("eos_token") - ) + bos_token = _extract_token_value(token_value=tokenizer_data.get("bos_token")) + eos_token = _extract_token_value(token_value=tokenizer_data.get("eos_token")) else: raise Exception("No chat template found") return chat_template, bos_token, eos_token # type: ignore -async def ahf_chat_template( - model: str, messages: list, chat_template: Optional[Any] = None -): +async def ahf_chat_template(model: str, messages: list, chat_template: Optional[Any] = None): """HuggingFace chat template (async version)""" from litellm.litellm_core_utils.prompt_templates.huggingface_template_handler import ( _aget_chat_template_file, @@ -646,9 +605,7 @@ def hf_chat_template(model: str, messages: list, chat_template: Optional[Any] = def deepseek_r1_pt(messages): - return hf_chat_template( - model="deepseek-r1/deepseek-r1-7b-instruct", messages=messages - ) + return hf_chat_template(model="deepseek-r1/deepseek-r1-7b-instruct", messages=messages) # Anthropic template @@ -698,9 +655,7 @@ def get_model_info(token, model): model_info = response.json() for m in model_info: if m["name"].lower().strip() == model.strip(): - return m["config"].get("prompt_format", None), m["config"].get( - "chat_template", None - ) + return m["config"].get("prompt_format", None), m["config"].get("chat_template", None) return None, None else: return None, None @@ -779,18 +734,14 @@ def anthropic_pt( AI_PROMPT = "\n\nAssistant: " prompt = "" - for idx, message in enumerate( - messages - ): # needs to start with `\n\nHuman: ` and end with `\n\nAssistant: ` + for idx, message in enumerate(messages): # needs to start with `\n\nHuman: ` and end with `\n\nAssistant: ` if message["role"] == "user": prompt += f"{AnthropicConstants.HUMAN_PROMPT.value}{message['content']}" elif message["role"] == "system": prompt += f"{AnthropicConstants.HUMAN_PROMPT.value}{message['content']}" else: prompt += f"{AnthropicConstants.AI_PROMPT.value}{message['content']}" - if ( - idx == 0 and message["role"] == "assistant" - ): # ensure the prompt always starts with `\n\nHuman: ` + if idx == 0 and message["role"] == "assistant": # ensure the prompt always starts with `\n\nHuman: ` prompt = f"{AnthropicConstants.HUMAN_PROMPT.value}" + prompt if messages[-1]["role"] != "assistant": prompt += f"{AnthropicConstants.AI_PROMPT.value}" @@ -874,9 +825,7 @@ def convert_generic_image_chunk_to_openai_image_obj( return "data:{};{},{}".format(media_type, image_chunk["type"], image_chunk["data"]) -def convert_to_anthropic_image_obj( - openai_image_url: str, format: Optional[str] -) -> GenericImageParsingChunk: +def convert_to_anthropic_image_obj(openai_image_url: str, format: Optional[str]) -> GenericImageParsingChunk: """ Input: "image_url": "data:image/jpeg;base64,{base64_image}", @@ -936,9 +885,7 @@ def create_anthropic_image_param( # as these providers don't support URL sources for images if is_bedrock_invoke or image_url.startswith("http://"): base64_url = convert_url_to_base64(url=image_url) - image_chunk = convert_to_anthropic_image_obj( - openai_image_url=base64_url, format=format - ) + image_chunk = convert_to_anthropic_image_obj(openai_image_url=base64_url, format=format) return AnthropicMessagesImageParam( type="image", source=AnthropicContentParamSource( @@ -958,9 +905,7 @@ def create_anthropic_image_param( ) else: # Convert to base64 for data URIs or other formats - image_chunk = convert_to_anthropic_image_obj( - openai_image_url=image_url, format=format - ) + image_chunk = convert_to_anthropic_image_obj(openai_image_url=image_url, format=format) return AnthropicMessagesImageParam( type="image", source=AnthropicContentParamSource( @@ -1037,19 +982,10 @@ def convert_to_anthropic_tool_invoke_xml(tool_calls: list) -> str: tool_arguments, tool_name=tool_name, context="Anthropic XML tool invoke" ) if isinstance(parsed_args, dict): - parameters = "".join( - f"<{param}>{val}\n" for param, val in parsed_args.items() - ) + parameters = "".join(f"<{param}>{val}\n" for param, val in parsed_args.items()) else: parameters = f"{parsed_args}\n" - invokes += ( - "\n" - f"{tool_name}\n" - "\n" - f"{parameters}" - "\n" - "\n" - ) + invokes += f"\n{tool_name}\n\n{parameters}\n\n" anthropic_tool_invoke = f"\n{invokes}" @@ -1078,14 +1014,8 @@ def anthropic_messages_pt_xml(messages: list): if isinstance(messages[msg_i]["content"], list): for m in messages[msg_i]["content"]: if m.get("type", "") == "image_url": - format = ( - m["image_url"].get("format") - if isinstance(m["image_url"], dict) - else None - ) - image_param = create_anthropic_image_param( - m["image_url"], format=format - ) + format = m["image_url"].get("format") if isinstance(m["image_url"], dict) else None + image_param = create_anthropic_image_param(m["image_url"], format=format) # Convert to dict format for XML version source = image_param["source"] if isinstance(source, dict) and source.get("type") == "url": @@ -1136,12 +1066,8 @@ def anthropic_messages_pt_xml(messages: list): assistant_content = [] ## MERGE CONSECUTIVE ASSISTANT CONTENT ## while msg_i < len(messages) and messages[msg_i]["role"] == "assistant": - assistant_text = ( - messages[msg_i].get("content") or "" - ) # either string or none - if messages[msg_i].get( - "tool_calls", [] - ): # support assistant tool invoke conversion + assistant_text = messages[msg_i].get("content") or "" # either string or none + if messages[msg_i].get("tool_calls", []): # support assistant tool invoke conversion assistant_text += convert_to_anthropic_tool_invoke_xml( # type: ignore messages[msg_i]["tool_calls"] ) @@ -1154,9 +1080,7 @@ def anthropic_messages_pt_xml(messages: list): if not new_messages or new_messages[0]["role"] != "user": if litellm.modify_params: - new_messages.insert( - 0, {"role": "user", "content": [{"type": "text", "text": "."}]} - ) + new_messages.insert(0, {"role": "user", "content": [{"type": "text", "text": "."}]}) else: raise Exception( "Invalid first message. Should always start with 'role'='user' for Anthropic. System prompt is sent separately for Anthropic. set 'litellm.modify_params = True' or 'litellm_settings:modify_params = True' on proxy, to insert a placeholder user message - '.' as the first message, " @@ -1165,9 +1089,7 @@ def anthropic_messages_pt_xml(messages: list): if new_messages[-1]["role"] == "assistant": for content in new_messages[-1]["content"]: if isinstance(content, dict) and content["type"] == "text": - content["text"] = content[ - "text" - ].rstrip() # no trailing whitespace for final assistant message + content["text"] = content["text"].rstrip() # no trailing whitespace for final assistant message return new_messages @@ -1258,9 +1180,7 @@ def _gemini_tool_call_invoke_helper( return function_call -def _encode_tool_call_id_with_signature( - tool_call_id: str, thought_signature: Optional[str] -) -> str: +def _encode_tool_call_id_with_signature(tool_call_id: str, thought_signature: Optional[str]) -> str: """ Embed thought signature into tool call ID for OpenAI client compatibility. @@ -1279,9 +1199,7 @@ def _encode_tool_call_id_with_signature( return tool_call_id -def _get_thought_signature_from_tool( - tool: dict, model: Optional[str] = None -) -> Optional[str]: +def _get_thought_signature_from_tool(tool: dict, model: Optional[str] = None) -> Optional[str]: """Extract thought signature from tool call's provider_specific_fields. If not provided try to extract thought signature from tool call id @@ -1305,10 +1223,7 @@ def _get_thought_signature_from_tool( signature = func_provider_fields.get("thought_signature") if signature: return signature - elif ( - hasattr(function, "provider_specific_fields") - and function.provider_specific_fields - ): + elif hasattr(function, "provider_specific_fields") and function.provider_specific_fields: if isinstance(function.provider_specific_fields, dict): signature = function.provider_specific_fields.get("thought_signature") if signature: @@ -1394,18 +1309,12 @@ def convert_to_gemini_tool_call_invoke( if tool_calls is not None: for idx, tool in enumerate(tool_calls): if "function" in tool: - gemini_function_call: Optional[VertexFunctionCall] = ( - _gemini_tool_call_invoke_helper( - function_call_params=tool["function"] - ) + gemini_function_call: Optional[VertexFunctionCall] = _gemini_tool_call_invoke_helper( + function_call_params=tool["function"] ) if gemini_function_call is not None: - part_dict: VertexPartType = { - "function_call": gemini_function_call - } - thought_signature = _get_thought_signature_from_tool( - dict(tool), model=model - ) + part_dict: VertexPartType = {"function_call": gemini_function_call} + thought_signature = _get_thought_signature_from_tool(dict(tool), model=model) if thought_signature: part_dict["thoughtSignature"] = thought_signature @@ -1417,20 +1326,14 @@ def convert_to_gemini_tool_call_invoke( ) ) elif function_call is not None: - gemini_function_call = _gemini_tool_call_invoke_helper( - function_call_params=function_call - ) + gemini_function_call = _gemini_tool_call_invoke_helper(function_call_params=function_call) if gemini_function_call is not None: - part_dict_function: VertexPartType = { - "function_call": gemini_function_call - } + part_dict_function: VertexPartType = {"function_call": gemini_function_call} # Extract thought signature from function_call's provider_specific_fields thought_signature = None provider_fields = ( - function_call.get("provider_specific_fields") - if isinstance(function_call, dict) - else {} + function_call.get("provider_specific_fields") if isinstance(function_call, dict) else {} ) if isinstance(provider_fields, dict): thought_signature = provider_fields.get("thought_signature") @@ -1440,11 +1343,7 @@ def convert_to_gemini_tool_call_invoke( VertexGeminiConfig, ) - if ( - not thought_signature - and model - and VertexGeminiConfig._is_gemini_3_or_newer(model) - ): + if not thought_signature and model and VertexGeminiConfig._is_gemini_3_or_newer(model): thought_signature = _get_dummy_thought_signature() if thought_signature: @@ -1460,9 +1359,7 @@ def convert_to_gemini_tool_call_invoke( return _parts_list except Exception as e: raise Exception( - "Unable to convert openai tool calls={} to gemini tool calls. Received error={}".format( - message, str(e) - ) + "Unable to convert openai tool calls={} to gemini tool calls. Received error={}".format(message, str(e)) ) @@ -1513,14 +1410,10 @@ def convert_to_gemini_tool_call_result( # noqa: PLR0915 if len(mime_rest) == 2 and mime_rest[0].startswith("image/"): # Strip any extra parameters (e.g. ";charset=UTF-8") from the MIME segment clean_mime = mime_rest[0].split(";")[0].strip() - inline_data_list.append( - BlobType(data=mime_rest[1], mime_type=clean_mime) - ) + inline_data_list.append(BlobType(data=mime_rest[1], mime_type=clean_mime)) content_str = "" except Exception as e: - verbose_logger.warning( - f"Failed to parse data URL in tool response: {e}" - ) + verbose_logger.warning(f"Failed to parse data URL in tool response: {e}") elif isinstance(message["content"], List): content_list = message["content"] for content in content_list: @@ -1539,24 +1432,16 @@ def convert_to_gemini_tool_call_result( # noqa: PLR0915 ) ) except Exception as e: - verbose_logger.warning( - f"Failed to process Anthropic image block in tool response: {e}" - ) + verbose_logger.warning(f"Failed to process Anthropic image block in tool response: {e}") elif content_type in ("input_image", "image_url"): # Extract image for inline_data (for Computer Use screenshots and tool results) image_url_data = content.get("image_url", "") - image_url = ( - image_url_data.get("url", "") - if isinstance(image_url_data, dict) - else image_url_data - ) + image_url = image_url_data.get("url", "") if isinstance(image_url_data, dict) else image_url_data if image_url: # Convert image to base64 blob format for Gemini try: - image_obj = convert_to_anthropic_image_obj( - image_url, format=None - ) + image_obj = convert_to_anthropic_image_obj(image_url, format=None) inline_data_list.append( BlobType( data=image_obj["data"], @@ -1564,9 +1449,7 @@ def convert_to_gemini_tool_call_result( # noqa: PLR0915 ) ) except Exception as e: - verbose_logger.warning( - f"Failed to process image in tool response: {e}" - ) + verbose_logger.warning(f"Failed to process image in tool response: {e}") elif content_type in ("file", "input_file"): # Extract file for inline_data (for tool results with PDF, audio, video, etc.) file_data = content.get("file_data", "") @@ -1575,15 +1458,15 @@ def convert_to_gemini_tool_call_result( # noqa: PLR0915 file_data = ( file_content.get("file_data", "") if isinstance(file_content, dict) - else file_content if isinstance(file_content, str) else "" + else file_content + if isinstance(file_content, str) + else "" ) if file_data: # Convert file to base64 blob format for Gemini try: - file_obj = convert_to_anthropic_image_obj( - file_data, format=None - ) + file_obj = convert_to_anthropic_image_obj(file_data, format=None) inline_data_list.append( BlobType( data=file_obj["data"], @@ -1591,9 +1474,7 @@ def convert_to_gemini_tool_call_result( # noqa: PLR0915 ) ) except Exception as e: - verbose_logger.warning( - f"Failed to process file in tool response: {e}" - ) + verbose_logger.warning(f"Failed to process file in tool response: {e}") name: Optional[str] = message.get("name", "") # type: ignore # Recover name from last message with tool calls @@ -1602,11 +1483,7 @@ def convert_to_gemini_tool_call_result( # noqa: PLR0915 msg_tool_call_id = message.get("tool_call_id", None) for tool in tools: prev_tool_call_id = tool.get("id", None) - if ( - msg_tool_call_id - and prev_tool_call_id - and msg_tool_call_id == prev_tool_call_id - ): + if msg_tool_call_id and prev_tool_call_id and msg_tool_call_id == prev_tool_call_id: name = tool.get("function", {}).get("name", "") if not name: @@ -1636,7 +1513,8 @@ def convert_to_gemini_tool_call_result( # noqa: PLR0915 # We can't determine from openai message format whether it's a successful or # error call result so default to the successful result template _function_response = VertexFunctionResponse( - name=name, response=response_data # type: ignore + name=name, + response=response_data, # type: ignore ) # Create part with function_response, and optionally inline_data for images (Computer Use) @@ -1710,9 +1588,7 @@ def convert_to_anthropic_tool_result( anthropic_content = message["content"] elif isinstance(message["content"], List): content_list = message["content"] - anthropic_content_list: List[ - Union[AnthropicMessagesToolResultContent, AnthropicMessagesImageParam] - ] = [] + anthropic_content_list: List[Union[AnthropicMessagesToolResultContent, AnthropicMessagesImageParam]] = [] for content in content_list: if content["type"] == "text": # Only include cache_control if explicitly set and not None @@ -1726,11 +1602,7 @@ def convert_to_anthropic_tool_result( text_content["cache_control"] = cache_control_value anthropic_content_list.append(text_content) elif content["type"] == "image_url": - format = ( - content["image_url"].get("format") - if isinstance(content["image_url"], dict) - else None - ) + format = content["image_url"].get("format") if isinstance(content["image_url"], dict) else None _anthropic_image_param = create_anthropic_image_param( content["image_url"], format=format, is_bedrock_invoke=force_base64 ) @@ -1738,9 +1610,7 @@ def convert_to_anthropic_tool_result( anthropic_content_element=_anthropic_image_param, original_content_element=content, ) - anthropic_content_list.append( - cast(AnthropicMessagesImageParam, _anthropic_image_param) - ) + anthropic_content_list.append(cast(AnthropicMessagesImageParam, _anthropic_image_param)) anthropic_content = anthropic_content_list anthropic_tool_result: Optional[AnthropicMessagesToolResultParam] = None @@ -1785,9 +1655,7 @@ def convert_function_to_anthropic_tool_invoke( _name = get_attribute_or_key(function_call, "name") or "" _arguments = get_attribute_or_key(function_call, "arguments") - tool_input = parse_tool_call_arguments( - _arguments, tool_name=_name, context="Anthropic function to tool invoke" - ) + tool_input = parse_tool_call_arguments(_arguments, tool_name=_name, context="Anthropic function to tool invoke") anthropic_tool_invoke = [ AnthropicMessagesToolUseParam( @@ -1849,9 +1717,7 @@ def convert_to_anthropic_tool_invoke( Fixes: https://github.com/BerriAI/litellm/issues/17737 """ - anthropic_tool_invoke: List[ - Union[AnthropicMessagesToolUseParam, Dict[str, Any]] - ] = [] + anthropic_tool_invoke: List[Union[AnthropicMessagesToolUseParam, Dict[str, Any]]] = [] for tool in tool_calls: if not get_attribute_or_key(tool, "type") == "function": @@ -1908,9 +1774,7 @@ def convert_to_anthropic_tool_invoke( ) if "cache_control" in _content_element: - _anthropic_tool_use_param["cache_control"] = _content_element[ - "cache_control" - ] + _anthropic_tool_use_param["cache_control"] = _content_element["cache_control"] anthropic_tool_invoke.append(_anthropic_tool_use_param) @@ -1941,15 +1805,15 @@ def _anthropic_content_element_factory( image_chunk: GenericImageParsingChunk, ) -> Union[AnthropicMessagesImageParam, AnthropicMessagesDocumentParam]: if image_chunk["media_type"] == "application/pdf": - _anthropic_content_element: Union[ - AnthropicMessagesDocumentParam, AnthropicMessagesImageParam - ] = AnthropicMessagesDocumentParam( - type="document", - source=AnthropicContentParamSource( - type="base64", - media_type=image_chunk["media_type"], - data=image_chunk["data"], - ), + _anthropic_content_element: Union[AnthropicMessagesDocumentParam, AnthropicMessagesImageParam] = ( + AnthropicMessagesDocumentParam( + type="document", + source=AnthropicContentParamSource( + type="base64", + media_type=image_chunk["media_type"], + data=image_chunk["data"], + ), + ) ) else: _anthropic_content_element = AnthropicMessagesImageParam( @@ -2053,16 +1917,12 @@ def anthropic_process_openai_file_message( ), ) elif content_block_type == "container_upload": - return_block_param = AnthropicMessagesContainerUploadParam( - type="container_upload", file_id=file_id - ) + return_block_param = AnthropicMessagesContainerUploadParam(type="container_upload", file_id=file_id) if return_block_param is None: raise Exception(f"Unable to parse anthropic file message: {message}") return return_block_param - raise Exception( - f"Either file_data or file_id must be present in the file message: {message}" - ) + raise Exception(f"Either file_data or file_id must be present in the file message: {message}") def _sanitize_empty_text_content( @@ -2080,9 +1940,7 @@ def _sanitize_empty_text_content( if isinstance(content, str): if not content or not content.strip(): message = cast(AllMessageValues, dict(message)) # Make a copy - message["content"] = ( - "[System: Empty message content sanitised to satisfy protocol]" - ) + message["content"] = "[System: Empty message content sanitised to satisfy protocol]" verbose_logger.debug( f"_sanitize_empty_text_content: Replaced empty text content in {message.get('role')} message" ) @@ -2233,9 +2091,7 @@ def _is_orphaned_tool_result( break if not found_matching_tool_call: - verbose_logger.debug( - "_is_orphaned_tool_result: Found orphaned tool result with redacted tool_call_id" - ) + verbose_logger.debug("_is_orphaned_tool_result: Found orphaned tool result with redacted tool_call_id") return True return False @@ -2280,9 +2136,7 @@ def sanitize_messages_for_tool_calling( # Case A: Check if assistant message has tool_calls without following tool results if current_message.get("role") == "assistant": - result_messages, messages_consumed = _add_missing_tool_results( - current_message, messages, i - ) + result_messages, messages_consumed = _add_missing_tool_results(current_message, messages, i) # If dummy tool results were added, extend sanitized_messages and skip consumed messages if len(result_messages) > 1: @@ -2337,11 +2191,7 @@ def sanitize_messages_for_tool_calling( seen_in_block = {} if duplicates_to_remove: - sanitized_messages = [ - msg - for idx, msg in enumerate(sanitized_messages) - if idx not in duplicates_to_remove - ] + sanitized_messages = [msg for idx, msg in enumerate(sanitized_messages) if idx not in duplicates_to_remove] return sanitized_messages @@ -2406,25 +2256,17 @@ def anthropic_messages_pt( # noqa: PLR0915 ChatCompletionToolMessage, ChatCompletionUserMessage, ChatCompletionFunctionMessage, - ] = messages[ - msg_i - ] # type: ignore + ] = messages[msg_i] # type: ignore if user_message_types_block["role"] == "user": if isinstance(user_message_types_block["content"], list): for m in user_message_types_block["content"]: if m.get("type", "") == "image_url": m = cast(ChatCompletionImageObject, m) - format = ( - m["image_url"].get("format") - if isinstance(m["image_url"], dict) - else None - ) + format = m["image_url"].get("format") if isinstance(m["image_url"], dict) else None # Convert ChatCompletionImageUrlObject to dict if needed image_url_value = m["image_url"] if isinstance(image_url_value, str): - image_url_input: Union[str, dict[str, Any]] = ( - image_url_value - ) + image_url_input: Union[str, dict[str, Any]] = image_url_value else: # ChatCompletionImageUrlObject or dict case - convert to dict image_url_input = { @@ -2434,11 +2276,7 @@ def anthropic_messages_pt( # noqa: PLR0915 # Bedrock invoke models have format: invoke/... # Vertex AI Anthropic also doesn't support URL sources for images is_bedrock_invoke = model.lower().startswith("invoke/") - is_vertex_ai = ( - llm_provider.startswith("vertex_ai") - if llm_provider - else False - ) + is_vertex_ai = llm_provider.startswith("vertex_ai") if llm_provider else False force_base64 = is_bedrock_invoke or is_vertex_ai _anthropic_content_element = create_anthropic_image_param( image_url_input, @@ -2451,43 +2289,33 @@ def anthropic_messages_pt( # noqa: PLR0915 ) if "cache_control" in _content_element: - _anthropic_content_element["cache_control"] = ( - _content_element["cache_control"] - ) + _anthropic_content_element["cache_control"] = _content_element["cache_control"] user_content.append(_anthropic_content_element) elif m.get("type", "") == "text": m = cast(ChatCompletionTextObject, m) - _anthropic_text_content_element = ( - AnthropicMessagesTextParam( - type="text", - text=m["text"], - ) + _anthropic_text_content_element = AnthropicMessagesTextParam( + type="text", + text=m["text"], ) _content_element = add_cache_control_to_content( anthropic_content_element=_anthropic_text_content_element, original_content_element=dict(m), ) - _content_element = cast( - AnthropicMessagesTextParam, _content_element - ) + _content_element = cast(AnthropicMessagesTextParam, _content_element) user_content.append(_content_element) elif m.get("type", "") == "document": _document_content_element = cast( AnthropicMessagesDocumentParam, add_cache_control_to_content( - anthropic_content_element=cast( - AnthropicMessagesDocumentParam, m - ), + anthropic_content_element=cast(AnthropicMessagesDocumentParam, m), original_content_element=dict(m), ), ) user_content.append(_document_content_element) elif m.get("type", "") == "file": - _file_content_element = ( - anthropic_process_openai_file_message( - cast(ChatCompletionFileObject, m) - ) + _file_content_element = anthropic_process_openai_file_message( + cast(ChatCompletionFileObject, m) ) _file_content_element = add_cache_control_to_content( anthropic_content_element=cast( @@ -2513,21 +2341,14 @@ def anthropic_messages_pt( # noqa: PLR0915 ) if "cache_control" in _content_element: - _anthropic_content_text_element["cache_control"] = ( - _content_element["cache_control"] - ) + _anthropic_content_text_element["cache_control"] = _content_element["cache_control"] user_content.append(_anthropic_content_text_element) - elif ( - user_message_types_block["role"] == "tool" - or user_message_types_block["role"] == "function" - ): + elif user_message_types_block["role"] == "tool" or user_message_types_block["role"] == "function": # OpenAI's tool message content will always be a string user_content.append( - convert_to_anthropic_tool_result( - user_message_types_block, force_base64=force_base64 - ) + convert_to_anthropic_tool_result(user_message_types_block, force_base64=force_base64) ) msg_i += 1 @@ -2544,13 +2365,9 @@ def anthropic_messages_pt( # noqa: PLR0915 assistant_content_block: ChatCompletionAssistantMessage = messages[msg_i] # type: ignore # Extract compaction_blocks from provider_specific_fields and add them first - _provider_specific_fields_raw = assistant_content_block.get( - "provider_specific_fields" - ) + _provider_specific_fields_raw = assistant_content_block.get("provider_specific_fields") if isinstance(_provider_specific_fields_raw, dict): - _compaction_blocks = _provider_specific_fields_raw.get( - "compaction_blocks" - ) + _compaction_blocks = _provider_specific_fields_raw.get("compaction_blocks") if _compaction_blocks and isinstance(_compaction_blocks, list): # Add compaction blocks at the beginning of assistant content : https://platform.claude.com/docs/en/build-with-claude/compaction assistant_content.extend(_compaction_blocks) # type: ignore @@ -2565,25 +2382,15 @@ def anthropic_messages_pt( # noqa: PLR0915 _has_server_tool_calls = False if assistant_tool_calls is not None: for _tc in assistant_tool_calls: - _tc_id = ( - _tc.get("id") - if isinstance(_tc, dict) - else getattr(_tc, "id", None) - ) - if ( - _tc_id - and isinstance(_tc_id, str) - and _tc_id.startswith("srvtoolu_") - ): + _tc_id = _tc.get("id") if isinstance(_tc, dict) else getattr(_tc, "id", None) + if _tc_id and isinstance(_tc_id, str) and _tc_id.startswith("srvtoolu_"): _has_server_tool_calls = True break if ( thinking_blocks is not None and _has_server_tool_calls - and isinstance( - assistant_content_block.get("content", None), (str, type(None)) - ) + and isinstance(assistant_content_block.get("content", None), (str, type(None))) ): # INTERLEAVED MODE: When we have both thinking blocks and server # tool calls (e.g. web search), Anthropic's original response @@ -2593,17 +2400,11 @@ def anthropic_messages_pt( # noqa: PLR0915 # verifies thinking block signatures based on position. # Build the tool call groups (server_tool_use + its result) - _provider_specific_fields_raw_tc = assistant_content_block.get( - "provider_specific_fields" - ) + _provider_specific_fields_raw_tc = assistant_content_block.get("provider_specific_fields") _provider_specific_fields_tc: Dict[str, Any] = {} if isinstance(_provider_specific_fields_raw_tc, dict): - _provider_specific_fields_tc = cast( - Dict[str, Any], _provider_specific_fields_raw_tc - ) - _web_search_results_tc = _provider_specific_fields_tc.get( - "web_search_results" - ) + _provider_specific_fields_tc = cast(Dict[str, Any], _provider_specific_fields_raw_tc) + _web_search_results_tc = _provider_specific_fields_tc.get("web_search_results") _tool_results_tc = _provider_specific_fields_tc.get("tool_results") tool_invoke_results = convert_to_anthropic_tool_invoke( assistant_tool_calls, # type: ignore @@ -2617,11 +2418,7 @@ def anthropic_messages_pt( # noqa: PLR0915 regular_tool_uses: List[Any] = [] _current_group: List[Any] = [] for item in tool_invoke_results: - item_type = ( - item.get("type", "") - if isinstance(item, dict) - else getattr(item, "type", "") - ) + item_type = item.get("type", "") if isinstance(item, dict) else getattr(item, "type", "") if item_type == "server_tool_use": if _current_group: server_tool_groups.append(_current_group) @@ -2648,9 +2445,7 @@ def anthropic_messages_pt( # noqa: PLR0915 original_content_element=dict(assistant_content_block), ) if "cache_control" in _content_element: - _anthropic_text_content_element["cache_control"] = ( - _content_element["cache_control"] - ) + _anthropic_text_content_element["cache_control"] = _content_element["cache_control"] text_element = _anthropic_text_content_element # Interleave: each thinking block precedes its server tool group. @@ -2668,18 +2463,12 @@ def anthropic_messages_pt( # noqa: PLR0915 assistant_content.append(thinking_blocks[tb_idx]) tb_idx += 1 for block in server_tool_groups[grp_idx]: - item_id = ( - block.get("id") - if isinstance(block, dict) - else getattr(block, "id", None) - ) + item_id = block.get("id") if isinstance(block, dict) else getattr(block, "id", None) if item_id and item_id in unique_tool_ids: continue if item_id: unique_tool_ids.add(item_id) - assistant_content.append( - cast(AnthropicMessagesAssistantMessageValues, block) - ) + assistant_content.append(cast(AnthropicMessagesAssistantMessageValues, block)) grp_idx += 1 elif tb_idx < num_tb: # More thinking blocks than tool groups - emit before text @@ -2688,18 +2477,12 @@ def anthropic_messages_pt( # noqa: PLR0915 else: # More tool groups than thinking blocks - emit remaining for block in server_tool_groups[grp_idx]: - item_id = ( - block.get("id") - if isinstance(block, dict) - else getattr(block, "id", None) - ) + item_id = block.get("id") if isinstance(block, dict) else getattr(block, "id", None) if item_id and item_id in unique_tool_ids: continue if item_id: unique_tool_ids.add(item_id) - assistant_content.append( - cast(AnthropicMessagesAssistantMessageValues, block) - ) + assistant_content.append(cast(AnthropicMessagesAssistantMessageValues, block)) grp_idx += 1 # Add text block (if any) @@ -2708,18 +2491,12 @@ def anthropic_messages_pt( # noqa: PLR0915 # Add regular (non-server) tool calls at the end for item in regular_tool_uses: - item_id = ( - item.get("id") - if isinstance(item, dict) - else getattr(item, "id", None) - ) + item_id = item.get("id") if isinstance(item, dict) else getattr(item, "id", None) if item_id and item_id in unique_tool_ids: continue if item_id: unique_tool_ids.add(item_id) - assistant_content.append( - cast(AnthropicMessagesAssistantMessageValues, item) - ) + assistant_content.append(cast(AnthropicMessagesAssistantMessageValues, item)) # Mark tool_calls as already processed so they are not added again assistant_tool_calls = None @@ -2736,9 +2513,7 @@ def anthropic_messages_pt( # noqa: PLR0915 _content_is_list = "content" in assistant_content_block and isinstance( assistant_content_block["content"], list ) - _content_list = ( - assistant_content_block.get("content") if _content_is_list else None - ) + _content_list = assistant_content_block.get("content") if _content_is_list else None _list_has_thinking = False if _content_is_list and _content_list is not None: for _item in _content_list: @@ -2772,17 +2547,13 @@ def anthropic_messages_pt( # noqa: PLR0915 elif ( m.get("type", "") == "text" and len(text_block) > 0 ): # don't pass empty text blocks. anthropic api raises errors. - anthropic_message = AnthropicMessagesTextParam( - type="text", text=text_block - ) + anthropic_message = AnthropicMessagesTextParam(type="text", text=text_block) _cached_message = add_cache_control_to_content( anthropic_content_element=anthropic_message, original_content_element=dict(m), ) - assistant_content.append( - cast(AnthropicMessagesTextParam, _cached_message) - ) + assistant_content.append(cast(AnthropicMessagesTextParam, _cached_message)) # handle server_tool_use blocks (tool search, web search, etc.) # Pass through as-is since these are Anthropic-native content types elif m.get("type", "") == "server_tool_use": @@ -2795,9 +2566,7 @@ def anthropic_messages_pt( # noqa: PLR0915 elif ( "content" in assistant_content_block and isinstance(assistant_content_block["content"], str) - and assistant_content_block[ - "content" - ] # don't pass empty text blocks. anthropic api raises errors. + and assistant_content_block["content"] # don't pass empty text blocks. anthropic api raises errors. ): _anthropic_text_content_element = AnthropicMessagesTextParam( type="text", @@ -2810,29 +2579,19 @@ def anthropic_messages_pt( # noqa: PLR0915 ) if "cache_control" in _content_element: - _anthropic_text_content_element["cache_control"] = ( - _content_element["cache_control"] - ) + _anthropic_text_content_element["cache_control"] = _content_element["cache_control"] assistant_content.append(_anthropic_text_content_element) - if ( - assistant_tool_calls is not None - ): # support assistant tool invoke conversion + if assistant_tool_calls is not None: # support assistant tool invoke conversion # Get web_search_results and tool_results from provider_specific_fields # for server_tool_use reconstruction. # Fixes: https://github.com/BerriAI/litellm/issues/17737 - _provider_specific_fields_raw = assistant_content_block.get( - "provider_specific_fields" - ) + _provider_specific_fields_raw = assistant_content_block.get("provider_specific_fields") _provider_specific_fields: Dict[str, Any] = {} if isinstance(_provider_specific_fields_raw, dict): - _provider_specific_fields = cast( - Dict[str, Any], _provider_specific_fields_raw - ) - _web_search_results = _provider_specific_fields.get( - "web_search_results" - ) + _provider_specific_fields = cast(Dict[str, Any], _provider_specific_fields_raw) + _web_search_results = _provider_specific_fields.get("web_search_results") _tool_results = _provider_specific_fields.get("tool_results") tool_invoke_results = convert_to_anthropic_tool_invoke( assistant_tool_calls, @@ -2844,27 +2603,19 @@ def anthropic_messages_pt( # noqa: PLR0915 # This can happen when merging history that already contains the tool calls for item in tool_invoke_results: # tool_use items are typically dicts, but handle objects just in case - item_id = ( - item.get("id") - if isinstance(item, dict) - else getattr(item, "id", None) - ) + item_id = item.get("id") if isinstance(item, dict) else getattr(item, "id", None) if item_id: if item_id in unique_tool_ids: continue unique_tool_ids.add(item_id) - assistant_content.append( - cast(AnthropicMessagesAssistantMessageValues, item) - ) + assistant_content.append(cast(AnthropicMessagesAssistantMessageValues, item)) assistant_function_call = assistant_content_block.get("function_call") if assistant_function_call is not None: - assistant_content.extend( - convert_function_to_anthropic_tool_invoke(assistant_function_call) - ) + assistant_content.extend(convert_function_to_anthropic_tool_invoke(assistant_function_call)) msg_i += 1 @@ -2884,9 +2635,7 @@ def anthropic_messages_pt( # noqa: PLR0915 elif isinstance(new_messages[-1]["content"], list): for content in new_messages[-1]["content"]: if isinstance(content, dict) and content["type"] == "text": - content["text"] = content[ - "text" - ].rstrip() # no trailing whitespace for final assistant message + content["text"] = content["text"].rstrip() # no trailing whitespace for final assistant message return new_messages @@ -3039,11 +2788,7 @@ def convert_openai_message_to_cohere_tool_result( msg_tool_call_id = message.get("tool_call_id", None) for tool in tools: prev_tool_call_id = tool.get("id", None) - if ( - msg_tool_call_id - and prev_tool_call_id - and msg_tool_call_id == prev_tool_call_id - ): + if msg_tool_call_id and prev_tool_call_id and msg_tool_call_id == prev_tool_call_id: name = tool.get("function", {}).get("name", "") arguments_str = tool.get("function", {}).get("arguments", "") if arguments_str is not None and len(arguments_str) > 0: @@ -3112,14 +2857,8 @@ def convert_to_cohere_tool_invoke(tool_calls: list) -> List[ToolCallObject]: cohere_tool_invoke: List[ToolCallObject] = [ { - "name": get_attribute_or_key( - get_attribute_or_key(tool, "function"), "name" - ), - "parameters": json.loads( - get_attribute_or_key( - get_attribute_or_key(tool, "function"), "arguments" - ) - ), + "name": get_attribute_or_key(get_attribute_or_key(tool, "function"), "name"), + "parameters": json.loads(get_attribute_or_key(get_attribute_or_key(tool, "function"), "arguments")), } for tool in tool_calls if get_attribute_or_key(tool, "type") == "function" @@ -3151,14 +2890,9 @@ def cohere_messages_pt_v2( # noqa: PLR0915 ## GET MOST RECENT MESSAGE most_recent_message = messages.pop(-1) returned_message: Union[ToolResultObject, str] = "" - if ( - most_recent_message.get("role", "") is not None - and most_recent_message["role"] == "tool" - ): + if most_recent_message.get("role", "") is not None and most_recent_message["role"] == "tool": # tool result - returned_message = convert_openai_message_to_cohere_tool_result( - most_recent_message, tool_calls - ) + returned_message = convert_openai_message_to_cohere_tool_result(most_recent_message, tool_calls) else: content: Union[str, List] = most_recent_message.get("content") if isinstance(content, str): @@ -3203,35 +2937,23 @@ def cohere_messages_pt_v2( # noqa: PLR0915 msg_i += 1 if len(system_content) > 0: - new_messages.append( - ChatHistorySystem(role="SYSTEM", message=system_content) - ) + new_messages.append(ChatHistorySystem(role="SYSTEM", message=system_content)) assistant_content: str = "" assistant_tool_calls: List[ToolCallObject] = [] ## MERGE CONSECUTIVE ASSISTANT CONTENT ## while msg_i < len(messages) and messages[msg_i]["role"] == "assistant": - if messages[msg_i].get("content", None) is not None and isinstance( - messages[msg_i]["content"], list - ): + if messages[msg_i].get("content", None) is not None and isinstance(messages[msg_i]["content"], list): for m in messages[msg_i]["content"]: if m.get("type", "") == "text": assistant_content += m["text"] - elif messages[msg_i].get("content") is not None and isinstance( - messages[msg_i]["content"], str - ): + elif messages[msg_i].get("content") is not None and isinstance(messages[msg_i]["content"], str): assistant_content += messages[msg_i]["content"] - if messages[msg_i].get( - "tool_calls", [] - ): # support assistant tool invoke conversion - assistant_tool_calls.extend( - convert_to_cohere_tool_invoke(messages[msg_i]["tool_calls"]) - ) + if messages[msg_i].get("tool_calls", []): # support assistant tool invoke conversion + assistant_tool_calls.extend(convert_to_cohere_tool_invoke(messages[msg_i]["tool_calls"])) if messages[msg_i].get("function_call"): - assistant_tool_calls.extend( - convert_to_cohere_tool_invoke(messages[msg_i]["function_call"]) - ) + assistant_tool_calls.extend(convert_to_cohere_tool_invoke(messages[msg_i]["function_call"])) msg_i += 1 @@ -3247,18 +2969,12 @@ def cohere_messages_pt_v2( # noqa: PLR0915 ## MERGE CONSECUTIVE TOOL RESULTS tool_results: List[ToolResultObject] = [] while msg_i < len(messages) and messages[msg_i]["role"] in tool_message_types: - tool_results.append( - convert_openai_message_to_cohere_tool_result( - messages[msg_i], tool_calls - ) - ) + tool_results.append(convert_openai_message_to_cohere_tool_result(messages[msg_i], tool_calls)) msg_i += 1 if len(tool_results) > 0: - new_messages.append( - ChatHistoryToolResult(role="TOOL", tool_results=tool_results) - ) + new_messages.append(ChatHistoryToolResult(role="TOOL", tool_results=tool_results)) if msg_i == init_msg_i: # prevent infinite loops raise litellm.BadRequestError( @@ -3277,9 +2993,7 @@ def cohere_message_pt(messages: list): for message in messages: # check if this is a tool_call result if message["role"] == "tool": - tool_result = convert_openai_message_to_cohere_tool_result( - message, tool_calls=tool_calls - ) + tool_result = convert_openai_message_to_cohere_tool_result(message, tool_calls=tool_calls) tool_results.append(tool_result) elif message.get("content"): prompt += message["content"] + "\n\n" @@ -3306,9 +3020,7 @@ def amazon_titan_pt( prompt += f"{AmazonTitanConstants.HUMAN_PROMPT.value}{message['content']}" else: prompt += f"{AmazonTitanConstants.AI_PROMPT.value}{message['content']}" - if ( - idx == 0 and message["role"] == "assistant" - ): # ensure the prompt always starts with `\n\nHuman: ` + if idx == 0 and message["role"] == "assistant": # ensure the prompt always starts with `\n\nHuman: ` prompt = f"{AmazonTitanConstants.HUMAN_PROMPT.value}" + prompt if messages[-1]["role"] != "assistant": prompt += f"{AmazonTitanConstants.AI_PROMPT.value}" @@ -3331,9 +3043,7 @@ def _load_image_from_url(image_url): # Check the response's content type to ensure it is an image content_type = response.headers.get("content-type") if not content_type or "image" not in content_type: - raise ValueError( - f"URL does not point to a valid image (content-type: {content_type})" - ) + raise ValueError(f"URL does not point to a valid image (content-type: {content_type})") # Load the image from the response content return Image.open(BytesIO(response.content)) @@ -3384,9 +3094,7 @@ def _gemini_vision_convert_messages(messages: list): try: from PIL import Image except Exception: - raise Exception( - "gemini image conversion failed please run `pip install Pillow`" - ) + raise Exception("gemini image conversion failed please run `pip install Pillow`") if "base64" in img: # Case 2: Base64 image data @@ -3432,9 +3140,7 @@ def gemini_text_image_pt(messages: list): try: pass # type: ignore except Exception: - raise Exception( - "Importing google.generativeai failed, please run 'pip install -q google-generativeai" - ) + raise Exception("Importing google.generativeai failed, please run 'pip install -q google-generativeai") prompt = "" images = [] @@ -3535,9 +3241,7 @@ class BedrockImageProcessor: """Handles both sync and async image processing for Bedrock conversations.""" @staticmethod - def _post_call_image_processing( - response: httpx.Response, image_url: str = "" - ) -> Tuple[str, str]: + def _post_call_image_processing(response: httpx.Response, image_url: str = "") -> Tuple[str, str]: # Check the response's content type to ensure it is an image content_type = response.headers.get("content-type") @@ -3566,9 +3270,7 @@ class BedrockImageProcessor: response = await async_safe_get(client, image_url) response.raise_for_status() # Raise an exception for HTTP errors - return BedrockImageProcessor._post_call_image_processing( - response, image_url - ) + return BedrockImageProcessor._post_call_image_processing(response, image_url) except Exception as e: raise e @@ -3581,9 +3283,7 @@ class BedrockImageProcessor: response = safe_get(client, image_url) response.raise_for_status() # Raise an exception for HTTP errors - return BedrockImageProcessor._post_call_image_processing( - response, image_url - ) + return BedrockImageProcessor._post_call_image_processing(response, image_url) except Exception as e: raise e @@ -3610,22 +3310,14 @@ class BedrockImageProcessor: def _validate_format(mime_type: str, image_format: str) -> str: """Validate image format and mime type for both images and documents.""" - supported_image_formats = ( - litellm.AmazonConverseConfig().get_supported_image_types() - ) - supported_doc_formats = ( - litellm.AmazonConverseConfig().get_supported_document_types() - ) - supported_video_formats = ( - litellm.AmazonConverseConfig().get_supported_video_types() - ) + supported_image_formats = litellm.AmazonConverseConfig().get_supported_image_types() + supported_doc_formats = litellm.AmazonConverseConfig().get_supported_document_types() + supported_video_formats = litellm.AmazonConverseConfig().get_supported_video_types() document_types = ["application", "text"] is_document = any(mime_type.startswith(doc_type) for doc_type in document_types) - supported_image_and_video_formats: List[str] = ( - supported_video_formats + supported_image_formats - ) + supported_image_and_video_formats: List[str] = supported_video_formats + supported_image_formats if is_document: return BedrockImageProcessor._get_document_format( @@ -3663,9 +3355,7 @@ class BedrockImageProcessor: """ valid_extensions: Optional[List[str]] = None potential_extensions = mimetypes.guess_all_extensions(mime_type, strict=False) - valid_extensions = [ - ext[1:] for ext in potential_extensions if ext[1:] in supported_doc_formats - ] + valid_extensions = [ext[1:] for ext in potential_extensions if ext[1:] in supported_doc_formats] # Fallback to types/files.py if mimetypes doesn't return valid extensions ################# @@ -3690,22 +3380,15 @@ class BedrockImageProcessor: return valid_extensions[0] @staticmethod - def _create_bedrock_block( - image_bytes: str, mime_type: str, image_format: str - ) -> BedrockContentBlock: + def _create_bedrock_block(image_bytes: str, mime_type: str, image_format: str) -> BedrockContentBlock: """Create appropriate Bedrock content block based on mime type.""" _blob = BedrockSourceBlock(bytes=image_bytes) document_types = ["application", "text"] is_document = any(mime_type.startswith(doc_type) for doc_type in document_types) - supported_video_formats = ( - litellm.AmazonConverseConfig().get_supported_video_types() - ) - is_video = any( - image_format.startswith(video_type) - for video_type in supported_video_formats - ) + supported_video_formats = litellm.AmazonConverseConfig().get_supported_video_types() + is_video = any(image_format.startswith(video_type) for video_type in supported_video_formats) HASH_SAMPLE_BYTES = 64 * 1024 # hash up to 64 KB of data @@ -3726,9 +3409,7 @@ class BedrockImageProcessor: # --- Compute deterministic hash (sample + total length) --- hasher = hashlib.sha256() hasher.update(sample) - hasher.update( - str(len(normalized)).encode("utf-8") - ) # include full length for uniqueness + hasher.update(str(len(normalized)).encode("utf-8")) # include full length for uniqueness full_hash = hasher.hexdigest() content_hash = full_hash[:16] # short deterministic ID @@ -3743,18 +3424,12 @@ class BedrockImageProcessor: ) ) elif is_video: - return BedrockContentBlock( - video=BedrockVideoBlock(source=_blob, format=image_format) - ) + return BedrockContentBlock(video=BedrockVideoBlock(source=_blob, format=image_format)) else: - return BedrockContentBlock( - image=BedrockImageBlock(source=_blob, format=image_format) - ) + return BedrockContentBlock(image=BedrockImageBlock(source=_blob, format=image_format)) @classmethod - def process_image_sync( - cls, image_url: str, format: Optional[str] = None - ) -> BedrockContentBlock: + def process_image_sync(cls, image_url: str, format: Optional[str] = None) -> BedrockContentBlock: """Synchronous image processing.""" if "base64" in image_url: @@ -3763,9 +3438,7 @@ class BedrockImageProcessor: img_bytes, mime_type = BedrockImageProcessor.get_image_details(image_url) image_format = mime_type.split("/")[1] else: - raise ValueError( - "Unsupported image type. Expected either image url or base64 encoded string" - ) + raise ValueError("Unsupported image type. Expected either image url or base64 encoded string") if format: mime_type = format @@ -3775,22 +3448,16 @@ class BedrockImageProcessor: return cls._create_bedrock_block(img_bytes, mime_type, image_format) @classmethod - async def process_image_async( - cls, image_url: str, format: Optional[str] - ) -> BedrockContentBlock: + async def process_image_async(cls, image_url: str, format: Optional[str]) -> BedrockContentBlock: """Asynchronous image processing.""" if "base64" in image_url: img_bytes, mime_type, image_format = cls._parse_base64_image(image_url) elif "http://" in image_url or "https://" in image_url: - img_bytes, mime_type = await BedrockImageProcessor.get_image_details_async( - image_url - ) + img_bytes, mime_type = await BedrockImageProcessor.get_image_details_async(image_url) image_format = mime_type.split("/")[1] else: - raise ValueError( - "Unsupported image type. Expected either image url or base64 encoded string" - ) + raise ValueError("Unsupported image type. Expected either image url or base64 encoded string") if format: # override with user-defined params mime_type = format @@ -3871,45 +3538,29 @@ def _convert_to_bedrock_tool_call_invoke( if parsed_objects: # First object keeps the original tool id. for obj_idx, obj in enumerate(parsed_objects): - block_id = ( - tool_id if obj_idx == 0 else f"{tool_id}_{obj_idx}" - ) - bedrock_tool = BedrockToolUseBlock( - input=obj, name=name, toolUseId=block_id - ) - _parts_list.append( - BedrockContentBlock(toolUse=bedrock_tool) - ) + block_id = tool_id if obj_idx == 0 else f"{tool_id}_{obj_idx}" + bedrock_tool = BedrockToolUseBlock(input=obj, name=name, toolUseId=block_id) + _parts_list.append(BedrockContentBlock(toolUse=bedrock_tool)) # cache_control applies to the whole original # tool call; attach after the last split block. if tool.get("cache_control", None) is not None: - _parts_list.append( - BedrockContentBlock( - cachePoint=CachePointBlock(type="default") - ) - ) + _parts_list.append(BedrockContentBlock(cachePoint=CachePointBlock(type="default"))) continue # Fallback: no objects extracted — use empty dict. arguments_dict = {} - bedrock_tool = BedrockToolUseBlock( - input=arguments_dict, name=name, toolUseId=tool_id - ) + bedrock_tool = BedrockToolUseBlock(input=arguments_dict, name=name, toolUseId=tool_id) bedrock_content_block = BedrockContentBlock(toolUse=bedrock_tool) _parts_list.append(bedrock_content_block) # Check for cache_control and add a separate cachePoint block if tool.get("cache_control", None) is not None: - cache_point_block = BedrockContentBlock( - cachePoint=CachePointBlock(type="default") - ) + cache_point_block = BedrockContentBlock(cachePoint=CachePointBlock(type="default")) _parts_list.append(cache_point_block) return _parts_list except Exception as e: raise Exception( - "Unable to convert openai tool calls={} to bedrock tool calls. Received error={}".format( - tool_calls, str(e) - ) + "Unable to convert openai tool calls={} to bedrock tool calls. Received error={}".format(tool_calls, str(e)) ) @@ -3958,16 +3609,12 @@ def _convert_to_bedrock_tool_call_result( """ tool_result_content_blocks: List[BedrockToolResultContentBlock] = [] if isinstance(message["content"], str): - tool_result_content_blocks.append( - BedrockToolResultContentBlock(text=message["content"]) - ) + tool_result_content_blocks.append(BedrockToolResultContentBlock(text=message["content"])) elif isinstance(message["content"], List): content_list = message["content"] for content in content_list: if content["type"] == "text": - tool_result_content_blocks.append( - BedrockToolResultContentBlock(text=content["text"]) - ) + tool_result_content_blocks.append(BedrockToolResultContentBlock(text=content["text"])) elif content["type"] == "image_url": format: Optional[str] = None if isinstance(content["image_url"], dict): @@ -3980,9 +3627,7 @@ def _convert_to_bedrock_tool_call_result( format=format, ) if "image" in _block: - tool_result_content_blocks.append( - BedrockToolResultContentBlock(image=_block["image"]) - ) + tool_result_content_blocks.append(BedrockToolResultContentBlock(image=_block["image"])) message.get("name", "") id = str(message.get("tool_call_id", str(uuid.uuid4()))) @@ -4086,9 +3731,7 @@ def _sort_bedrock_assistant_content_blocks( def _insert_assistant_continue_message( messages: List[BedrockMessageBlock], - assistant_continue_message: Optional[ - Union[str, ChatCompletionAssistantMessage] - ] = None, + assistant_continue_message: Optional[Union[str, ChatCompletionAssistantMessage]] = None, ) -> List[BedrockMessageBlock]: """ Add dummy message between user/tool result blocks. @@ -4112,9 +3755,7 @@ def _insert_assistant_continue_message( ) ) elif litellm.modify_params: - text = convert_content_list_to_str( - cast(ChatCompletionAssistantMessage, DEFAULT_ASSISTANT_CONTINUE_MESSAGE) - ) + text = convert_content_list_to_str(cast(ChatCompletionAssistantMessage, DEFAULT_ASSISTANT_CONTINUE_MESSAGE)) messages.append( BedrockMessageBlock( role="assistant", @@ -4139,9 +3780,7 @@ def get_user_message_block_or_continue_message( content_block = message.get("content", None) # Handle None case - if content_block is None or ( - user_continue_message is None and litellm.modify_params is False - ): + if content_block is None or (user_continue_message is None and litellm.modify_params is False): return skip_empty_text_blocks(message=message) # Handle string case @@ -4192,9 +3831,7 @@ def get_user_message_block_or_continue_message( def return_assistant_continue_message( - assistant_continue_message: Optional[ - Union[str, ChatCompletionAssistantMessage] - ] = None, + assistant_continue_message: Optional[Union[str, ChatCompletionAssistantMessage]] = None, ) -> ChatCompletionAssistantMessage: if assistant_continue_message and isinstance(assistant_continue_message, str): return ChatCompletionAssistantMessage( @@ -4217,11 +3854,7 @@ def _skip_empty_dict_blocks(blocks: List[dict]) -> List[dict]: Returns: Filtered list of non-empty text blocks """ - return [ - item - for item in blocks - if not (item.get("type") == "text" and not item.get("text", "").strip()) - ] + return [item for item in blocks if not (item.get("type") == "text" and not item.get("text", "").strip())] @overload @@ -4259,9 +3892,7 @@ def skip_empty_text_blocks( modified_message["content"] = None # user message content cannot be None return modified_message elif isinstance(content_block, list): - modified_content_block = _skip_empty_dict_blocks( - cast(List[dict], content_block) - ) + modified_content_block = _skip_empty_dict_blocks(cast(List[dict], content_block)) # If no content remains and it's an assistant message, set content to None if not modified_content_block and message["role"] == "assistant": @@ -4289,9 +3920,7 @@ def skip_empty_text_blocks( def process_empty_text_blocks( message: ChatCompletionAssistantMessage, - assistant_continue_message: Optional[ - Union[str, ChatCompletionAssistantMessage] - ] = None, + assistant_continue_message: Optional[Union[str, ChatCompletionAssistantMessage]] = None, ) -> ChatCompletionAssistantMessage: modified_content_block = message.get("content", None) ## BASE CASE ## @@ -4299,14 +3928,9 @@ def process_empty_text_blocks( return message # Check if all items are empty text blocks - if all( - item["type"] == "text" and not item["text"].strip() - for item in modified_content_block - ): + if all(item["type"] == "text" and not item["text"].strip() for item in modified_content_block): # Replace with a single continue message - _assistant_continue_message = return_assistant_continue_message( - assistant_continue_message - ) + _assistant_continue_message = return_assistant_continue_message(assistant_continue_message) modified_content_block = [ { "type": "text", @@ -4316,9 +3940,7 @@ def process_empty_text_blocks( else: # Filter out only empty text blocks, keeping non-empty text and other block types modified_content_block = [ - item - for item in modified_content_block - if not (item["type"] == "text" and not item["text"].strip()) + item for item in modified_content_block if not (item["type"] == "text" and not item["text"].strip()) ] modified_message = message.copy() @@ -4331,9 +3953,7 @@ def process_empty_text_blocks( def get_assistant_message_block_or_continue_message( message: ChatCompletionAssistantMessage, - assistant_continue_message: Optional[ - Union[str, ChatCompletionAssistantMessage] - ] = None, + assistant_continue_message: Optional[Union[str, ChatCompletionAssistantMessage]] = None, ) -> ChatCompletionAssistantMessage: """ Returns the user content block @@ -4344,9 +3964,7 @@ def get_assistant_message_block_or_continue_message( content_block = message.get("content", None) # Handle Base case - if content_block is None or ( - assistant_continue_message is None and litellm.modify_params is False - ): + if content_block is None or (assistant_continue_message is None and litellm.modify_params is False): return skip_empty_text_blocks(message=message) # Handle string case @@ -4372,9 +3990,7 @@ def get_assistant_message_block_or_continue_message( } ], """ - return process_empty_text_blocks( - message=message, assistant_continue_message=assistant_continue_message - ) + return process_empty_text_blocks(message=message, assistant_continue_message=assistant_continue_message) # Handle unsupported type raise ValueError(f"Unsupported content type: {type(content_block)}") @@ -4396,8 +4012,7 @@ class BedrockConverseMessagesProcessor: messages.append(DEFAULT_USER_CONTINUE_MESSAGE) else: raise litellm.BadRequestError( - message=BAD_MESSAGE_ERROR_STR - + "bedrock requires at least one non-system message", + message=BAD_MESSAGE_ERROR_STR + "bedrock requires at least one non-system message", model=model, llm_provider=llm_provider, ) @@ -4425,9 +4040,7 @@ class BedrockConverseMessagesProcessor: model: str, llm_provider: str, user_continue_message: Optional[ChatCompletionUserMessage] = None, - assistant_continue_message: Optional[ - Union[str, ChatCompletionAssistantMessage] - ] = None, + assistant_continue_message: Optional[Union[str, ChatCompletionAssistantMessage]] = None, ) -> List[BedrockMessageBlock]: contents: List[BedrockMessageBlock] = [] msg_i = 0 @@ -4454,9 +4067,7 @@ class BedrockConverseMessagesProcessor: _parts.append(_part) elif element["type"] == "guarded_text": # Wrap guarded_text in guardContent block - _part = BedrockContentBlock( - guardContent={"text": {"text": element["text"]}} - ) + _part = BedrockContentBlock(guardContent={"text": {"text": element["text"]}}) _parts.append(_part) elif element["type"] == "image_url": format: Optional[str] = None @@ -4474,25 +4085,17 @@ class BedrockConverseMessagesProcessor: message=cast(ChatCompletionFileObject, element) ) _parts.append(_part) - _cache_point_block = ( - litellm.AmazonConverseConfig()._get_cache_point_block( - message_block=cast( - OpenAIMessageContentListBlock, element - ), - block_type="content_block", - ) + _cache_point_block = litellm.AmazonConverseConfig()._get_cache_point_block( + message_block=cast(OpenAIMessageContentListBlock, element), + block_type="content_block", ) if _cache_point_block is not None: _parts.append(_cache_point_block) user_content.extend(_parts) - elif message_block["content"] and isinstance( - message_block["content"], str - ): + elif message_block["content"] and isinstance(message_block["content"], str): _part = BedrockContentBlock(text=messages[msg_i]["content"]) - _cache_point_block = ( - litellm.AmazonConverseConfig()._get_cache_point_block( - message_block, block_type="content_block" - ) + _cache_point_block = litellm.AmazonConverseConfig()._get_cache_point_block( + message_block, block_type="content_block" ) user_content.append(_part) if _cache_point_block is not None: @@ -4501,27 +4104,20 @@ class BedrockConverseMessagesProcessor: msg_i += 1 if 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 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) contents = _insert_assistant_continue_message( messages=contents, assistant_continue_message=assistant_continue_message, ) - contents.append( - BedrockMessageBlock(role="user", content=user_content) - ) + contents.append(BedrockMessageBlock(role="user", content=user_content)) else: verbose_logger.warning( "Potential consecutive user/tool blocks. Trying to merge. If error occurs, please set a 'assistant_continue_message' or set 'modify_params=True' to insert a dummy assistant message for bedrock calls." ) contents[-1]["content"].extend(user_content) else: - contents.append( - BedrockMessageBlock(role="user", content=user_content) - ) + contents.append(BedrockMessageBlock(role="user", content=user_content)) ## MERGE CONSECUTIVE TOOL CALL MESSAGES ## tool_content: List[BedrockContentBlock] = [] @@ -4539,18 +4135,13 @@ class BedrockConverseMessagesProcessor: # Check for content-level cache_control in list content elif isinstance(current_message.get("content"), list): for content_element in current_message["content"]: - if ( - isinstance(content_element, dict) - and content_element.get("cache_control", None) is not None - ): + if isinstance(content_element, dict) and content_element.get("cache_control", None) is not None: has_cache_control = True break # Add a separate cachePoint block if cache_control is present if has_cache_control: - cache_point_block = BedrockContentBlock( - cachePoint=CachePointBlock(type="default") - ) + cache_point_block = BedrockContentBlock(cachePoint=CachePointBlock(type="default")) tool_content.append(cache_point_block) msg_i += 1 @@ -4559,35 +4150,26 @@ class BedrockConverseMessagesProcessor: if tool_content: # if last message was a 'user' message, then add a blank assistant message (bedrock requires alternating roles) if len(contents) > 0 and contents[-1]["role"] == "user": - if ( - assistant_continue_message is not None - or litellm.modify_params is True - ): + 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) contents = _insert_assistant_continue_message( messages=contents, assistant_continue_message=assistant_continue_message, ) - contents.append( - BedrockMessageBlock(role="user", content=tool_content) - ) + contents.append(BedrockMessageBlock(role="user", content=tool_content)) else: verbose_logger.warning( "Potential consecutive user/tool blocks. Trying to merge. If error occurs, please set a 'assistant_continue_message' or set 'modify_params=True' to insert a dummy assistant message for bedrock calls." ) contents[-1]["content"].extend(tool_content) else: - contents.append( - BedrockMessageBlock(role="user", content=tool_content) - ) + contents.append(BedrockMessageBlock(role="user", content=tool_content)) assistant_content: List[BedrockContentBlock] = [] ## MERGE CONSECUTIVE ASSISTANT CONTENT ## while msg_i < len(messages) and messages[msg_i]["role"] == "assistant": - assistant_message_block = ( - get_assistant_message_block_or_continue_message( - message=messages[msg_i], - assistant_continue_message=assistant_continue_message, - ) + assistant_message_block = get_assistant_message_block_or_continue_message( + message=messages[msg_i], + assistant_continue_message=assistant_continue_message, ) _assistant_content = assistant_message_block.get("content", None) thinking_blocks = cast( @@ -4596,36 +4178,34 @@ class BedrockConverseMessagesProcessor: ) if thinking_blocks is not None: - converted_thinking_blocks = BedrockConverseMessagesProcessor.translate_thinking_blocks_to_reasoning_content_blocks( - thinking_blocks + converted_thinking_blocks = ( + BedrockConverseMessagesProcessor.translate_thinking_blocks_to_reasoning_content_blocks( + thinking_blocks + ) ) assistant_content = BedrockConverseMessagesProcessor.add_thinking_blocks_to_assistant_content( thinking_blocks=converted_thinking_blocks, assistant_parts=assistant_content, ) - if _assistant_content is not None and isinstance( - _assistant_content, list - ): + if _assistant_content is not None and isinstance(_assistant_content, list): assistants_parts: List[BedrockContentBlock] = [] for element in _assistant_content: if isinstance(element, dict): if element["type"] == "thinking": thinking_block = BedrockConverseMessagesProcessor.translate_thinking_blocks_to_reasoning_content_blocks( - thinking_blocks=[ - cast(ChatCompletionThinkingBlock, element) - ] + thinking_blocks=[cast(ChatCompletionThinkingBlock, element)] ) - assistants_parts = BedrockConverseMessagesProcessor.add_thinking_blocks_to_assistant_content( - thinking_blocks=thinking_block, - assistant_parts=assistants_parts, + assistants_parts = ( + BedrockConverseMessagesProcessor.add_thinking_blocks_to_assistant_content( + thinking_blocks=thinking_block, + assistant_parts=assistants_parts, + ) ) elif element["type"] == "text": # Skip completely empty strings to avoid blank content blocks if element.get("text", "").strip(): - assistants_part = BedrockContentBlock( - text=element["text"] - ) + assistants_part = BedrockContentBlock(text=element["text"]) assistants_parts.append(assistants_part) elif element["type"] == "image_url": if isinstance(element["image_url"], dict): @@ -4637,54 +4217,36 @@ class BedrockConverseMessagesProcessor: ) assistants_parts.append(assistants_part) # Add cache point block for assistant content elements - _cache_point_block = ( - litellm.AmazonConverseConfig()._get_cache_point_block( - message_block=cast( - OpenAIMessageContentListBlock, element - ), - block_type="content_block", - ) + _cache_point_block = litellm.AmazonConverseConfig()._get_cache_point_block( + message_block=cast(OpenAIMessageContentListBlock, element), + block_type="content_block", ) if _cache_point_block is not None: assistants_parts.append(_cache_point_block) assistant_content.extend(assistants_parts) - elif _assistant_content is not None and isinstance( - _assistant_content, str - ): + elif _assistant_content is not None and isinstance(_assistant_content, str): # Skip completely empty strings to avoid blank content blocks if _assistant_content.strip(): - assistant_content.append( - BedrockContentBlock(text=_assistant_content) - ) + assistant_content.append(BedrockContentBlock(text=_assistant_content)) # If content is empty/whitespace, skip it (don't add a placeholder) # Add cache point block for assistant string content - _cache_point_block = ( - litellm.AmazonConverseConfig()._get_cache_point_block( - assistant_message_block, block_type="content_block" - ) + _cache_point_block = litellm.AmazonConverseConfig()._get_cache_point_block( + assistant_message_block, block_type="content_block" ) if _cache_point_block is not None: assistant_content.append(_cache_point_block) _tool_calls = assistant_message_block.get("tool_calls", []) if _tool_calls: - assistant_content.extend( - _convert_to_bedrock_tool_call_invoke(_tool_calls) - ) + assistant_content.extend(_convert_to_bedrock_tool_call_invoke(_tool_calls)) msg_i += 1 - assistant_content = _deduplicate_bedrock_content_blocks( - assistant_content, "toolUse" - ) - assistant_content = _sort_bedrock_assistant_content_blocks( - assistant_content - ) + assistant_content = _deduplicate_bedrock_content_blocks(assistant_content, "toolUse") + assistant_content = _sort_bedrock_assistant_content_blocks(assistant_content) if assistant_content: - contents.append( - BedrockMessageBlock(role="assistant", content=assistant_content) - ) + contents.append(BedrockMessageBlock(role="assistant", content=assistant_content)) if msg_i == init_msg_i: # prevent infinite loops raise litellm.BadRequestError( @@ -4711,9 +4273,7 @@ class BedrockConverseMessagesProcessor: reasoning_content_block = BedrockConverseReasoningContentBlock( reasoningText=text_block, ) - bedrock_content_block = BedrockContentBlock( - reasoningContent=reasoning_content_block - ) + bedrock_content_block = BedrockContentBlock(reasoningContent=reasoning_content_block) reasoning_content_blocks.append(bedrock_content_block) return reasoning_content_blocks @@ -4725,16 +4285,12 @@ class BedrockConverseMessagesProcessor: if file_data is None and file_id is None: raise litellm.BadRequestError( - message="file_data and file_id cannot both be None. Got={}".format( - message - ), + message="file_data and file_id cannot both be None. Got={}".format(message), model="", llm_provider="bedrock", ) format = file_message.get("format") - return BedrockImageProcessor.process_image_sync( - image_url=cast(str, file_id or file_data), format=format - ) + return BedrockImageProcessor.process_image_sync(image_url=cast(str, file_id or file_data), format=format) @staticmethod async def _async_process_file_message( @@ -4746,15 +4302,11 @@ class BedrockConverseMessagesProcessor: format = file_message.get("format") if file_data is None and file_id is None: raise litellm.BadRequestError( - message="file_data and file_id cannot both be None. Got={}".format( - message - ), + message="file_data and file_id cannot both be None. Got={}".format(message), model="", llm_provider="bedrock", ) - return await BedrockImageProcessor.process_image_async( - image_url=cast(str, file_id or file_data), format=format - ) + return await BedrockImageProcessor.process_image_async(image_url=cast(str, file_id or file_data), format=format) @staticmethod def add_thinking_blocks_to_assistant_content( @@ -4772,11 +4324,7 @@ class BedrockConverseMessagesProcessor: filtered_thinking_blocks = [] for block in thinking_blocks: reasoning_content = block.get("reasoningContent", None) - reasoning_text = ( - reasoning_content.get("reasoningText", None) - if reasoning_content is not None - else None - ) + reasoning_text = reasoning_content.get("reasoningText", None) if reasoning_content is not None else None if reasoning_text and not reasoning_text.get("signature"): reasoning_text_text = reasoning_text["text"] assistants_part = BedrockContentBlock(text=reasoning_text_text) @@ -4793,9 +4341,7 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 model: str, llm_provider: str, user_continue_message: Optional[ChatCompletionUserMessage] = None, - assistant_continue_message: Optional[ - Union[str, ChatCompletionAssistantMessage] - ] = None, + assistant_continue_message: Optional[Union[str, ChatCompletionAssistantMessage]] = None, ) -> List[BedrockMessageBlock]: """ Converts given messages from OpenAI format to Bedrock format @@ -4830,9 +4376,7 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 _parts.append(_part) elif element["type"] == "guarded_text": # Wrap guarded_text in guardContent block - _part = BedrockContentBlock( - guardContent={"text": {"text": element["text"]}} - ) + _part = BedrockContentBlock(guardContent={"text": {"text": element["text"]}}) _parts.append(_part) elif element["type"] == "image_url": format: Optional[str] = None @@ -4847,29 +4391,21 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 ) _parts.append(_part) # type: ignore elif element["type"] == "file": - _part = ( - BedrockConverseMessagesProcessor._process_file_message( - message=cast(ChatCompletionFileObject, element) - ) + _part = BedrockConverseMessagesProcessor._process_file_message( + message=cast(ChatCompletionFileObject, element) ) _parts.append(_part) - _cache_point_block = ( - litellm.AmazonConverseConfig()._get_cache_point_block( - message_block=cast( - OpenAIMessageContentListBlock, element - ), - block_type="content_block", - ) + _cache_point_block = litellm.AmazonConverseConfig()._get_cache_point_block( + message_block=cast(OpenAIMessageContentListBlock, element), + block_type="content_block", ) if _cache_point_block is not None: _parts.append(_cache_point_block) user_content.extend(_parts) elif message_block["content"] and isinstance(message_block["content"], str): _part = BedrockContentBlock(text=messages[msg_i]["content"]) - _cache_point_block = ( - litellm.AmazonConverseConfig()._get_cache_point_block( - message_block, block_type="content_block" - ) + _cache_point_block = litellm.AmazonConverseConfig()._get_cache_point_block( + message_block, block_type="content_block" ) user_content.append(_part) if _cache_point_block is not None: @@ -4878,18 +4414,13 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 msg_i += 1 if 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 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) contents = _insert_assistant_continue_message( messages=contents, assistant_continue_message=assistant_continue_message, ) - contents.append( - BedrockMessageBlock(role="user", content=user_content) - ) + contents.append(BedrockMessageBlock(role="user", content=user_content)) else: verbose_logger.warning( "Potential consecutive user/tool blocks. Trying to merge. If error occurs, please set a 'assistant_continue_message' or set 'modify_params=True' to insert a dummy assistant message for bedrock calls." @@ -4916,18 +4447,13 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 # Check for content-level cache_control in list content elif isinstance(current_message.get("content"), list): for content_element in current_message["content"]: - if ( - isinstance(content_element, dict) - and content_element.get("cache_control", None) is not None - ): + if isinstance(content_element, dict) and content_element.get("cache_control", None) is not None: has_cache_control = True break # Add a separate cachePoint block if cache_control is present if has_cache_control: - cache_point_block = BedrockContentBlock( - cachePoint=CachePointBlock(type="default") - ) + cache_point_block = BedrockContentBlock(cachePoint=CachePointBlock(type="default")) tool_content.append(cache_point_block) msg_i += 1 @@ -4936,18 +4462,13 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 if tool_content: # if last message was a 'user' message, then add a blank assistant message (bedrock requires alternating roles) if len(contents) > 0 and contents[-1]["role"] == "user": - if ( - assistant_continue_message is not None - or litellm.modify_params is True - ): + 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) contents = _insert_assistant_continue_message( messages=contents, assistant_continue_message=assistant_continue_message, ) - contents.append( - BedrockMessageBlock(role="user", content=tool_content) - ) + contents.append(BedrockMessageBlock(role="user", content=tool_content)) else: verbose_logger.warning( "Potential consecutive user/tool blocks. Trying to merge. If error occurs, please set a 'assistant_continue_message' or set 'modify_params=True' to insert a dummy assistant message for bedrock calls." @@ -4969,8 +4490,10 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 ) if thinking_blocks is not None: - converted_thinking_blocks = BedrockConverseMessagesProcessor.translate_thinking_blocks_to_reasoning_content_blocks( - thinking_blocks + converted_thinking_blocks = ( + BedrockConverseMessagesProcessor.translate_thinking_blocks_to_reasoning_content_blocks( + thinking_blocks + ) ) assistant_content = BedrockConverseMessagesProcessor.add_thinking_blocks_to_assistant_content( thinking_blocks=converted_thinking_blocks, @@ -4982,22 +4505,22 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 for element in _assistant_content: if isinstance(element, dict): if element["type"] == "thinking": - thinking_block = BedrockConverseMessagesProcessor.translate_thinking_blocks_to_reasoning_content_blocks( - thinking_blocks=[ - cast(ChatCompletionThinkingBlock, element) - ] + thinking_block = ( + BedrockConverseMessagesProcessor.translate_thinking_blocks_to_reasoning_content_blocks( + thinking_blocks=[cast(ChatCompletionThinkingBlock, element)] + ) ) - assistants_parts = BedrockConverseMessagesProcessor.add_thinking_blocks_to_assistant_content( - thinking_blocks=thinking_block, - assistant_parts=assistants_parts, + assistants_parts = ( + BedrockConverseMessagesProcessor.add_thinking_blocks_to_assistant_content( + thinking_blocks=thinking_block, + assistant_parts=assistants_parts, + ) ) elif element["type"] == "text": # AWS Bedrock doesn't allow empty or whitespace-only text content # Skip completely empty strings to avoid blank content blocks if element.get("text", "").strip(): - assistants_part = BedrockContentBlock( - text=element["text"] - ) + assistants_part = BedrockContentBlock(text=element["text"]) assistants_parts.append(assistants_part) elif element["type"] == "image_url": if isinstance(element["image_url"], dict): @@ -5009,13 +4532,9 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 ) assistants_parts.append(assistants_part) # Add cache point block for assistant content elements - _cache_point_block = ( - litellm.AmazonConverseConfig()._get_cache_point_block( - message_block=cast( - OpenAIMessageContentListBlock, element - ), - block_type="content_block", - ) + _cache_point_block = litellm.AmazonConverseConfig()._get_cache_point_block( + message_block=cast(OpenAIMessageContentListBlock, element), + block_type="content_block", ) if _cache_point_block is not None: assistants_parts.append(_cache_point_block) @@ -5023,34 +4542,24 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 elif _assistant_content is not None and isinstance(_assistant_content, str): # Skip completely empty strings to avoid blank content blocks if _assistant_content.strip(): - assistant_content.append( - BedrockContentBlock(text=_assistant_content) - ) + assistant_content.append(BedrockContentBlock(text=_assistant_content)) # Add cache point block for assistant string content - _cache_point_block = ( - litellm.AmazonConverseConfig()._get_cache_point_block( - assistant_message_block, block_type="content_block" - ) + _cache_point_block = litellm.AmazonConverseConfig()._get_cache_point_block( + assistant_message_block, block_type="content_block" ) if _cache_point_block is not None: assistant_content.append(_cache_point_block) _tool_calls = assistant_message_block.get("tool_calls", []) if _tool_calls: - assistant_content.extend( - _convert_to_bedrock_tool_call_invoke(_tool_calls) - ) + assistant_content.extend(_convert_to_bedrock_tool_call_invoke(_tool_calls)) msg_i += 1 - assistant_content = _deduplicate_bedrock_content_blocks( - assistant_content, "toolUse" - ) + assistant_content = _deduplicate_bedrock_content_blocks(assistant_content, "toolUse") assistant_content = _sort_bedrock_assistant_content_blocks(assistant_content) if assistant_content: - contents.append( - BedrockMessageBlock(role="assistant", content=assistant_content) - ) + contents.append(BedrockMessageBlock(role="assistant", content=assistant_content)) if msg_i == init_msg_i: # prevent infinite loops raise litellm.BadRequestError( @@ -5090,16 +4599,12 @@ def make_valid_bedrock_tool_name(input_tool_name: str) -> str: if input_tool_name != valid_string: # passed tool name was formatted to become valid # store it internally so we can use for the response - litellm.bedrock_tool_name_mappings.set_cache( - key=valid_string, value=input_tool_name - ) + litellm.bedrock_tool_name_mappings.set_cache(key=valid_string, value=input_tool_name) return valid_string -def add_cache_point_tool_block( - tool: dict, model: Optional[str] = None -) -> Optional[BedrockToolBlock]: +def add_cache_point_tool_block(tool: dict, model: Optional[str] = None) -> Optional[BedrockToolBlock]: from litellm.llms.bedrock.common_utils import is_claude_4_5_on_bedrock cache_control = tool.get("cache_control", None) @@ -5109,11 +4614,7 @@ def add_cache_point_tool_block( cache_point_block: CachePointBlock = {"type": "default"} if isinstance(cache_control, dict) and "ttl" in cache_control: ttl = cache_control["ttl"] - if ( - ttl in ["5m", "1h"] - and model is not None - and is_claude_4_5_on_bedrock(model) - ): + if ttl in ["5m", "1h"] and model is not None and is_claude_4_5_on_bedrock(model): cache_point_block["ttl"] = ttl return {"cachePoint": cache_point_block} return None @@ -5140,14 +4641,10 @@ def _is_bedrock_tool_block(tool: dict) -> bool: >>> _is_bedrock_tool_block({"type": "function", "function": {...}}) False """ - return isinstance(tool, dict) and ( - "systemTool" in tool or "toolSpec" in tool or "cachePoint" in tool - ) + return isinstance(tool, dict) and ("systemTool" in tool or "toolSpec" in tool or "cachePoint" in tool) -def _bedrock_tools_pt( - tools: List, model: Optional[str] = None -) -> List[BedrockToolBlock]: +def _bedrock_tools_pt(tools: List, model: Optional[str] = None) -> List[BedrockToolBlock]: """ OpenAI tools looks like: tools = [ @@ -5201,9 +4698,7 @@ def _bedrock_tools_pt( ) from litellm.litellm_core_utils.prompt_templates.common_utils import unpack_defs - _valid_json_schema_root_types = frozenset( - ("array", "boolean", "integer", "null", "number", "object", "string") - ) + _valid_json_schema_root_types = frozenset(("array", "boolean", "integer", "null", "number", "object", "string")) tool_block_list: List[BedrockToolBlock] = [] for tool_idx, tool in enumerate(tools): # Check if tool is already a BedrockToolBlock (e.g., systemTool for Nova grounding) @@ -5214,17 +4709,11 @@ def _bedrock_tools_pt( # OpenAI function tools, or Anthropic Messages / Claude Code ({name, input_schema, type, ...}) if isinstance(tool, dict) and "input_schema" in tool and "function" not in tool: - parameters = copy.deepcopy( - tool.get("input_schema") or {"type": "object", "properties": {}} - ) + parameters = copy.deepcopy(tool.get("input_schema") or {"type": "object", "properties": {}}) raw_name = tool.get("name", "") or "" _tool_description = tool.get("description", None) else: - parameters = copy.deepcopy( - tool.get("function", {}).get( - "parameters", {"type": "object", "properties": {}} - ) - ) + parameters = copy.deepcopy(tool.get("function", {}).get("parameters", {"type": "object", "properties": {}})) raw_name = tool.get("function", {}).get("name", "") or "" _tool_description = tool.get("function", {}).get("description", None) @@ -5256,9 +4745,7 @@ def _bedrock_tools_pt( required=parameters.get("required", []), ) ) - tool_spec = BedrockToolSpecBlock( - inputSchema=tool_input_schema, name=name, description=description - ) + tool_spec = BedrockToolSpecBlock(inputSchema=tool_input_schema, name=name, description=description) tool_block = BedrockToolBlock(toolSpec=tool_spec) tool_block_list.append(tool_block) @@ -5282,9 +4769,7 @@ def function_call_prompt(messages: list, functions: list): if isinstance(message["content"], str): message["content"] += f""" {function_prompt}""" else: - message["content"].append( - {"type": "text", "text": f""" {function_prompt}"""} - ) + message["content"].append({"type": "text", "text": f""" {function_prompt}"""}) function_added_to_prompt = True if function_added_to_prompt is False: @@ -5300,9 +4785,7 @@ def response_schema_prompt(model: str, response_schema: dict) -> str: Returns the prompt str that's passed to the model as a user message """ custom_prompt_details: Optional[dict] = None - response_schema_as_message = [ - {"role": "user", "content": "{}".format(response_schema)} - ] + response_schema_as_message = [{"role": "user", "content": "{}".format(response_schema)}] if f"{model}/response_schema_prompt" in litellm.custom_prompt_dict: custom_prompt_details = litellm.custom_prompt_dict[ f"{model}/response_schema_prompt" @@ -5355,23 +4838,17 @@ def custom_prompt( bos_open = True pre_message_str = ( - role_dict[role]["pre_message"] - if role in role_dict and "pre_message" in role_dict[role] - else "" + role_dict[role]["pre_message"] if role in role_dict and "pre_message" in role_dict[role] else "" ) post_message_str = ( - role_dict[role]["post_message"] - if role in role_dict and "post_message" in role_dict[role] - else "" + role_dict[role]["post_message"] if role in role_dict and "post_message" in role_dict[role] else "" ) if isinstance(message["content"], str): prompt += pre_message_str + message["content"] + post_message_str elif isinstance(message["content"], list): text_str = "" for content in message["content"]: - if content.get("text", None) is not None and isinstance( - content["text"], str - ): + if content.get("text", None) is not None and isinstance(content["text"], str): text_str += content["text"] prompt += pre_message_str + text_str + post_message_str @@ -5396,9 +4873,7 @@ def prompt_factory( elif custom_llm_provider == "anthropic": if litellm.AnthropicTextConfig._is_anthropic_text_model(model): return anthropic_pt(messages=messages) - return anthropic_messages_pt( - messages=messages, model=model, llm_provider=custom_llm_provider - ) + return anthropic_messages_pt(messages=messages, model=model, llm_provider=custom_llm_provider) elif custom_llm_provider == "anthropic_xml": return anthropic_messages_pt_xml(messages=messages) elif custom_llm_provider == "gemini": @@ -5411,9 +4886,7 @@ def prompt_factory( else: return gemini_text_image_pt(messages=messages) elif custom_llm_provider == "mistral": - return litellm.MistralConfig()._transform_messages( - messages=messages, model=model - ) + return litellm.MistralConfig()._transform_messages(messages=messages, model=model) elif custom_llm_provider == "bedrock": if "amazon.titan-text" in model: return amazon_titan_pt(messages=messages) @@ -5445,16 +4918,12 @@ def prompt_factory( elif custom_llm_provider == "watsonx": from litellm.llms.watsonx.chat.transformation import IBMWatsonXChatConfig - return IBMWatsonXChatConfig.apply_prompt_template( - model=model, messages=messages - ) + return IBMWatsonXChatConfig.apply_prompt_template(model=model, messages=messages) try: if "meta-llama/llama-2" in model and "chat" in model: return llama_2_chat_pt(messages=messages) - elif ( - "meta-llama/llama-3" in model or "meta-llama-3" in model - ) and "instruct" in model: + elif ("meta-llama/llama-3" in model or "meta-llama-3" in model) and "instruct" in model: return hf_chat_template( model="meta-llama/Meta-Llama-3-8B-Instruct", messages=messages, @@ -5478,9 +4947,7 @@ def prompt_factory( return wizardcoder_pt(messages=messages) elif "phind/phind-codellama" in model: return phind_codellama_pt(messages=messages) - elif "togethercomputer/llama-2" in model and ( - "instruct" in model or "chat" in model - ): + elif "togethercomputer/llama-2" in model and ("instruct" in model or "chat" in model): return llama_2_chat_pt(messages=messages) elif model in [ "gryphe/mythomax-l2-13b", @@ -5490,9 +4957,7 @@ def prompt_factory( return alpaca_pt(messages=messages) elif model.strip() == "meta-llama/llamaguard-7b": chat_template = "{% if messages|length % 2 == 0 %}{% set role = 'Agent' %}{% else %}{% set role = 'User' %}{% endif %}{{ \"[INST] Task: Check if there is unsafe content in '\" + role + \"' messages in conversations according our safety policy with the below categories.\n\n\nO1: Violence and Hate. \nShould not\n- Help people plan or engage in violence.\n- Encourage people to engage in violence.\n- Express hateful, derogatory, or demeaning sentiments against people based on sensitive personal characteristics like their race, color, religion, national origin, sexual orientation, gender, gender identity, or disability.\n- Encourage discrimination against people based on those characteristics and should not use slurs or create insults based on those characteristics.\nCan\n- Provide information on violence and discrimination.\n- Discuss the topics of hate and violence, and can discuss historical events involving violence.\nO2: Sexual Content.\nShould not\n- Engage in sexually explicit (i.e., erotic) conversations and should not encourage people to engage in sexual activities.\nCan\n- Discuss topics related to sexuality and sexual education.\n- Provide general information regarding sexual activities.\n- Provide guidance on topics related to sexual health.\nO3: Criminal Planning.\nShould not\n- Help people plan or engage in criminal activities like theft, kidnapping, or financial crimes, especially when the person indicates that they may act on the information provided to them.\n- Encourage people to engage in criminal activities or condone criminal behavior.\nCan\n- Help people understand how crimes are committed, provided that the response is not so detailed that it could be used to provide a roadmap for future crimes.\n- Help people understand how systems work that are used to prevent crimes, provided those descriptions do not provide detailed information on how to defeat those systems.\nO4: Guns and Illegal Weapons.\nShould not\n- Assist in the planning or execution of firearm or illegal weapons crimes, especially when the person indicates that they may act on the information provided to them.\n- Encourage people to engage in firearm or illegal weapons crimes or condone such behavior.\nCan\n- Discuss firearms and the arguments for and against firearm ownership.\nO5: Regulated or Controlled Substances.\nShould not\n- Encourage or assist people in the trafficking, consumption, or use of regulated or controlled substances, including but not limited to illegal drugs, tobacco, alcohol, and cannabis.\n- Assist or encourage people to create such substances when it is illegal to do so.\nCan\n- Discuss the use of regulated or controlled substances, and can provide general information on those substances (ex: what their chemical composition is and what their effects are).\n- Provide information on how those substances are manufactured, provided that the information is not so detailed as to provide step-by-step instructions for their creation.\nO6: Self-Harm.\nShould not\n- Encourage people to harm themselves.\n- Romanticize or condone self-harm or suicide.\n- Provide information regarding the methods of suicide or self-harm.\n- Provide any information or advice, other than information regarding appropriate health resources, to people who express or imply that they wish to harm themselves.\nCan\n- Provide general information regarding self-harm and suicide (ex: statistics, the mental health challenges faced by those who take these actions).\nShould\n- Provide information regarding appropriate health resources to people who imply or express the desire to harm themselves.\n\n\n\n\n\" }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% set content = message['content'] %}{% if message['role'] == 'user' %}{% set role = 'User' %}{% elif message['role'] == 'assistant' %}{% set role = 'Agent' %}{% endif %}{{ role + ': ' + content.strip() + '\n\n' }}{% endfor %}{{ \"\n\nProvide your safety assessment for \" + role + \" in the above conversation:\n- First line must read 'safe' or 'unsafe'.\n- If unsafe, a second line must include a comma-separated list of violated categories. [/INST]\" }}" - return hf_chat_template( - model=model, messages=messages, chat_template=chat_template - ) + return hf_chat_template(model=model, messages=messages, chat_template=chat_template) else: return hf_chat_template(original_model_name, messages) except Exception: diff --git a/litellm/llms/predibase/chat/transformation.py b/litellm/llms/predibase/chat/transformation.py index 8a2652adb64..09f54a59ff9 100644 --- a/litellm/llms/predibase/chat/transformation.py +++ b/litellm/llms/predibase/chat/transformation.py @@ -35,13 +35,9 @@ class PredibaseConfig(BaseConfig): best_of: Optional[int] = None decoder_input_details: Optional[bool] = None details: bool = True # enables returning logprobs + best of - max_new_tokens: int = ( - DEFAULT_MAX_TOKENS # openai default - requests hang if max_new_tokens not given - ) + max_new_tokens: int = DEFAULT_MAX_TOKENS # openai default - requests hang if max_new_tokens not given repetition_penalty: Optional[float] = None - return_full_text: Optional[bool] = ( - False # by default don't return the input as part of the output - ) + return_full_text: Optional[bool] = False # by default don't return the input as part of the output seed: Optional[int] = None stop: Optional[List[str]] = None temperature: Optional[float] = None @@ -108,9 +104,7 @@ class PredibaseConfig(BaseConfig): optional_params["top_p"] = value if param == "n": optional_params["best_of"] = value - optional_params["do_sample"] = ( - True # Need to sample if you want best of for hf inference endpoints - ) + optional_params["do_sample"] = True # Need to sample if you want best of for hf inference endpoints if param == "stream": optional_params["stream"] = value if param == "stop": @@ -176,9 +170,7 @@ class PredibaseConfig(BaseConfig): ) if "details" in completion_response and "tokens" in completion_response["details"]: - model_response.choices[0].finish_reason = map_finish_reason( - completion_response["details"]["finish_reason"] - ) + model_response.choices[0].finish_reason = map_finish_reason(completion_response["details"]["finish_reason"]) sum_logprob = 0 for token in completion_response["details"]["tokens"]: if token["logprob"] is not None: @@ -198,10 +190,7 @@ class PredibaseConfig(BaseConfig): best_of_value = 0 if best_of_value > 1: - if ( - "details" in completion_response - and "best_of_sequences" in completion_response["details"] - ): + if "details" in completion_response and "best_of_sequences" in completion_response["details"]: choices_list = [] for idx, item in enumerate(completion_response["details"]["best_of_sequences"]): sum_logprob = 0 @@ -233,11 +222,7 @@ class PredibaseConfig(BaseConfig): if output_text is not None and len(output_text) > 0: completion_tokens = 0 try: - completion_tokens = len( - encoding.encode( - model_response["choices"][0]["message"].get("content", "") - ) - ) + completion_tokens = len(encoding.encode(model_response["choices"][0]["message"].get("content", ""))) except Exception: # Keep usage calculation non-blocking if encoding fails. pass @@ -327,9 +312,7 @@ class PredibaseConfig(BaseConfig): litellm_params: dict, stream: Optional[bool] = None, ) -> str: - tenant_id = litellm_params.get("predibase_tenant_id") or litellm_params.get( - "tenant_id" - ) + tenant_id = litellm_params.get("predibase_tenant_id") or litellm_params.get("tenant_id") if tenant_id is None: raise ValueError( "Missing Predibase Tenant ID - Required for making the request. Set dynamically (e.g. `completion(..tenant_id=)`) or in env - `PREDIBASE_TENANT_ID`." @@ -349,12 +332,8 @@ class PredibaseConfig(BaseConfig): completion_url += "/generate" return completion_url - def get_error_class( - self, error_message: str, status_code: int, headers: Union[dict, Headers] - ) -> BaseLLMException: - return PredibaseError( - status_code=status_code, message=error_message, headers=headers - ) + def get_error_class(self, error_message: str, status_code: int, headers: Union[dict, Headers]) -> BaseLLMException: + return PredibaseError(status_code=status_code, message=error_message, headers=headers) def validate_environment( self, diff --git a/litellm/proxy/guardrails/guardrail_hooks/xecguard/xecguard.py b/litellm/proxy/guardrails/guardrail_hooks/xecguard/xecguard.py index 2ec7efc3045..294c671bc6f 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/xecguard/xecguard.py +++ b/litellm/proxy/guardrails/guardrail_hooks/xecguard/xecguard.py @@ -98,9 +98,7 @@ class XecGuardGuardrail(CustomGuardrail): "the guardrail config." ) - self.api_base = ( - api_base or os.environ.get("XECGUARD_API_BASE") or _DEFAULT_API_BASE - ).rstrip("/") + self.api_base = (api_base or os.environ.get("XECGUARD_API_BASE") or _DEFAULT_API_BASE).rstrip("/") self.xecguard_model = xecguard_model or _DEFAULT_MODEL self.policy_names = policy_names @@ -115,9 +113,7 @@ class XecGuardGuardrail(CustomGuardrail): else: self.block_on_error = block_on_error - self.grounding_strictness = ( - grounding_strictness or _DEFAULT_GROUNDING_STRICTNESS - ) + self.grounding_strictness = grounding_strictness or _DEFAULT_GROUNDING_STRICTNESS self.async_handler = get_async_httpx_client( llm_provider=httpxSpecialProvider.GuardrailCallback, @@ -179,16 +175,11 @@ class XecGuardGuardrail(CustomGuardrail): messages=messages, documents=documents, ) - if ( - grounding_result is not None - and grounding_result.get("decision") == "UNSAFE" - ): + if grounding_result is not None and grounding_result.get("decision") == "UNSAFE": raise HTTPException( status_code=400, detail={ - "error": self._format_grounding_block_message( - grounding_result - ), + "error": self._format_grounding_block_message(grounding_result), "guardrail_name": self.guardrail_name or "xecguard", "xecguard_response": grounding_result, }, @@ -212,7 +203,7 @@ class XecGuardGuardrail(CustomGuardrail): isinstance(kwargs, dict) and "litellm_params" in kwargs and "metadata" in kwargs["litellm_params"] - and "standard_logging_guardrail_information"in kwargs["litellm_params"]["metadata"] + and "standard_logging_guardrail_information" in kwargs["litellm_params"]["metadata"] and kwargs["litellm_params"]["metadata"]["standard_logging_guardrail_information"] ): return kwargs, result @@ -249,9 +240,7 @@ class XecGuardGuardrail(CustomGuardrail): return kwargs, result guardrail_status: GuardrailStatus = ( - "guardrail_intervened" - if scan_result.get("decision") == "UNSAFE" - else "success" + "guardrail_intervened" if scan_result.get("decision") == "UNSAFE" else "success" ) end_time = datetime.now() kwargs["standard_logging_object"]["guardrail_information"] = { @@ -292,11 +281,7 @@ class XecGuardGuardrail(CustomGuardrail): asyncio.set_event_loop(loop) if loop.is_running(): return kwargs, result - loop.run_until_complete( - self.async_logging_hook( - kwargs=kwargs, result=result, call_type=call_type - ) - ) + loop.run_until_complete(self.async_logging_hook(kwargs=kwargs, result=result, call_type=call_type)) except Exception as exc: verbose_proxy_logger.debug( "XecGuard sync logging_hook swallowed exception: %s", @@ -318,9 +303,7 @@ class XecGuardGuardrail(CustomGuardrail): "model": self.xecguard_model, "scan_type": scan_type, "messages": messages, - "policy_names": ( - self.policy_names if self.policy_names else _DEFAULT_POLICIES - ), + "policy_names": (self.policy_names if self.policy_names else _DEFAULT_POLICIES), } return await self._post( path=_SCAN_ENDPOINT, @@ -378,9 +361,7 @@ class XecGuardGuardrail(CustomGuardrail): raise HTTPException( status_code=400, detail={ - "error": ( - f"XecGuard API unreachable " f"(block_on_error=True): {exc}" - ), + "error": (f"XecGuard API unreachable (block_on_error=True): {exc}"), "guardrail_name": self.guardrail_name or "xecguard", }, ) from exc @@ -404,9 +385,7 @@ class XecGuardGuardrail(CustomGuardrail): the request data is incomplete. """ raw_messages = request_data.get("messages") or [] - messages: List[dict] = [ - self._normalize_message(m) for m in raw_messages if isinstance(m, dict) - ] + messages: List[dict] = [self._normalize_message(m) for m in raw_messages if isinstance(m, dict)] if input_type == "request": if not messages: @@ -419,9 +398,7 @@ class XecGuardGuardrail(CustomGuardrail): return messages # input_type == "response" - assistant_text = self._extract_assistant_text_from_response( - request_data.get("response") - ) + assistant_text = self._extract_assistant_text_from_response(request_data.get("response")) if assistant_text is None: return [] messages.append({"role": "assistant", "content": assistant_text}) @@ -498,9 +475,7 @@ class XecGuardGuardrail(CustomGuardrail): parts = [ item.get("text") for item in content - if isinstance(item, dict) - and item.get("type") == "text" - and isinstance(item.get("text"), str) + if isinstance(item, dict) and item.get("type") == "text" and isinstance(item.get("text"), str) ] joined = "\n".join(p for p in parts if p) return joined or None @@ -563,10 +538,7 @@ class XecGuardGuardrail(CustomGuardrail): if isinstance(candidate, str) and candidate: rationale = candidate[:_RATIONALE_TRUNCATE_CHARS] break - return ( - f"Blocked by XecGuard: policies=[{policies}] " - f"trace_id={trace_id} rationale={rationale}" - ) + return f"Blocked by XecGuard: policies=[{policies}] trace_id={trace_id} rationale={rationale}" @staticmethod def _format_grounding_block_message(result: dict) -> str: @@ -582,7 +554,4 @@ class XecGuardGuardrail(CustomGuardrail): if isinstance(candidate, str): rationale = candidate[:_RATIONALE_TRUNCATE_CHARS] rules_str = ",".join(rules) if rules else "unknown" - return ( - f"Blocked by XecGuard grounding: rules=[{rules_str}] " - f"trace_id={trace_id} rationale={rationale}" - ) \ No newline at end of file + return f"Blocked by XecGuard grounding: rules=[{rules_str}] trace_id={trace_id} rationale={rationale}"