Skip to content

[LHF-04] Naive character-based token estimation logic #110

Description

@sheepdestroyer

Overview

The estimate_prompt_tokens function uses a simplistic character-count heuristic (len(content) // 4) to estimate token counts. While this provides a rough approximation for English prose, it fails significantly for code (heavy punctuation), multi-byte Unicode strings (CJK, emojis), and whitespace-padded content, compromising context-window clamping accuracy and routing metrics.

Location

File:router/main.pyestimate_prompt_tokens(), Lines 68–85

Current Code

defestimate_prompt_tokens(body: dict) ->int:
"""Estimate prompt tokens by counting characters in message contents (1 token ~= 4 chars) to avoid inflating metrics with large tool/schema declarations. """tokens=0formsginbody.get("messages", []):
ifnotisinstance(msg, dict):
continuecontent=msg.get("content") or""ifisinstance(content, str):
tokens+=len(content) //4elifisinstance(content, list):
forblockincontent:
ifisinstance(block, dict) andblock.get("type") =="text":
tokens+=len(block.get("text") or"") //4tokens+=50returnmax(1, tokens)

Problems

Content TypeActual Tokenslen//4 EstimateError
English prose~100~100✅ ~0%
Python code~100~60❌ -40% (punctuation is individually tokenized)
CJK text (Chinese/Japanese)~50~75❌ +50% (multi-byte chars inflate len())
Whitespace-padded JSON~100~200❌ +100% (spaces count as chars but not tokens)
System emojis~10~40❌ +300% (4-byte chars)

Impact

  • Severity: 🟡 Medium (Accuracy)
  • Under-estimation for code prompts → context window overflows not caught by the router
  • Over-estimation for CJK → unnecessarily routing to larger models
  • Metrics dashboard shows inaccurate token counts, misleading cost analysis

Recommendations

Option A: Word-count baseline (Zero dependency, better accuracy)

A word-count heuristic provides tighter tracking than character division for mixed content:

defestimate_prompt_tokens(body: dict) ->int:
"""Estimate prompt tokens using word-count heuristic. Average English: ~1.3 tokens/word. Code: ~1.5 tokens/word. We use 1.3 as a conservative multiplier. """tokens=0formsginbody.get("messages", []):
ifnotisinstance(msg, dict):
continuecontent=msg.get("content") or""ifisinstance(content, str):
words=len(content.split())
tokens+=int(words*1.3)
elifisinstance(content, list):
forblockincontent:
ifisinstance(block, dict) andblock.get("type") =="text":
text=block.get("text") or""tokens+=int(len(text.split()) *1.3)
tokens+=50# metadata overheadreturnmax(1, tokens)

Option B: Hybrid heuristic (Best accuracy without dependencies)

Combine character and word counting for a more robust estimate:

defestimate_prompt_tokens(body: dict) ->int:
tokens=0formsginbody.get("messages", []):
ifnotisinstance(msg, dict):
continuecontent=msg.get("content") or""ifisinstance(content, str):
# Average of char-based and word-based estimateschar_est=len(content) //4word_est=int(len(content.split()) *1.3)
tokens+= (char_est+word_est) //2elifisinstance(content, list):
forblockincontent:
ifisinstance(block, dict) andblock.get("type") =="text":
text=block.get("text") or""char_est=len(text) //4word_est=int(len(text.split()) *1.3)
tokens+= (char_est+word_est) //2tokens+=50returnmax(1, tokens)

Option C: tiktoken (Most accurate, adds dependency)

For production-grade accuracy, use OpenAI's tiktoken library:

importtiktoken_enc=tiktoken.get_encoding("cl100k_base") # GPT-4 / Claude compatibledefestimate_prompt_tokens(body: dict) ->int:
tokens=0formsginbody.get("messages", []):
content=msg.get("content") or""ifisinstance(content, str):
tokens+=len(_enc.encode(content))
tokens+=50returnmax(1, tokens)

Note: tiktoken adds a dependency but provides exact tokenizer-aligned counts. Consider the tradeoff vs. zero-dependency heuristics.

Acceptance Criteria

  • Token estimation accuracy is within ±20% for code, prose, and mixed content
  • Multi-byte Unicode inputs do not produce wildly inflated estimates
  • The function remains fast (sub-millisecond for typical message arrays)
  • No new hard dependencies unless explicitly approved
  • Existing tests pass and dashboard metrics remain consistent

Metadata

Metadata

Assignees

No one assigned

    Labels

    code-qualityCode quality improvementenhancementNew feature or requestjulesIssues for Jules

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions