(text: string)
| 42 | * tracks real BPE much better than a flat divisor without needing the vocab. |
| 43 | */ |
| 44 | export function heuristicTokens(text: string): number { |
| 45 | if (!text) return 0; |
| 46 | // Count alphanumeric runs (≈ 1-2 tokens each for long words) + standalone |
| 47 | // punctuation/symbols (≈ 1 token each) + whitespace runs (cheap). |
| 48 | let tokens = 0; |
| 49 | const wordRuns = text.match(/[A-Za-z0-9_]+/g); |
| 50 | if (wordRuns) { |
| 51 | for (const w of wordRuns) { |
| 52 | // BPE splits long identifiers; ~4 chars per sub-token is a good code average. |
| 53 | tokens += Math.max(1, Math.ceil(w.length / 4)); |
| 54 | } |
| 55 | } |
| 56 | // Non-word, non-space chars (operators, brackets, punctuation) ≈ 1 token each. |
| 57 | const punct = text.match(/[^\sA-Za-z0-9_]/g); |
| 58 | if (punct) tokens += punct.length; |
| 59 | // Newlines are usually their own token in code. |
| 60 | const newlines = text.match(/\n/g); |
| 61 | if (newlines) tokens += Math.ceil(newlines.length * 0.5); |
| 62 | return Math.max(1, tokens); |
| 63 | } |
| 64 | |
| 65 | /** Kick off the lazy load of gpt-tokenizer. Safe to call repeatedly. */ |
| 66 | async function ensureEncoder(): Promise<void> { |
no outgoing calls
no test coverage detected