From bf81334046126326c0491517ab03addfb598c4cb Mon Sep 17 00:00:00 2001 From: dlowzzxx Date: Fri, 11 Sep 2026 23:02:59 +0200 Subject: [PATCH 1/3] fix(token_counter): handle tool array schema without items property (#40344) Prevent KeyError: 'items' when tool parameter schema defines type: array without an items specification by falling back to any[]. --- litellm/litellm_core_utils/token_counter.py | 6 ++-- .../test_token_counter_tool.py | 29 +++++++++++++++++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/litellm/litellm_core_utils/token_counter.py b/litellm/litellm_core_utils/token_counter.py index 1de2533d514..f08ec7bc62c 100644 --- a/litellm/litellm_core_utils/token_counter.py +++ b/litellm/litellm_core_utils/token_counter.py @@ -987,8 +987,10 @@ 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") + if isinstance(items, dict): + return f"{_format_type(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/test_litellm/litellm_core_utils/test_token_counter_tool.py b/tests/test_litellm/litellm_core_utils/test_token_counter_tool.py index 9f8c1070a47..045c1a13eb1 100644 --- a/tests/test_litellm/litellm_core_utils/test_token_counter_tool.py +++ b/tests/test_litellm/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,28 @@ 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 From 78f9b5f0cc3a398c265e0899ff326e13cc90a354 Mon Sep 17 00:00:00 2001 From: dlowzzxx Date: Thu, 17 Sep 2026 23:12:00 +0200 Subject: [PATCH 2/3] fix(token_counter): tolerate non-dictionary property schemas --- litellm/litellm_core_utils/token_counter.py | 2 ++ .../test_token_counter_tool.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/litellm/litellm_core_utils/token_counter.py b/litellm/litellm_core_utils/token_counter.py index f08ec7bc62c..997f995ce29 100644 --- a/litellm/litellm_core_utils/token_counter.py +++ b/litellm/litellm_core_utils/token_counter.py @@ -970,6 +970,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}") diff --git a/tests/test_litellm/litellm_core_utils/test_token_counter_tool.py b/tests/test_litellm/litellm_core_utils/test_token_counter_tool.py index 045c1a13eb1..df16046e2fd 100644 --- a/tests/test_litellm/litellm_core_utils/test_token_counter_tool.py +++ b/tests/test_litellm/litellm_core_utils/test_token_counter_tool.py @@ -99,3 +99,20 @@ def test_token_counter_tool_array_schema_without_items(): 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 From d2160f155df93d26071048e004cb45a5b5064f5d Mon Sep 17 00:00:00 2001 From: dlowzzxx <210235699+dlowzzxx@users.noreply.github.com> Date: Wed, 14 Oct 2026 11:00:52 +0200 Subject: [PATCH 3/3] fix(token_counter): avoid new unknown type diagnostics Assisted-by: OpenAI Codex --- litellm/litellm_core_utils/token_counter.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/litellm/litellm_core_utils/token_counter.py b/litellm/litellm_core_utils/token_counter.py index 0ee36e0964e..3ba853bdeff 100644 --- a/litellm/litellm_core_utils/token_counter.py +++ b/litellm/litellm_core_utils/token_counter.py @@ -1112,9 +1112,8 @@ def _format_type(props, indent): return " | ".join([f'"{item}"' for item in props["enum"]]) return "string" elif type == "array": - items: Final = props.get("items") - if isinstance(items, dict): - return f"{_format_type(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}}"