Skip to content

Commit 8f47956

Browse files
nathan-gageclaude
andcommitted
[anthropic] add tests for count_tokens
Add comprehensive test coverage for the Anthropic `count_tokens` implementation: - test_anthropic_model_usage_limit_exceeded: Verifies UsageLimitExceeded is raised when input_tokens_limit would be exceeded (18 < 19 tokens) - test_anthropic_model_usage_limit_not_exceeded: Tests successful execution when within token limits (25 > 19 tokens) - test_anthropic_count_tokens_error: Tests ModelHTTPError handling for invalid model names (404 response) Tests follow the same pattern as Bedrock's count_tokens tests, using the standard "quick brown fox" prompt which tokenizes to 19 input tokens with Anthropic's tokenizer. All tests include VCR cassettes for the `/v1/messages/count_tokens?beta=true` API endpoint. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 72bf79a commit 8f47956

File tree

4 files changed

+268
-1
lines changed

4 files changed

+268
-1
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
interactions:
2+
- request:
3+
headers:
4+
accept:
5+
- application/json
6+
accept-encoding:
7+
- gzip, deflate
8+
connection:
9+
- keep-alive
10+
content-length:
11+
- '105'
12+
content-type:
13+
- application/json
14+
host:
15+
- api.anthropic.com
16+
method: POST
17+
parsed_body:
18+
messages:
19+
- content:
20+
- text: hello
21+
type: text
22+
role: user
23+
model: claude-does-not-exist
24+
uri: https://api.anthropic.com/v1/messages/count_tokens?beta=true
25+
response:
26+
headers:
27+
connection:
28+
- keep-alive
29+
content-length:
30+
- '136'
31+
content-type:
32+
- application/json
33+
strict-transport-security:
34+
- max-age=31536000; includeSubDomains; preload
35+
transfer-encoding:
36+
- chunked
37+
parsed_body:
38+
error:
39+
message: 'model: claude-does-not-exist'
40+
type: not_found_error
41+
request_id: req_011CVEA3SF7rnb3DuBZytqQa
42+
type: error
43+
status:
44+
code: 404
45+
message: Not Found
46+
version: 1
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
interactions:
2+
- request:
3+
headers:
4+
accept:
5+
- application/json
6+
accept-encoding:
7+
- gzip, deflate
8+
connection:
9+
- keep-alive
10+
content-length:
11+
- '139'
12+
content-type:
13+
- application/json
14+
host:
15+
- api.anthropic.com
16+
method: POST
17+
parsed_body:
18+
messages:
19+
- content:
20+
- text: The quick brown fox jumps over the lazydog.
21+
type: text
22+
role: user
23+
model: claude-sonnet-4-5
24+
uri: https://api.anthropic.com/v1/messages/count_tokens?beta=true
25+
response:
26+
headers:
27+
connection:
28+
- keep-alive
29+
content-length:
30+
- '19'
31+
content-type:
32+
- application/json
33+
strict-transport-security:
34+
- max-age=31536000; includeSubDomains; preload
35+
parsed_body:
36+
input_tokens: 19
37+
status:
38+
code: 200
39+
message: OK
40+
version: 1
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
interactions:
2+
- request:
3+
headers:
4+
accept:
5+
- application/json
6+
accept-encoding:
7+
- gzip, deflate
8+
connection:
9+
- keep-alive
10+
content-length:
11+
- '139'
12+
content-type:
13+
- application/json
14+
host:
15+
- api.anthropic.com
16+
method: POST
17+
parsed_body:
18+
messages:
19+
- content:
20+
- text: The quick brown fox jumps over the lazydog.
21+
type: text
22+
role: user
23+
model: claude-sonnet-4-5
24+
uri: https://api.anthropic.com/v1/messages/count_tokens?beta=true
25+
response:
26+
headers:
27+
connection:
28+
- keep-alive
29+
content-length:
30+
- '19'
31+
content-type:
32+
- application/json
33+
strict-transport-security:
34+
- max-age=31536000; includeSubDomains; preload
35+
parsed_body:
36+
input_tokens: 19
37+
status:
38+
code: 200
39+
message: OK
40+
- request:
41+
headers:
42+
accept:
43+
- application/json
44+
accept-encoding:
45+
- gzip, deflate
46+
connection:
47+
- keep-alive
48+
content-length:
49+
- '172'
50+
content-type:
51+
- application/json
52+
host:
53+
- api.anthropic.com
54+
method: POST
55+
parsed_body:
56+
max_tokens: 4096
57+
messages:
58+
- content:
59+
- text: The quick brown fox jumps over the lazydog.
60+
type: text
61+
role: user
62+
model: claude-sonnet-4-5
63+
stream: false
64+
uri: https://api.anthropic.com/v1/messages?beta=true
65+
response:
66+
headers:
67+
connection:
68+
- keep-alive
69+
content-length:
70+
- '729'
71+
content-type:
72+
- application/json
73+
retry-after:
74+
- '19'
75+
strict-transport-security:
76+
- max-age=31536000; includeSubDomains; preload
77+
transfer-encoding:
78+
- chunked
79+
parsed_body:
80+
content:
81+
- text: |-
82+
I noticed a small typo in that famous pangram! It should be:
83+
84+
"The quick brown fox jumps over the **lazy dog**."
85+
86+
(There should be a space between "lazy" and "dog")
87+
88+
This sentence is often used for testing typewriters, fonts, and keyboards because it contains every letter of the English alphabet at least once.
89+
type: text
90+
id: msg_01QHpSAhCiB6L5pL23LjdRAy
91+
model: claude-sonnet-4-5-20250929
92+
role: assistant
93+
stop_reason: end_turn
94+
stop_sequence: null
95+
type: message
96+
usage:
97+
cache_creation:
98+
ephemeral_1h_input_tokens: 0
99+
ephemeral_5m_input_tokens: 0
100+
cache_creation_input_tokens: 0
101+
cache_read_input_tokens: 0
102+
input_tokens: 19
103+
output_tokens: 77
104+
service_tier: standard
105+
status:
106+
code: 200
107+
message: OK
108+
version: 1

