Class TokenCounter
This is an estimate, not a real tokenizer. It does
not load byte-pair encoding (BPE) merges, SentencePiece, or any
provider's exact vocabulary — no dependency, no embedded vocabulary. Instead
it approximates GPT-style BPE segmentation with a handful of cheap structural
rules, so its counts land close to a provider's billed counts
(typically within roughly ±15% for ordinary English prose, further off
for code, non-Latin scripts, or long runs of punctuation). It exists so
MockServer can populate plausible usage numbers when a mocked
completion omits them, back the rough token-quota accounting, and drive
subword-granular streaming deltas.
Segmentation heuristic (BPE approximation)
The text is walked once and split into runs, each scored/segmented by class:- Words (letters/digits, with
- ' _treated as word-internal soopen-sourcestays one word): a leading space is absorbed (real BPE merges a leading space into the following token), the word is split atlower→Uppercase boundaries and letter↔digit boundaries (soChatGPT→Chat,GPT), and each resulting sub-run costs1token plus one extra per ~5 characters beyond the first five (so short/common words stay whole while long words split into subword units). - CJK (Han/Hangul ~1.2 tokens/char, Kana ~0.5): scripts that tokenizers split far more densely than Latin text.
- Punctuation/symbols: ~1 token each — BPE tends to split these into their own tokens.
- Whitespace: a plain space is free (absorbed into the next word); each newline/tab costs ~1 token.
1 for any non-empty text (every real
request costs at least one token) and 0 for null/empty.
The methods are pure and side-effect free: the same input always yields the same result, so they never make a test flaky.
-
Method Summary
Modifier and TypeMethodDescriptionstatic intestimateCompletionTokens(String text, List<ToolUse> toolCalls) Estimate the approximate completion (output) token count for the text and tool-call arguments a mocked completion would return.static intestimatePromptTokens(ParsedConversation conversation) Estimate the approximate prompt (input) token count for a decoded conversation: the sum of the per-message text estimates plus a small fixed per-message overhead (real chat formats wrap each message in role markers / delimiters that cost a few tokens).static intestimateTokens(String text) Estimate the approximate token count for a single piece of text using the BPE-approximating segmentation described in the class javadoc.segmentForStreaming(String text) Segment text into subword-sized, concatenation-exact pieces approximating BPE tokens for streaming.streamingTextTokens(String text, StreamingPhysics physics) Split completion text into the ordered list of streamed delta strings for a streaming response, honouring the streaming physics'subwordStreamingmode.
-
Method Details
-
estimateTokens
Estimate the approximate token count for a single piece of text using the BPE-approximating segmentation described in the class javadoc.- Parameters:
text- the text to estimate (may benull)- Returns:
0fornull/empty text, otherwise an approximate token count>= 1
-
estimatePromptTokens
Estimate the approximate prompt (input) token count for a decoded conversation: the sum of the per-message text estimates plus a small fixed per-message overhead (real chat formats wrap each message in role markers / delimiters that cost a few tokens). Tool-call arguments and tool results carried on a message are included in its text estimate.- Parameters:
conversation- the decoded conversation (may benull)- Returns:
0for anull/empty conversation, otherwise an approximate prompt token count>= 1
-
estimateCompletionTokens
Estimate the approximate completion (output) token count for the text and tool-call arguments a mocked completion would return.- Parameters:
text- the response text (may benull)toolCalls- the response tool calls (may benull/empty)- Returns:
- an approximate completion token count (
0when there is no output at all)
-
streamingTextTokens
Split completion text into the ordered list of streamed delta strings for a streaming response, honouring the streaming physics'subwordStreamingmode.When
subwordStreamingis on — which is now the default for anullphysics or an unset flag — the text is segmented into subword-sized pieces viasegmentForStreaming(String), giving finer, more realistic per-token deltas. Only an explicitsubwordStreaming=falseselects the legacy whitespace-boundary split (each word and each whitespace run its own delta). Either way the concatenation of the returned pieces equals the input text exactly, and every piece is non-empty.- Parameters:
text- the completion text (may benull/empty)physics- the streaming physics (may benull)- Returns:
- the ordered, non-empty delta pieces (empty list for
null/empty text)
-
segmentForStreaming
Segment text into subword-sized, concatenation-exact pieces approximating BPE tokens for streaming. A leading space is absorbed into the following word's first piece (BPE-style), words are chunked into 4-character subword units, CJK and punctuation characters are emitted individually. The pieces joined in order equal the input exactly.- Parameters:
text- the text to segment (may benull/empty)- Returns:
- the ordered, non-empty pieces (empty list for
null/empty text)
-