Skip to content

Commit c0b95bf

Browse files
chuenchen309claude
andauthored
Stop estimate_tokens from double-counting text content (#868)
estimate_tokens added message.all_text() and then serialized every content block (including text) with model_dump_json(), so text was counted twice and a pure-text message estimated at ~2x its real size. The docstring says it counts "text plus serialized non-text payloads"; skip content already covered by all_text() in the per-content loop so text is counted once, while non-text payloads are still serialized. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 34301b4 commit c0b95bf

2 files changed

Lines changed: 12 additions & 0 deletions

File tree

src/fast_agent/history/compaction.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from fast_agent.constants import FAST_AGENT_COMPACTION_CHANNEL
2424
from fast_agent.core.logging.logger import get_logger
2525
from fast_agent.event_progress import ProgressAction
26+
from fast_agent.mcp.helpers.content_helpers import get_text
2627
from fast_agent.mcp.prompt import Prompt
2728
from fast_agent.types.llm_stop_reason import LlmStopReason
2829

@@ -185,6 +186,10 @@ def estimate_tokens(messages: list[PromptMessageExtended]) -> int:
185186
for message in messages:
186187
chars += len(message.all_text())
187188
for content in message.content:
189+
if get_text(content) is not None:
190+
# Text is already counted via all_text() above; only serialize
191+
# non-text payloads here (matches the "non-text" docstring).
192+
continue
188193
try:
189194
chars += len(content.model_dump_json())
190195
except Exception:

tests/unit/fast_agent/history/test_compaction.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from fast_agent.config import CompactionSettings, Settings, get_settings
1717
from fast_agent.context import Context
1818
from fast_agent.history.compaction import (
19+
_CHARS_PER_TOKEN,
1920
DEFAULT_COMPACTION_PROMPT,
2021
FAST_AGENT_COMPACTION_CHANNEL,
2122
CompactionSkipped,
@@ -359,6 +360,12 @@ def test_counts_non_text_content_and_channels(self):
359360

360361
assert estimate_tokens([msg]) > plain + 5_000
361362

363+
def test_does_not_double_count_text(self):
364+
# Text is counted once via all_text(); the per-content loop must skip it
365+
# (docstring: "Count text plus serialized non-text payloads").
366+
text = "x" * 1200
367+
assert estimate_tokens([_user(text)]) == len(text) // _CHARS_PER_TOKEN
368+
362369

363370
class _FakeLLM:
364371
def __init__(self, summary: str = "SUMMARY OF WORK") -> None:

0 commit comments

Comments
 (0)