tests/models/test_anthropic.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
ToolCallPart,
4242
ToolCallPartDelta,
4343
ToolReturnPart,
44+
UsageLimitExceeded,
4445
UserPromptPart,
4546
)
4647
from pydantic_ai.builtin_tools import CodeExecutionTool, MCPServerTool, MemoryTool, WebSearchTool
@@ -53,7 +54,7 @@
5354
from pydantic_ai.output import NativeOutput, PromptedOutput, TextOutput, ToolOutput
5455
from pydantic_ai.result import RunUsage
5556
from pydantic_ai.settings import ModelSettings
56-
from pydantic_ai.usage import RequestUsage
57+
from pydantic_ai.usage import RequestUsage, UsageLimits
5758

5859
from ..conftest import IsDatetime, IsInstance, IsNow, IsStr, TestEnv, raise_if_exception, try_import
5960
from ..parts_from_messages import part_types_from_messages
@@ -6405,3 +6406,75 @@ def memory(**command: Any) -> Any:
64056406
64066407
According to my memory, you live in **Mexico City**.\
64076408
""")
6409+
6410+
6411+
async def test_anthropic_model_usage_limit_exceeded(
6412+
allow_model_requests: None,
6413+
anthropic_api_key: str,
6414+
):
6415+
model = AnthropicModel('claude-sonnet-4-5', provider=AnthropicProvider(api_key=anthropic_api_key))
6416+
agent = Agent(model=model)
6417+
6418+
with pytest.raises(
6419+
UsageLimitExceeded,
6420+
match='The next request would exceed the input_tokens_limit of 18 \\(input_tokens=19\\)',
6421+
):
6422+
await agent.run(
6423+
'The quick brown fox jumps over the lazydog.',
6424+
usage_limits=UsageLimits(input_tokens_limit=18, count_tokens_before_request=True),
6425+
)
6426+
6427+
6428+
async def test_anthropic_model_usage_limit_not_exceeded(
6429+
allow_model_requests: None,
6430+
anthropic_api_key: str,
6431+
):
6432+
model = AnthropicModel('claude-sonnet-4-5', provider=AnthropicProvider(api_key=anthropic_api_key))
6433+
agent = Agent(model=model)
6434+
6435+
result = await agent.run(
6436+
'The quick brown fox jumps over the lazydog.',
6437+
usage_limits=UsageLimits(input_tokens_limit=25, count_tokens_before_request=True),
6438+
)
6439+
assert result.output == snapshot(
6440+
"""\
6441+
I noticed a small typo in that famous pangram! It should be:
6442+
6443+
"The quick brown fox jumps over the **lazy dog**."
6444+
6445+
(There should be a space between "lazy" and "dog")
6446+
6447+
This sentence is often used for testing typewriters, fonts, and keyboards because it contains every letter of the English alphabet at least once.\
6448+
"""
6449+
)
6450+
6451+
6452+
@pytest.mark.vcr()
6453+
async def test_anthropic_count_tokens_error(allow_model_requests: None, anthropic_api_key: str):
6454+
"""Test that errors convert to ModelHTTPError."""
6455+
model_id = 'claude-does-not-exist'
6456+
model = AnthropicModel(model_id, provider=AnthropicProvider(api_key=anthropic_api_key))
6457+
agent = Agent(model)
6458+
6459+
with pytest.raises(ModelHTTPError) as exc_info:
6460+
await agent.run('hello', usage_limits=UsageLimits(input_tokens_limit=20, count_tokens_before_request=True))
6461+
6462+
assert exc_info.value.status_code == 404
6463+
assert exc_info.value.model_name == model_id
6464+
6465+
6466+
async def test_anthropic_bedrock_count_tokens_not_supported(env: TestEnv):
6467+
"""Test that AsyncAnthropicBedrock raises UserError for count_tokens."""
6468+
from anthropic import AsyncAnthropicBedrock
6469+
6470+
bedrock_client = AsyncAnthropicBedrock(
6471+
aws_access_key='test-access-key',
6472+
aws_secret_key='test-secret-key',
6473+
aws_region='us-east-1',
6474+
)
6475+
provider = AnthropicProvider(anthropic_client=bedrock_client)
6476+
model = AnthropicModel('anthropic.claude-3-5-sonnet-20241022-v2:0', provider=provider)
6477+
agent = Agent(model)
6478+
6479+
with pytest.raises(UserError, match='AsyncAnthropicBedrock client does not support `count_tokens` api.'):
6480+
await agent.run('hello', usage_limits=UsageLimits(input_tokens_limit=20, count_tokens_before_request=True))

0 commit comments

Comments
 (0)