From 0d3dd48fbbf2ad746e69d12a98c86993e68c6f68 Mon Sep 17 00:00:00 2001 From: Liyang Zhu Date: Thu, 22 Oct 2026 20:54:43 -0400 Subject: [PATCH 1/4] fix: keep brand casing and dotted versions in model display names --- src/utils/format-model-name.ts | 38 +++++++++++++++++++++++++++++++--- test/format-model-name.test.ts | 7 +++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/utils/format-model-name.ts b/src/utils/format-model-name.ts index 9e93ef7..063cadc 100644 --- a/src/utils/format-model-name.ts +++ b/src/utils/format-model-name.ts @@ -11,6 +11,17 @@ function stripVersionSuffix(part: string): string { return part.replace(/@(default|\d{6,8})$/i, '') } +/** + * Like `looksLikeVersionComponent`, but tolerates a single leading + * version letter: `v3`, `k2`, `m2`. Dotted ids like `deepseek-v3.2` + * or `kimi-k2.5` have their version split apart by the tokenizer, + * and the letter-prefixed half must still merge back with the digits + * after the dot. + */ +function looksLikePrefixedVersionComponent(token: string): boolean { + return /^[a-z]?\d{1,2}$/i.test(token) +} + /** * Heuristic: does this numeric token look like a real version component * (single or double digit) rather than a date stamp or build number? @@ -31,6 +42,11 @@ function looksLikeVersionComponent(token: string): boolean { * version pair, but the formatter would otherwise render it as * "Claude Opus 4 5". * + * The left-hand token may also carry a single version letter (`v3`, + * `k2`, `m2`): ids like `deepseek-v3.2` or `kimi-k2.5` get their dot + * split apart by the tokenizer and must re-join as `v3.2` / `k2.5`, + * never render as "DeepSeek V3 2" / "Kimi K2 5". + * * Conservative on purpose: * - Only the FIRST mergeable pair scanning from the right is collapsed: * `gpt-3-5-turbo-16k` merges `3-5` into `3.5` and stops there. @@ -49,7 +65,7 @@ function joinTrailingVersionPair(tokens: string[]): string[] { for (let i = tokens.length - 1; i >= 1; i--) { const a = tokens[i - 1] const b = tokens[i] - if (looksLikeVersionComponent(a) && looksLikeVersionComponent(b)) { + if (looksLikePrefixedVersionComponent(a) && looksLikeVersionComponent(b)) { const prev = tokens[i - 2] const next = tokens[i + 1] const prevIsShortNumeric = prev !== undefined && looksLikeVersionComponent(prev) @@ -101,7 +117,11 @@ export function categorizeModel(model: LiteLLMModel): 'chat' | 'embedding' | 'im * - Collapse a trailing pair of bare numeric tokens into a single * dotted version. Anthropic uses dash-only ids — `claude-opus-4-7` * would otherwise render as "Claude Opus 4 7" instead of - * "Claude Opus 4.7". + * "Claude Opus 4.7". The left-hand token may carry a version + * letter, so `deepseek-v3.2` renders "DeepSeek V3.2", not + * "Deepseek V3 2". + * - Keep the official casing of known brand names (GLM, DeepSeek, + * MiniMax) instead of generic title-case. * * Examples: * "openai/gpt-4o-mini" -> "GPT 4o Mini" @@ -110,6 +130,10 @@ export function categorizeModel(model: LiteLLMModel): 'chat' | 'embedding' | 'im * "claude-sonnet-4-6@default" -> "Claude Sonnet 4.6" * "bedrock/amazon.nova-pro-v1" -> "Amazon Nova Pro V1" * "qwen/qwen3-30b-a3b" -> "Qwen3 30B A3B" + * "litellm/deepseek-v3.2" -> "DeepSeek V3.2" + * "litellm/glm-5.3" -> "GLM 5.3" + * "litellm/kimi-k2.5" -> "Kimi K2.5" + * "litellm/minimax-m2.5" -> "MiniMax M2.5" */ export function formatModelName(model: LiteLLMModel): string { const { id } = model @@ -124,10 +148,17 @@ export function formatModelName(model: LiteLLMModel): string { const modelPart = stripVersionSuffix(afterProvider) const acronyms = new Set([ - 'gpt', 'oss', 'api', 'gguf', 'ggml', 'nomic', 'vl', 'it', 'mlx', + 'gpt', 'glm', 'oss', 'api', 'gguf', 'ggml', 'nomic', 'vl', 'it', 'mlx', 'llm', 'ai', 'sdk', 'aws', 'gcp', 'tts', 'stt', 'mm', ]) + // Mixed-case brand names that generic title-case would mangle + // ("Deepseek", "Minimax"). + const brandNames: Record = { + deepseek: 'DeepSeek', + minimax: 'MiniMax', + } + const rawTokens = modelPart .split(/[-_.]/) // split on -, _ and . .filter(Boolean) @@ -138,6 +169,7 @@ export function formatModelName(model: LiteLLMModel): string { // when both sides are pure digits). const tokens = joinTrailingVersionPair(rawTokens).map((token) => { const lower = token.toLowerCase() + if (brandNames[lower]) return brandNames[lower] if (acronyms.has(lower)) return token.toUpperCase() // sizes (30b, 7b, 4k...) if (/^\d+[bkmg]$/i.test(token)) return token.toUpperCase() diff --git a/test/format-model-name.test.ts b/test/format-model-name.test.ts index 0beb61a..2bca5ba 100644 --- a/test/format-model-name.test.ts +++ b/test/format-model-name.test.ts @@ -15,6 +15,13 @@ describe('formatModelName', () => { ['claude-sonnet-4-6@default', 'Claude Sonnet 4.6'], ['bedrock/amazon.nova-pro-v1', 'Amazon Nova Pro V1'], ['qwen/qwen3-30b-a3b', 'Qwen3 30B A3B'], + ['gemini/gemini-2.5-flash', 'Gemini 2.5 Flash'], + ['openai/whisper-large-v3', 'Whisper Large V3'], + ['gpt/dall-e-3', 'Dall E 3'], + ['litellm/deepseek-v3.2', 'DeepSeek V3.2'], + ['litellm/glm-5.3', 'GLM 5.3'], + ['litellm/kimi-k2.5', 'Kimi K2.5'], + ['litellm/minimax-m2.5', 'MiniMax M2.5'], ] it.each(cases)('formats %s as %s', (id, expected) => { From 5cbd188295ce34647394c0f0321afe545bc8a938 Mon Sep 17 00:00:00 2001 From: Liyang Zhu Date: Thu, 22 Oct 2026 21:39:00 -0400 Subject: [PATCH 2/4] fix: prefer pure version pairs over letter-prefixed ones in display names --- src/utils/format-model-name.ts | 16 ++++++++++++++-- test/format-model-name.test.ts | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/utils/format-model-name.ts b/src/utils/format-model-name.ts index 063cadc..58d4967 100644 --- a/src/utils/format-model-name.ts +++ b/src/utils/format-model-name.ts @@ -46,6 +46,12 @@ function looksLikeVersionComponent(token: string): boolean { * `k2`, `m2`): ids like `deepseek-v3.2` or `kimi-k2.5` get their dot * split apart by the tokenizer and must re-join as `v3.2` / `k2.5`, * never render as "DeepSeek V3 2" / "Kimi K2 5". + * Such pairs are lower confidence than pure numeric ones, so they + * only merge when the id contains no pure numeric pair — otherwise a + * quantization token like `q4_0` (tokenised as `q4` + `0`) would + * steal the merge and `llama-3.1-8b-instruct-q4_0` would render + * "Llama 3 1 8B Instruct Q4.0" instead of "Llama 3.1 8B Instruct + * Q4 0". * * Conservative on purpose: * - Only the FIRST mergeable pair scanning from the right is collapsed: @@ -62,6 +68,7 @@ function looksLikeVersionComponent(token: string): boolean { * not "Claude Opus 4 5 20251101". */ function joinTrailingVersionPair(tokens: string[]): string[] { + let prefixedMerge: string[] | undefined for (let i = tokens.length - 1; i >= 1; i--) { const a = tokens[i - 1] const b = tokens[i] @@ -75,11 +82,16 @@ function joinTrailingVersionPair(tokens: string[]): string[] { (next === undefined || nextIsDateStamp || !/^\d+$/.test(next)) ) { const merged = [...tokens.slice(0, i - 1), `${a}.${b}`, ...tokens.slice(i + 1)] - return merged + if (looksLikeVersionComponent(a)) { + return merged + } + if (prefixedMerge === undefined) { + prefixedMerge = merged + } } } } - return tokens + return prefixedMerge ?? tokens } /** diff --git a/test/format-model-name.test.ts b/test/format-model-name.test.ts index 2bca5ba..6507f2e 100644 --- a/test/format-model-name.test.ts +++ b/test/format-model-name.test.ts @@ -18,6 +18,7 @@ describe('formatModelName', () => { ['gemini/gemini-2.5-flash', 'Gemini 2.5 Flash'], ['openai/whisper-large-v3', 'Whisper Large V3'], ['gpt/dall-e-3', 'Dall E 3'], + ['llama-3.1-8b-instruct-q4_0', 'Llama 3.1 8B Instruct Q4 0'], ['litellm/deepseek-v3.2', 'DeepSeek V3.2'], ['litellm/glm-5.3', 'GLM 5.3'], ['litellm/kimi-k2.5', 'Kimi K2.5'], From 4fc77f0d8d7f982f138d70c3c54e771eee657af8 Mon Sep 17 00:00:00 2001 From: Liyang Zhu Date: Tue, 27 Oct 2026 15:27:25 -0400 Subject: [PATCH 3/4] fix: pair only known version letters in model display names --- src/utils/format-model-name.ts | 31 ++++++++++++------------------- test/format-model-name.test.ts | 1 + 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/utils/format-model-name.ts b/src/utils/format-model-name.ts index 58d4967..a9dae20 100644 --- a/src/utils/format-model-name.ts +++ b/src/utils/format-model-name.ts @@ -13,13 +13,14 @@ function stripVersionSuffix(part: string): string { /** * Like `looksLikeVersionComponent`, but tolerates a single leading - * version letter: `v3`, `k2`, `m2`. Dotted ids like `deepseek-v3.2` - * or `kimi-k2.5` have their version split apart by the tokenizer, - * and the letter-prefixed half must still merge back with the digits - * after the dot. + * version letter — only the letters seen in real version positions + * (`v3`, `k2`, `m2`), so quantization prefixes like `q4` never pair. + * Dotted ids like `deepseek-v3.2` or `kimi-k2.5` have their version + * split apart by the tokenizer, and the letter-prefixed half must + * still merge back with the digits after the dot. */ function looksLikePrefixedVersionComponent(token: string): boolean { - return /^[a-z]?\d{1,2}$/i.test(token) + return /^[vkm]?\d{1,2}$/i.test(token) } /** @@ -46,12 +47,10 @@ function looksLikeVersionComponent(token: string): boolean { * `k2`, `m2`): ids like `deepseek-v3.2` or `kimi-k2.5` get their dot * split apart by the tokenizer and must re-join as `v3.2` / `k2.5`, * never render as "DeepSeek V3 2" / "Kimi K2 5". - * Such pairs are lower confidence than pure numeric ones, so they - * only merge when the id contains no pure numeric pair — otherwise a - * quantization token like `q4_0` (tokenised as `q4` + `0`) would - * steal the merge and `llama-3.1-8b-instruct-q4_0` would render - * "Llama 3 1 8B Instruct Q4.0" instead of "Llama 3.1 8B Instruct - * Q4 0". + * Quantization prefixes never pair (`q4_0` tokenises as `q4` + `0`, + * but `q` is not a version letter), so `mistral-7b-instruct-q4_0` + * renders "Mistral 7B Instruct Q4 0" while ids with a real version + * pair like `llama-3.1-8b-instruct-q4_0` keep merging `3.1`. * * Conservative on purpose: * - Only the FIRST mergeable pair scanning from the right is collapsed: @@ -68,7 +67,6 @@ function looksLikeVersionComponent(token: string): boolean { * not "Claude Opus 4 5 20251101". */ function joinTrailingVersionPair(tokens: string[]): string[] { - let prefixedMerge: string[] | undefined for (let i = tokens.length - 1; i >= 1; i--) { const a = tokens[i - 1] const b = tokens[i] @@ -82,16 +80,11 @@ function joinTrailingVersionPair(tokens: string[]): string[] { (next === undefined || nextIsDateStamp || !/^\d+$/.test(next)) ) { const merged = [...tokens.slice(0, i - 1), `${a}.${b}`, ...tokens.slice(i + 1)] - if (looksLikeVersionComponent(a)) { - return merged - } - if (prefixedMerge === undefined) { - prefixedMerge = merged - } + return merged } } } - return prefixedMerge ?? tokens + return tokens } /** diff --git a/test/format-model-name.test.ts b/test/format-model-name.test.ts index 6507f2e..c8990a3 100644 --- a/test/format-model-name.test.ts +++ b/test/format-model-name.test.ts @@ -19,6 +19,7 @@ describe('formatModelName', () => { ['openai/whisper-large-v3', 'Whisper Large V3'], ['gpt/dall-e-3', 'Dall E 3'], ['llama-3.1-8b-instruct-q4_0', 'Llama 3.1 8B Instruct Q4 0'], + ['mistral-7b-instruct-q4_0', 'Mistral 7B Instruct Q4 0'], ['litellm/deepseek-v3.2', 'DeepSeek V3.2'], ['litellm/glm-5.3', 'GLM 5.3'], ['litellm/kimi-k2.5', 'Kimi K2.5'], From 049987261cddaa3a163250b8a0eec6d1ffc9bbd2 Mon Sep 17 00:00:00 2001 From: Liyang Zhu Date: Tue, 27 Oct 2026 15:35:54 -0400 Subject: [PATCH 4/4] test: drop dall-e-3 case that locks in unintended display name --- test/format-model-name.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/format-model-name.test.ts b/test/format-model-name.test.ts index c8990a3..fdb5cbb 100644 --- a/test/format-model-name.test.ts +++ b/test/format-model-name.test.ts @@ -17,7 +17,6 @@ describe('formatModelName', () => { ['qwen/qwen3-30b-a3b', 'Qwen3 30B A3B'], ['gemini/gemini-2.5-flash', 'Gemini 2.5 Flash'], ['openai/whisper-large-v3', 'Whisper Large V3'], - ['gpt/dall-e-3', 'Dall E 3'], ['llama-3.1-8b-instruct-q4_0', 'Llama 3.1 8B Instruct Q4 0'], ['mistral-7b-instruct-q4_0', 'Mistral 7B Instruct Q4 0'], ['litellm/deepseek-v3.2', 'DeepSeek V3.2'],