From 418c2b1708e22c32a97e18eb82aacbfcc62391a4 Mon Sep 17 00:00:00 2001 From: otiscuilei Date: Wed, 8 Jul 2026 21:19:16 +0800 Subject: [PATCH] fix: token_counter crashed on a tool array parameter without items _format_type indexed props['items'] unguarded, so token_counter raised KeyError('items') for any tool whose array-typed parameter omitted items, and AttributeError when items was a list (JSON-Schema tuple validation). This is token counting, not request validation, and the module degrades gracefully elsewhere, so a missing or non-dict items should fall back to the generic any type instead of raising. It is reachable through the public litellm.token_counter API for both the OpenAI and Anthropic tool shapes. --- litellm/litellm_core_utils/token_counter.py | 5 +- .../litellm_core_utils/test_token_counter.py | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/token_counter.py b/litellm/litellm_core_utils/token_counter.py index 071b16c8378..d36edbff1f7 100644 --- a/litellm/litellm_core_utils/token_counter.py +++ b/litellm/litellm_core_utils/token_counter.py @@ -814,8 +814,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)}[]" + items = props.get("items") + items = items if isinstance(items, dict) else {} + return f"{_format_type(items, indent)}[]" elif type == "object": return f"{{\n{_format_object_parameters(props, indent + 2)}\n}}" elif type in ["integer", "number"]: diff --git a/tests/test_litellm/litellm_core_utils/test_token_counter.py b/tests/test_litellm/litellm_core_utils/test_token_counter.py index 71e686563a5..f83315d1ffc 100644 --- a/tests/test_litellm/litellm_core_utils/test_token_counter.py +++ b/tests/test_litellm/litellm_core_utils/test_token_counter.py @@ -1114,3 +1114,53 @@ def test_count_content_list_rejects_unknown_type(): message = str(exc_info.value) assert "Invalid content item type: totally_unknown_block" in message assert "tool_reference" in message + + +@pytest.mark.parametrize( + "tools", + [ + [ + { + "type": "function", + "function": { + "name": "f", + "parameters": { + "type": "object", + "properties": {"tags": {"type": "array"}}, + "required": ["tags"], + }, + }, + } + ], + [ + { + "type": "function", + "function": { + "name": "f", + "parameters": { + "type": "object", + "properties": {"tags": {"type": "array", "items": [{"type": "string"}]}}, + "required": ["tags"], + }, + }, + } + ], + [ + { + "name": "f", + "input_schema": { + "type": "object", + "properties": {"tags": {"type": "array"}}, + "required": ["tags"], + }, + } + ], + ], + ids=["openai_array_without_items", "openai_array_items_as_list", "anthropic_array_without_items"], +) +def test_token_counter_tool_array_param_missing_or_invalid_items(tools): + result = litellm.token_counter( + model="gpt-4o", messages=[{"role": "user", "content": "hi"}], tools=tools + ) + assert isinstance(result, int) + assert result > 0