diff --git a/litellm/litellm_core_utils/token_counter.py b/litellm/litellm_core_utils/token_counter.py index 94757280cda..68c16e1b4b2 100644 --- a/litellm/litellm_core_utils/token_counter.py +++ b/litellm/litellm_core_utils/token_counter.py @@ -1033,8 +1033,7 @@ 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)}[]" + return f"{_format_type(props.get('items', {}), indent)}[]" 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.py b/tests/unit/litellm_core_utils/test_token_counter.py index 731407a85be..3492bcafc5c 100644 --- a/tests/unit/litellm_core_utils/test_token_counter.py +++ b/tests/unit/litellm_core_utils/test_token_counter.py @@ -1565,6 +1565,21 @@ def test_token_counter_uses_the_tokenizer_of_each_model_family_and_of_a_custom_t } +def test_token_counter_counts_array_parameter_without_items(): + messages = [{"role": "user", "content": "tag this"}] + tags_tool = { + "type": "function", + "function": { + "name": "set_tags", + "parameters": {"type": "object", "properties": {"tags": {"type": "array"}}, "required": ["tags"]}, + }, + } + + assert token_counter(model="gpt-4o", messages=messages, tools=[tags_tool]) > token_counter( + model="gpt-4o", messages=messages + ) + + class _Unprintable: def __str__(self) -> str: raise AssertionError("opaque block values must be dropped before they are serialized") @@ -1608,6 +1623,7 @@ def test_uncountable_block_nesting_past_the_depth_limit_is_truncated(): assert "leaf" not in text + def test_uncountable_block_elides_inline_base64_data_but_keeps_plain_text_data(): (message,) = messages_with_uncountable_blocks_as_text( [ diff --git a/tests/unit/proxy/test_proxy_token_counter.py b/tests/unit/proxy/test_proxy_token_counter.py index 16431c1deda..4f932f3e2ac 100644 --- a/tests/unit/proxy/test_proxy_token_counter.py +++ b/tests/unit/proxy/test_proxy_token_counter.py @@ -1382,3 +1382,25 @@ async def test_gemini_non_json_success_body_surfaces_as_bad_gateway_when_fallbac ) assert exc_info.value.code == "502" + + +@pytest.mark.asyncio +async def test_local_estimate_counts_a_tool_with_an_array_property_without_items(monkeypatch): + monkeypatch.setattr(litellm.proxy.proxy_server, "llm_router", None) + tags_tool = { + "type": "function", + "function": { + "name": "set_tags", + "description": "Set tags", + "parameters": {"type": "object", "properties": {"tags": {"type": "array"}}, "required": ["tags"]}, + }, + } + + async def count(tools: list[dict[str, object]] | None) -> int: + result = await token_counter( + request=TokenCountRequest(model="gpt-4o", messages=[{"role": "user", "content": "tag this"}], tools=tools) + ) + return result.total_tokens + + assert await count([tags_tool]) > await count(None) +