fix: tokenize text-source documents instead of using flat fallback

This commit is contained in:
michelligabriele 2026-04-28 22:12:31 +02:00
parent 3576093641
commit fafd46161d
No known key found for this signature in database
2 changed files with 54 additions and 5 deletions

View file

@ -681,17 +681,32 @@ def _count_anthropic_document(
Anthropic shape:
{"type": "document", "source": {...}, "title": ..., "context": ..., "citations": ...}
We count the text-bearing fields ("title", "context") with the model
tokenizer and add DEFAULT_IMAGE_TOKEN_COUNT for the opaque source payload
(typically a PDF we cannot dimension it). Skipped: "type",
"cache_control", "citations" (configuration metadata, not prompt content).
Source variants (per the Anthropic Citations API):
- {"type": "text", "media_type": "text/plain", "data": "<inline text>"}
- {"type": "base64", "media_type": ..., "data": ...}
- {"type": "url", "url": ...}
- {"type": "file", "file_id": ...}
For text sources we tokenize source["data"] with the model tokenizer
(it's plain prompt content). For base64 / url / file sources the
payload is opaque (typically a PDF we cannot dimension it) and we
add DEFAULT_IMAGE_TOKEN_COUNT as the fallback.
Title and context are always counted. Skipped: "type", "cache_control",
"citations" (configuration metadata, not prompt content).
"""
tokens = 0
for field in ("title", "context"):
value = content.get(field)
if isinstance(value, str) and value:
tokens += count_function(value)
if content.get("source") is not None:
source = content.get("source")
if isinstance(source, dict) and source.get("type") == "text":
data = source.get("data")
if isinstance(data, str) and data:
tokens += count_function(data)
elif source is not None:
# base64 / url / file / unknown — opaque payload, use the fallback.
tokens += DEFAULT_IMAGE_TOKEN_COUNT
return tokens

View file

@ -1130,6 +1130,40 @@ def test_token_counter_with_anthropic_document_file():
assert tokens > 0, f"Expected positive token count, got {tokens}"
def test_token_counter_with_anthropic_document_text():
"""
Anthropic-native document block with source.type='text' tokenizes the
inline text in source['data'], not the flat DEFAULT_IMAGE_TOKEN_COUNT
fallback used for opaque (base64/url/file) payloads.
"""
inline_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " * 200
messages = [
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": inline_text,
},
"title": "Long inline doc",
}
],
}
]
tokens = token_counter(
model="anthropic/claude-sonnet-4-5-20250929", messages=messages
)
# The inline text alone is well over a thousand tokens — substantially
# more than the 250-token fallback used for opaque sources.
assert (
tokens > 500
), f"Expected text-source document to tokenize source['data'], got {tokens}"
def test_token_counter_with_image_inside_tool_result():
"""
Image block nested inside tool_result.content is counted via recursion.