From 8b98193a510daea87afcda0a0358046cafce3b04 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 9 Aug 2024 13:16:38 -0700 Subject: [PATCH 1/2] bedrock make_valid_bedrock_tool_name --- litellm/llms/prompt_templates/factory.py | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index 7fce1929678..db655c01d0d 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -2293,6 +2293,34 @@ def _bedrock_converse_messages_pt( return contents +def make_valid_bedrock_tool_name(input_tool_name: str) -> str: + """ + Replaces any invalid characters in the input tool name with underscores + and ensures the resulting string is a valid identifier for Bedrock tools + """ + + def replace_invalid(char): + """ + Bedrock tool names only supports alpha-numeric characters and underscores + """ + if char.isalnum() or char == "_": + return char + return "_" + + # If the string is empty, return a default valid identifier + if input_tool_name is None or len(input_tool_name) == 0: + return input_tool_name + + # If it doesn't start with a letter, prepend 'a' + if not input_tool_name[0].isalpha(): + input_tool_name = "a" + input_tool_name + + # Replace any invalid characters with underscores + valid_string = "".join(replace_invalid(char) for char in input_tool_name) + + return valid_string + + def _bedrock_tools_pt(tools: List) -> List[BedrockToolBlock]: """ OpenAI tools looks like: @@ -2346,6 +2374,10 @@ def _bedrock_tools_pt(tools: List) -> List[BedrockToolBlock]: for tool in tools: parameters = tool.get("function", {}).get("parameters", None) name = tool.get("function", {}).get("name", "") + + # related issue: https://github.com/BerriAI/litellm/issues/5007 + # Bedrock tool names must satisfy regular expression pattern: [a-zA-Z][a-zA-Z0-9_]* ensure this is true + name = make_valid_bedrock_tool_name(input_tool_name=name) description = tool.get("function", {}).get( "description", name ) # converse api requires a description From 6dc9b390956e0fd9d0c3d3d980bb2534b16f609a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 9 Aug 2024 13:26:21 -0700 Subject: [PATCH 2/2] test invalid tool namehandling --- litellm/tests/test_bedrock_completion.py | 125 ++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/litellm/tests/test_bedrock_completion.py b/litellm/tests/test_bedrock_completion.py index 4b1e544c7f1..fa1894e74ce 100644 --- a/litellm/tests/test_bedrock_completion.py +++ b/litellm/tests/test_bedrock_completion.py @@ -25,8 +25,9 @@ from litellm import ( completion_cost, embedding, ) -from litellm.llms.bedrock_httpx import BedrockLLM +from litellm.llms.bedrock_httpx import BedrockLLM, ToolBlock from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler +from litellm.llms.prompt_templates.factory import _bedrock_tools_pt # litellm.num_retries = 3 litellm.cache = None @@ -983,3 +984,125 @@ def test_completion_bedrock_external_client_region(): pass except Exception as e: pytest.fail(f"Error occurred: {e}") + + +def test_bedrock_tool_calling(): + """ + # related issue: https://github.com/BerriAI/litellm/issues/5007 + # Bedrock tool names must satisfy regular expression pattern: [a-zA-Z][a-zA-Z0-9_]* ensure this is true + """ + litellm.set_verbose = True + response = litellm.completion( + model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0", + fallbacks=["bedrock/meta.llama3-1-8b-instruct-v1:0"], + messages=[ + { + "role": "user", + "content": "What's the weather like in Boston today in Fahrenheit?", + } + ], + tools=[ + { + "type": "function", + "function": { + "name": "-DoSomethingVeryCool-forLitellm_Testin999229291-0293993", + "description": "do something very cool", + "parameters": {"type": "object", "properties": {}}, + }, + } + ], + ) + + print("bedrock response") + print(response) + + +def test_bedrock_tools_pt_valid_names(): + """ + # related issue: https://github.com/BerriAI/litellm/issues/5007 + # Bedrock tool names must satisfy regular expression pattern: [a-zA-Z][a-zA-Z0-9_]* ensure this is true + + """ + tools = [ + { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"}, + }, + "required": ["location"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "search_restaurants", + "description": "Search for restaurants", + "parameters": { + "type": "object", + "properties": { + "cuisine": {"type": "string"}, + }, + "required": ["cuisine"], + }, + }, + }, + ] + + result = _bedrock_tools_pt(tools) + + assert len(result) == 2 + assert result[0]["toolSpec"]["name"] == "get_current_weather" + assert result[1]["toolSpec"]["name"] == "search_restaurants" + + +def test_bedrock_tools_pt_invalid_names(): + """ + # related issue: https://github.com/BerriAI/litellm/issues/5007 + # Bedrock tool names must satisfy regular expression pattern: [a-zA-Z][a-zA-Z0-9_]* ensure this is true + + """ + + tools = [ + { + "type": "function", + "function": { + "name": "123-invalid@name", + "description": "Invalid name test", + "parameters": { + "type": "object", + "properties": { + "test": {"type": "string"}, + }, + "required": ["test"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "another@invalid#name", + "description": "Another invalid name test", + "parameters": { + "type": "object", + "properties": { + "test": {"type": "string"}, + }, + "required": ["test"], + }, + }, + }, + ] + + result = _bedrock_tools_pt(tools) + + print("bedrock tools after prompt formatting=", result) + + assert len(result) == 2 + assert result[0]["toolSpec"]["name"] == "a123_invalid_name" + assert result[1]["toolSpec"]["name"] == "another_invalid_name"