diff --git a/litellm/litellm_core_utils/token_counter.py b/litellm/litellm_core_utils/token_counter.py index e6a68de07e9..5e111f49452 100644 --- a/litellm/litellm_core_utils/token_counter.py +++ b/litellm/litellm_core_utils/token_counter.py @@ -509,7 +509,23 @@ def _count_extra( num_tokens = 3 # every reply is primed with <|start|>assistant<|message|> if tools: - num_tokens += count_function(_format_function_definitions(tools)) + # Normalize Anthropic-format tools to OpenAI format before counting + normalized_tools = [] + for t in tools: + if isinstance(t, dict) and "function" not in t and "input_schema" in t: + normalized_tools.append( + { + "type": "function", + "function": { + "name": t.get("name"), + "description": t.get("description", ""), + "parameters": t.get("input_schema", {}), + }, + } + ) + else: + normalized_tools.append(t) + num_tokens += count_function(_format_function_definitions(normalized_tools)) num_tokens += 9 # Additional tokens for function definition of tools # If there's a system message and tools are present, subtract four tokens if tools and includes_system_message: diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 814111762b6..444c4d75769 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -10512,11 +10512,13 @@ async def token_counter(request: TokenCountRequest, call_endpoint: bool = False) ) tokenizer_used = str(_tokenizer_used["type"]) + total_tokens = token_counter( model=model_to_use, text=prompt, messages=messages, custom_tokenizer=_tokenizer_used, # type: ignore + tools=tools, # type: ignore[arg-type] ) return TokenCountResponse( total_tokens=total_tokens, diff --git a/tests/proxy_unit_tests/test_count_tokens_tools.py b/tests/proxy_unit_tests/test_count_tokens_tools.py new file mode 100644 index 00000000000..e0a5a335f93 --- /dev/null +++ b/tests/proxy_unit_tests/test_count_tokens_tools.py @@ -0,0 +1,35 @@ +import pytest +from litellm.proxy._types import TokenCountRequest + + +@pytest.mark.asyncio +async def test_count_tokens_anthropic_tools_not_ignored(): + """Test that Anthropic-style tools are counted in fallback path""" + from litellm.proxy.proxy_server import token_counter as proxy_token_counter + + request_with_tools = TokenCountRequest( + model="claude-opus-4-6", + messages=[{"role": "user", "content": "hello"}], + tools=[{ + "name": "example_tool", + "description": "A tool " + "with long description " * 100, + "input_schema": { + "type": "object", + "properties": { + "param1": {"type": "string", "description": "A parameter"} + } + } + }] + ) + + request_without_tools = TokenCountRequest( + model="claude-opus-4-6", + messages=[{"role": "user", "content": "hello"}], + ) + + response_with = await proxy_token_counter(request=request_with_tools, call_endpoint=False) + response_without = await proxy_token_counter(request=request_without_tools, call_endpoint=False) + + assert response_with.total_tokens > response_without.total_tokens, ( + f"Tools should add tokens: with={response_with.total_tokens}, without={response_without.total_tokens}" + )