Skip to content

Commit 305a005

Browse files
authored
fix: set use_native_token_count default to false (strands-agents#2284)
1 parent 6b53928 commit 305a005

10 files changed

Lines changed: 78 additions & 23 deletions

File tree

src/strands/models/anthropic.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ class AnthropicConfig(BaseModelConfig, total=False):
5858
params: Additional model parameters (e.g., temperature).
5959
For a complete list of supported parameters, see https://docs.anthropic.com/en/api/messages.
6060
use_native_token_count: Whether to use the native Anthropic count_tokens API.
61-
When True (default), count_tokens() calls the Anthropic API for accurate counts.
62-
When False, skips the API call and uses the local estimator.
61+
When True, count_tokens() calls the Anthropic API for accurate counts.
62+
When False (default), skips the API call and uses the local estimator.
6363
"""
6464

6565
max_tokens: Required[int]
@@ -398,7 +398,7 @@ async def count_tokens(
398398
Returns:
399399
Total input token count.
400400
"""
401-
if self.config.get("use_native_token_count") is False:
401+
if self.config.get("use_native_token_count") is not True:
402402
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
403403

404404
try:

src/strands/models/bedrock.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ class BedrockConfig(BaseModelConfig, total=False):
118118
temperature: Controls randomness in generation (higher = more random)
119119
top_p: Controls diversity via nucleus sampling (alternative to temperature)
120120
use_native_token_count: Whether to use the native Bedrock CountTokens API.
121-
When True (default), count_tokens() calls the Bedrock API for accurate counts.
122-
When False, skips the API call and uses the local estimator.
121+
When True, count_tokens() calls the Bedrock API for accurate counts.
122+
When False (default), skips the API call and uses the local estimator.
123123
"""
124124

125125
additional_args: dict[str, Any] | None
@@ -798,7 +798,7 @@ async def count_tokens(
798798
Returns:
799799
Total input token count.
800800
"""
801-
if self.config.get("use_native_token_count") is False:
801+
if self.config.get("use_native_token_count") is not True:
802802
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
803803

804804
model_id: str = self.config["model_id"]

src/strands/models/gemini.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ class GeminiConfig(BaseModelConfig, total=False):
5050
For a complete list of supported tools, see
5151
https://ai.google.dev/api/caching#Tool
5252
use_native_token_count: Whether to use the native Gemini count_tokens API.
53-
When True (default), count_tokens() calls the Gemini API for accurate counts.
54-
When False, skips the API call and uses the local estimator.
53+
When True, count_tokens() calls the Gemini API for accurate counts.
54+
When False (default), skips the API call and uses the local estimator.
5555
"""
5656

5757
model_id: Required[str]
@@ -461,7 +461,7 @@ async def count_tokens(
461461
Returns:
462462
Total input token count.
463463
"""
464-
if self.config.get("use_native_token_count") is False:
464+
if self.config.get("use_native_token_count") is not True:
465465
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
466466

467467
try:

src/strands/models/llamacpp.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ class LlamaCppConfig(BaseModelConfig, total=False):
126126
- slot_id: Slot ID for parallel inference
127127
- samplers: Custom sampler order
128128
use_native_token_count: Whether to use the native llama.cpp /tokenize endpoint.
129-
When True (default), count_tokens() calls the server's tokenize endpoint for accurate counts.
130-
When False, skips the API call and uses the local estimator.
129+
When True, count_tokens() calls the server's tokenize endpoint for accurate counts.
130+
When False (default), skips the API call and uses the local estimator.
131131
"""
132132

133133
model_id: str
@@ -537,7 +537,7 @@ async def count_tokens(
537537
Returns:
538538
Total input token count.
539539
"""
540-
if self.config.get("use_native_token_count") is False:
540+
if self.config.get("use_native_token_count") is not True:
541541
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
542542

543543
try:

src/strands/models/openai_responses.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ class OpenAIResponsesConfig(BaseModelConfig, total=False):
137137
When True, the server stores conversation history and the client does not need to
138138
send the full message history with each request. Defaults to False.
139139
use_native_token_count: Whether to use the native OpenAI input_tokens.count API.
140-
When True (default), count_tokens() calls the OpenAI API for accurate counts.
141-
When False, skips the API call and uses the local estimator.
140+
When True, count_tokens() calls the OpenAI API for accurate counts.
141+
When False (default), skips the API call and uses the local estimator.
142142
"""
143143

144144
model_id: str
@@ -242,7 +242,7 @@ async def count_tokens(
242242
Returns:
243243
Total input token count.
244244
"""
245-
if self.config.get("use_native_token_count") is False:
245+
if self.config.get("use_native_token_count") is not True:
246246
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
247247

248248
try:

tests/strands/models/test_anthropic.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ class TestCountTokens:
10721072
@pytest.fixture
10731073
def model_with_client(self, anthropic_client, model_id, max_tokens):
10741074
_ = anthropic_client
1075-
return AnthropicModel(model_id=model_id, max_tokens=max_tokens)
1075+
return AnthropicModel(model_id=model_id, max_tokens=max_tokens, use_native_token_count=True)
10761076

10771077
@pytest.fixture
10781078
def messages(self):
@@ -1175,3 +1175,14 @@ async def test_skip_native_api_when_use_native_token_count_false(
11751175
anthropic_client.messages.count_tokens.assert_not_called()
11761176
assert isinstance(result, int)
11771177
assert result >= 0
1178+
1179+
@pytest.mark.asyncio
1180+
async def test_skip_native_api_by_default(self, anthropic_client, model_id, max_tokens, messages):
1181+
_ = anthropic_client
1182+
model = AnthropicModel(model_id=model_id, max_tokens=max_tokens)
1183+
1184+
result = await model.count_tokens(messages=messages)
1185+
1186+
anthropic_client.messages.count_tokens.assert_not_called()
1187+
assert isinstance(result, int)
1188+
assert result >= 0

tests/strands/models/test_bedrock.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3343,7 +3343,7 @@ def clean_cache(self):
33433343
@pytest.fixture
33443344
def model_with_client(self, bedrock_client, model_id):
33453345
_ = bedrock_client
3346-
return BedrockModel(model_id=model_id)
3346+
return BedrockModel(model_id=model_id, use_native_token_count=True)
33473347

33483348
@pytest.fixture
33493349
def messages(self):
@@ -3459,7 +3459,7 @@ async def test_fallback_logs_debug(self, model_with_client, bedrock_client, mess
34593459

34603460
@pytest.mark.asyncio
34613461
async def test_caches_model_id_when_count_tokens_unsupported(self, bedrock_client, messages):
3462-
model = BedrockModel(model_id="unsupported-cache-test-model")
3462+
model = BedrockModel(model_id="unsupported-cache-test-model", use_native_token_count=True)
34633463
bedrock_client.count_tokens.side_effect = ClientError(
34643464
{"Error": {"Code": "ValidationException", "Message": "The provided model doesn't support counting tokens"}},
34653465
"CountTokens",
@@ -3475,7 +3475,7 @@ async def test_caches_model_id_when_count_tokens_unsupported(self, bedrock_clien
34753475

34763476
@pytest.mark.asyncio
34773477
async def test_caches_model_id_when_access_denied(self, bedrock_client, messages):
3478-
model = BedrockModel(model_id="access-denied-cache-test-model")
3478+
model = BedrockModel(model_id="access-denied-cache-test-model", use_native_token_count=True)
34793479
bedrock_client.count_tokens.side_effect = ClientError(
34803480
{
34813481
"Error": {
@@ -3523,7 +3523,7 @@ async def test_access_denied_logs_warning_with_full_error(
35233523

35243524
@pytest.mark.asyncio
35253525
async def test_does_not_cache_model_id_for_other_errors(self, bedrock_client, messages):
3526-
model = BedrockModel(model_id="transient-error-test-model")
3526+
model = BedrockModel(model_id="transient-error-test-model", use_native_token_count=True)
35273527
bedrock_client.count_tokens.side_effect = RuntimeError("Transient network error")
35283528

35293529
await model.count_tokens(messages=messages)
@@ -3543,3 +3543,14 @@ async def test_skip_native_api_when_use_native_token_count_false(self, bedrock_c
35433543
bedrock_client.count_tokens.assert_not_called()
35443544
assert isinstance(result, int)
35453545
assert result >= 0
3546+
3547+
@pytest.mark.asyncio
3548+
async def test_skip_native_api_by_default(self, bedrock_client, model_id, messages):
3549+
_ = bedrock_client
3550+
model = BedrockModel(model_id=model_id)
3551+
3552+
result = await model.count_tokens(messages=messages)
3553+
3554+
bedrock_client.count_tokens.assert_not_called()
3555+
assert isinstance(result, int)
3556+
assert result >= 0

tests/strands/models/test_gemini.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ def gemini_client(self):
11441144
@pytest.fixture
11451145
def model(self, gemini_client):
11461146
_ = gemini_client
1147-
return GeminiModel(model_id="m1")
1147+
return GeminiModel(model_id="m1", use_native_token_count=True)
11481148

11491149
@pytest.fixture
11501150
def messages(self):
@@ -1239,3 +1239,14 @@ async def test_skip_native_api_when_use_native_token_count_false(self, gemini_cl
12391239
gemini_client.aio.models.count_tokens.assert_not_called()
12401240
assert isinstance(result, int)
12411241
assert result >= 0
1242+
1243+
@pytest.mark.asyncio
1244+
async def test_skip_native_api_by_default(self, gemini_client, messages):
1245+
_ = gemini_client
1246+
model = GeminiModel(model_id="m1")
1247+
1248+
result = await model.count_tokens(messages=messages)
1249+
1250+
gemini_client.aio.models.count_tokens.assert_not_called()
1251+
assert isinstance(result, int)
1252+
assert result >= 0

tests/strands/models/test_llamacpp.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ class TestCountTokens:
713713

714714
@pytest.fixture
715715
def model(self):
716-
return LlamaCppModel(base_url="http://localhost:8080")
716+
return LlamaCppModel(base_url="http://localhost:8080", use_native_token_count=True)
717717

718718
@pytest.fixture
719719
def messages(self):
@@ -814,3 +814,14 @@ async def test_skip_native_api_when_use_native_token_count_false(self, messages)
814814
model.client.post.assert_not_called()
815815
assert isinstance(result, int)
816816
assert result >= 0
817+
818+
@pytest.mark.asyncio
819+
async def test_skip_native_api_by_default(self, messages):
820+
model = LlamaCppModel(base_url="http://localhost:8080")
821+
model.client.post = AsyncMock()
822+
823+
result = await model.count_tokens(messages=messages)
824+
825+
model.client.post.assert_not_called()
826+
assert isinstance(result, int)
827+
assert result >= 0

tests/strands/models/test_openai_responses.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1224,7 +1224,7 @@ def openai_client(self):
12241224
@pytest.fixture
12251225
def model(self, openai_client):
12261226
_ = openai_client
1227-
return OpenAIResponsesModel(model_id="gpt-4o")
1227+
return OpenAIResponsesModel(model_id="gpt-4o", use_native_token_count=True)
12281228

12291229
@pytest.fixture
12301230
def messages(self):
@@ -1329,6 +1329,17 @@ async def test_skip_native_api_when_use_native_token_count_false(self, openai_cl
13291329
assert isinstance(result, int)
13301330
assert result >= 0
13311331

1332+
@pytest.mark.asyncio
1333+
async def test_skip_native_api_by_default(self, openai_client, messages):
1334+
_ = openai_client
1335+
model = OpenAIResponsesModel(model_id="gpt-4o")
1336+
1337+
result = await model.count_tokens(messages=messages)
1338+
1339+
openai_client.responses.input_tokens.count.assert_not_called()
1340+
assert isinstance(result, int)
1341+
assert result >= 0
1342+
13321343

13331344
# =============================================================================
13341345
# Bedrock Mantle (bedrock_mantle_config) integration with OpenAIResponsesModel

0 commit comments

Comments
 (0)