diff --git a/litellm/litellm_core_utils/token_counter.py b/litellm/litellm_core_utils/token_counter.py index 8aded69aed2..020fc3cf875 100644 --- a/litellm/litellm_core_utils/token_counter.py +++ b/litellm/litellm_core_utils/token_counter.py @@ -1160,6 +1160,8 @@ def _format_object_parameters(parameters, indent): required_params: Final = parameters.get("required", []) lines: Final = [] for key, props in properties.items(): + if not isinstance(props, dict): + continue description = props.get("description") if description: lines.append(f"// {description}") @@ -1177,8 +1179,9 @@ def _format_type(props, indent): return " | ".join([f'"{item}"' for item in props["enum"]]) return "string" elif type == "array": - # items is required, OpenAI throws an error if it's missing - return f"{_format_type(props['items'], indent)}[]" + if "items" in props and isinstance(props["items"], dict): + return f"{_format_type(props['items'], indent)}[]" + return "any[]" elif type == "object": return f"{{\n{_format_object_parameters(props, indent + 2)}\n}}" elif type in ["integer", "number"]: diff --git a/tests/unit/litellm_core_utils/test_token_counter_tool.py b/tests/unit/litellm_core_utils/test_token_counter_tool.py index f61b7d335c1..e5744cc88bf 100644 --- a/tests/unit/litellm_core_utils/test_token_counter_tool.py +++ b/tests/unit/litellm_core_utils/test_token_counter_tool.py @@ -31,9 +31,7 @@ def test_token_counter_tool_increases(messages): conversation.append(message) tokens = token_counter(model="gpt-3.5-turbo", messages=conversation, tools=TOOLS) # type: ignore print(f"tokens: {tokens}") - assert ( - tokens > prev_tokens - ), f"Token did not increase: {tokens} <= {prev_tokens}" + assert tokens > prev_tokens, f"Token did not increase: {tokens} <= {prev_tokens}" prev_tokens = tokens @@ -76,3 +74,45 @@ def assertGrow(usermessage, tool_call, assertBiggerThanBoth=True): f"tokens_usermessage: {tokens_usermessage}, tokens_tool_call: {tokens_tool_call}, " + f"tokens_both: {tokens_both} diff: {tokens_usermessage + tokens_tool_call - tokens_both}" ) + + +def test_token_counter_tool_array_schema_without_items(): + """Test that tool array schema without 'items' property does not crash token counting (#40344).""" + tools = [ + { + "type": "function", + "function": { + "name": "search_data", + "description": "Search for data", + "parameters": { + "type": "object", + "properties": { + "tags": { + "type": "array", + "description": "List of tags", + }, + }, + }, + }, + } + ] + messages = [{"role": "user", "content": "Hello"}] + tokens = token_counter(model="gpt-3.5-turbo", messages=messages, tools=tools) + assert tokens > 0 + + +def test_token_counter_tool_non_dict_property_schema(): + """Test that malformed string-valued property schemas do not crash token counting (#40344).""" + tools = [ + { + "type": "function", + "function": { + "name": "f", + "description": "d", + "parameters": {"type": "object", "properties": {"s": "string"}}, + }, + } + ] + messages = [{"role": "user", "content": "Hello"}] + tokens = token_counter(model="gpt-3.5-turbo", messages=messages, tools=tools) + assert tokens > 0