mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-06 05:55:28 +08:00
hotfix(models): disable temperature and top_p for Kimi K2.6 (#14580)
### What this PR does Before this PR: - `isKimi25Model` only matched `kimi-k2.5`, so the Moonshot provider's Kimi K2.6 model was treated as supporting arbitrary `temperature` / `top_p`. Sending any value other than `1` / `0.95` causes the Moonshot API to reject the request. After this PR: - The helper is renamed to `isKimi25OrNewerModel` and uses a forward-looking regex (`/kimi-k(?:2\.[5-9]\d*|[3-9]\d*)/`) that matches Kimi K2.5+ (K2.5, K2.6, K2.7, ...) as well as K3+ (K3, K3.x, K4, ...), while still excluding older K2 variants such as `kimi-k2`, `kimi-k2-thinking`, and `kimi-k2-0711-preview`. - `isSupportTemperatureModel` and `isSupportTopPModel` therefore return `false` for K2.6 (and future K2.x / K3+), so the UI hides the sliders and the inference pipeline does not forward custom values. - `_isKimiReasoningModel` and `_isSupportedThinkingTokenKimiModel` also recognize these versions so reasoning-effort / thinking-token options stay consistent across the family. - Unit tests added in `utils.test.ts` and `reasoning.test.ts` covering K2.5, K2.6, K2.7, K3, K3.5, K4, plus negative cases for older K2 variants. Fixes #14573 ### Why we need it and why it was done in this way Moonshot's Kimi K2.5 introduced a fixed `temperature=1` / `top_p=0.95` constraint. K2.6 inherits the same constraint, and based on the naming pattern it is reasonable to assume future K2.x and K3+ revisions will behave the same. Extending the existing helper (rather than enumerating model IDs) avoids having to ship yet another hotfix every time Moonshot bumps the version number. The following tradeoffs were made: - Preemptively covers K2.7+, K3+ even though they are not released yet. If Moonshot changes the sampling policy for a future model, the regex will need to be tightened — this is an accepted trade-off to avoid repeated hotfixes. The following alternatives were considered: - Matching only K2.5 and K2.6 explicitly (`/kimi-k2\.[56]/`). Rejected because it requires a new hotfix for every future K2.x / K3+ model. - Introducing a separate `isKimiFixedSamplingModel` helper. Rejected because K2.5 and K2.6 behave identically to how the existing check is used and splitting would duplicate call sites. Links to places where the discussion took place: N/A ### Breaking changes None. ### Special notes for your reviewer The function rename (`isKimi25Model` → `isKimi25OrNewerModel`) is purely local to `src/renderer/src/config/models/` — no exports cross package boundaries. ### Checklist - [x] PR: The PR description is expressive enough and will help future contributors - [x] Code: Write code that humans can understand and Keep it simple - [x] Refactor: You have left the code cleaner than you found it (Boy Scout Rule) - [x] Upgrade: Impact of this change on upgrade flows was considered and addressed if required - [x] Documentation: A user-guide update was considered and is not required (no new user-facing surface; just a bug fix in model-capability detection). - [x] Self-review: I have reviewed my own code before requesting review from others ### Release note ```release-note Fix Moonshot Kimi K2.6 requests being rejected by the provider due to unsupported temperature / top_p values. ``` --------- Signed-off-by: suyao <sy20010504@gmail.com> Co-authored-by: ousugo <dkzyxh@gmail.com>
This commit is contained in:
@@ -2562,6 +2562,14 @@ describe('isInterleavedThinkingModel', () => {
|
||||
expect(isInterleavedThinkingModel(createModel({ id: 'kimi-k2.5' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true for kimi-k2.6', () => {
|
||||
expect(isInterleavedThinkingModel(createModel({ id: 'kimi-k2.6' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true for kimi-k2.6 variants', () => {
|
||||
expect(isInterleavedThinkingModel(createModel({ id: 'kimi-k2.6-preview' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false for other kimi models', () => {
|
||||
expect(isInterleavedThinkingModel(createModel({ id: 'kimi-k2' }))).toBe(false)
|
||||
expect(isInterleavedThinkingModel(createModel({ id: 'kimi-k2-preview' }))).toBe(false)
|
||||
@@ -2691,14 +2699,27 @@ describe('Kimi Models', () => {
|
||||
expect(isKimiReasoningModel(createModel({ id: 'kimi-k2.5' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should recognize kimi-k2.6', () => {
|
||||
expect(isKimiReasoningModel(createModel({ id: 'kimi-k2.6' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should recognize future K2.x and K3+ variants', () => {
|
||||
expect(isKimiReasoningModel(createModel({ id: 'kimi-k2.7' }))).toBe(true)
|
||||
expect(isKimiReasoningModel(createModel({ id: 'kimi-k3' }))).toBe(true)
|
||||
expect(isKimiReasoningModel(createModel({ id: 'kimi-k3.5' }))).toBe(true)
|
||||
expect(isKimiReasoningModel(createModel({ id: 'kimi-k4' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle model IDs with slashes', () => {
|
||||
expect(isKimiReasoningModel(createModel({ id: 'moonshot/kimi-k2-thinking' }))).toBe(true)
|
||||
expect(isKimiReasoningModel(createModel({ id: 'moonshot/kimi-k2.5' }))).toBe(true)
|
||||
expect(isKimiReasoningModel(createModel({ id: 'moonshot/kimi-k2.6' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle case insensitivity', () => {
|
||||
expect(isKimiReasoningModel(createModel({ id: 'KIMI-K2-THINKING' }))).toBe(true)
|
||||
expect(isKimiReasoningModel(createModel({ id: 'Kimi-K2.5' }))).toBe(true)
|
||||
expect(isKimiReasoningModel(createModel({ id: 'Kimi-K2.6' }))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2751,14 +2772,28 @@ describe('Kimi Models', () => {
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'kimi-k2.5' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should recognize kimi-k2.6', () => {
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'kimi-k2.6' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should recognize future K2.x and K3+ variants', () => {
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'kimi-k2.7' }))).toBe(true)
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'kimi-k3' }))).toBe(true)
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'kimi-k3.5' }))).toBe(true)
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'kimi-k4' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle model IDs with provider prefixes', () => {
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'moonshot/kimi-k2.5' }))).toBe(true)
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'openrouter/kimi-k2.5' }))).toBe(true)
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'moonshot/kimi-k2.6' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle case insensitivity', () => {
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'KIMI-K2.5' }))).toBe(true)
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'Kimi-K2.5' }))).toBe(true)
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'KIMI-K2.6' }))).toBe(true)
|
||||
expect(isSupportedThinkingTokenKimiModel(createModel({ id: 'Kimi-K2.6' }))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -139,6 +139,7 @@ describe('isFunctionCallingModel', () => {
|
||||
it('supports kimi models through kimi-k2 regex match', () => {
|
||||
expect(isFunctionCallingModel(createModel({ id: 'kimi-k2-0711-preview', provider: 'moonshot' }))).toBe(true)
|
||||
expect(isFunctionCallingModel(createModel({ id: 'kimi-k2', provider: 'kimi' }))).toBe(true)
|
||||
expect(isFunctionCallingModel(createModel({ id: 'kimi-k2.6', provider: 'moonshot' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('supports deepseek models through deepseek regex match', () => {
|
||||
|
||||
@@ -220,6 +220,26 @@ describe('model utils', () => {
|
||||
expect(isSupportTemperatureModel(qwenMt)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for Kimi K2.5+ and K3+ models', () => {
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k2.5' }))).toBe(false)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'Kimi-K2.5' }))).toBe(false)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'moonshot/kimi-k2.5' }))).toBe(false)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k2.6' }))).toBe(false)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'Kimi-K2.6' }))).toBe(false)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'moonshot/kimi-k2.6' }))).toBe(false)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k2.7' }))).toBe(false)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k3' }))).toBe(false)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k3.5' }))).toBe(false)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k4' }))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true for older Kimi models', () => {
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k2' }))).toBe(true)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k2-thinking' }))).toBe(true)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k2-0711-preview' }))).toBe(true)
|
||||
expect(isSupportTemperatureModel(createModel({ id: 'kimi-k2-turbo-preview' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for null/undefined models', () => {
|
||||
expect(isSupportTemperatureModel(null)).toBe(false)
|
||||
expect(isSupportTemperatureModel(undefined)).toBe(false)
|
||||
@@ -253,6 +273,26 @@ describe('model utils', () => {
|
||||
expect(isSupportTopPModel(qwenMt)).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for Kimi K2.5+ and K3+ models', () => {
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k2.5' }))).toBe(false)
|
||||
expect(isSupportTopPModel(createModel({ id: 'Kimi-K2.5' }))).toBe(false)
|
||||
expect(isSupportTopPModel(createModel({ id: 'moonshot/kimi-k2.5' }))).toBe(false)
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k2.6' }))).toBe(false)
|
||||
expect(isSupportTopPModel(createModel({ id: 'Kimi-K2.6' }))).toBe(false)
|
||||
expect(isSupportTopPModel(createModel({ id: 'moonshot/kimi-k2.6' }))).toBe(false)
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k2.7' }))).toBe(false)
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k3' }))).toBe(false)
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k3.5' }))).toBe(false)
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k4' }))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true for older Kimi models', () => {
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k2' }))).toBe(true)
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k2-thinking' }))).toBe(true)
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k2-0711-preview' }))).toBe(true)
|
||||
expect(isSupportTopPModel(createModel({ id: 'kimi-k2-turbo-preview' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for null/undefined models', () => {
|
||||
expect(isSupportTopPModel(null)).toBe(false)
|
||||
expect(isSupportTopPModel(undefined)).toBe(false)
|
||||
|
||||
@@ -343,6 +343,8 @@ describe('isVisionModel', () => {
|
||||
it('should return true for kimi models', () => {
|
||||
expect(isVisionModel(createModel({ id: 'kimi-k2.5' }))).toBe(true)
|
||||
expect(isVisionModel(createModel({ id: 'moonshot/kimi-k2.5' }))).toBe(true)
|
||||
expect(isVisionModel(createModel({ id: 'kimi-k2.6' }))).toBe(true)
|
||||
expect(isVisionModel(createModel({ id: 'moonshot/kimi-k2.6' }))).toBe(true)
|
||||
})
|
||||
it('should return false for kimi non-vision models', () => {
|
||||
expect(isVisionModel(createModel({ id: 'kimi-k2-thinking' }))).toBe(false)
|
||||
|
||||
@@ -732,6 +732,14 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
||||
owned_by: 'moonshot',
|
||||
capabilities: [{ type: 'text' }, { type: 'vision' }, { type: 'function_calling' }]
|
||||
},
|
||||
{
|
||||
id: 'kimi-k2.6',
|
||||
provider: 'moonshot',
|
||||
name: 'Kimi K2.6',
|
||||
group: 'Kimi K2.6',
|
||||
owned_by: 'moonshot',
|
||||
capabilities: [{ type: 'text' }, { type: 'vision' }, { type: 'function_calling' }]
|
||||
},
|
||||
{
|
||||
id: 'kimi-k2-0905-Preview',
|
||||
provider: 'moonshot',
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
isGemini3ProModel,
|
||||
isGemini31FlashLiteModel,
|
||||
isGemini31ProModel,
|
||||
isKimi25Model,
|
||||
isKimi25OrNewerModel,
|
||||
withModelIdAndNameAsId
|
||||
} from './utils'
|
||||
import { isTextToImageModel } from './vision'
|
||||
@@ -633,13 +633,13 @@ export const isSupportedThinkingTokenMiMoModel = (model: Model): boolean => {
|
||||
* Detects whether a Kimi model supports thinking control
|
||||
*
|
||||
* This function identifies Kimi models that support thinking token control.
|
||||
* Currently only supports Kimi K2.5 and its variants.
|
||||
* Currently only supports Kimi K2.5 / K2.6 and their variants.
|
||||
*
|
||||
* @param model - The model object to check
|
||||
* @returns true if the model supports thinking control, false otherwise
|
||||
*/
|
||||
const _isSupportedThinkingTokenKimiModel = (model: Model): boolean => {
|
||||
return isKimi25Model(model)
|
||||
return isKimi25OrNewerModel(model)
|
||||
}
|
||||
|
||||
export const isSupportedThinkingTokenKimiModel = (model: Model): boolean => {
|
||||
@@ -732,16 +732,15 @@ export const isBaichuanReasoningModel = (model?: Model): boolean => {
|
||||
* This function identifies Moonshot AI's Kimi series reasoning models.
|
||||
* Currently should only support:
|
||||
* - Kimi K2 Thinking and its variants (including -turbo suffix)
|
||||
* - Kimi K2.5
|
||||
* - Kimi K2.5+ (K2.5, K2.6, ...) and K3+ (K3, K3.x, K4, ...)
|
||||
*
|
||||
* @param model - The model object to check, can be undefined
|
||||
* @returns true if it's a Kimi reasoning model, false otherwise
|
||||
*/
|
||||
const _isKimiReasoningModel = (model: Model): boolean => {
|
||||
const modelId = getLowerBaseModelName(model.id, '/')
|
||||
// Match kimi-k2-thinking, kimi-k2-thinking-turbo, or kimi-k2.5
|
||||
// The regex ensures no extra suffixes after these patterns
|
||||
return /^kimi-k2-thinking(?:-turbo)?$|^kimi-k2\.5(?:-\w)*$/.test(modelId)
|
||||
// Match kimi-k2-thinking, kimi-k2-thinking-turbo, or kimi-k2.5+ / kimi-k3+
|
||||
return /^kimi-k2-thinking(?:-turbo)?$|^kimi-k(?:2\.[5-9]\d*|[3-9]\d*)(?:[.-]\w+)*$/.test(modelId)
|
||||
}
|
||||
|
||||
export function isKimiReasoningModel(model?: Model): boolean {
|
||||
@@ -887,7 +886,7 @@ export const isFixedReasoningModel = (model: Model) =>
|
||||
// https://platform.moonshot.cn/docs/guide/use-kimi-k2-thinking-model#%E5%A4%9A%E6%AD%A5%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8
|
||||
/** @deprecated No longer used. */
|
||||
const INTERLEAVED_THINKING_MODEL_REGEX =
|
||||
/minimax-m2(.(\d+))?(?:-[\w-]+)?|mimo-v2-flash|glm-5(?:.\d+)?(?:-[\w-]+)?|glm-4.(\d+)(?:-[\w-]+)?|kimi-k2-thinking?|kimi-k2.5$/i
|
||||
/minimax-m2(.(\d+))?(?:-[\w-]+)?|mimo-v2-flash|glm-5(?:.\d+)?(?:-[\w-]+)?|glm-4.(\d+)(?:-[\w-]+)?|kimi-k2-thinking?|kimi-k2\.[56](?:-[\w-]+)?$/i
|
||||
|
||||
/**
|
||||
* Determines whether the given model supports interleaved thinking.
|
||||
|
||||
@@ -81,8 +81,8 @@ export function isSupportTemperatureModel(model: Model | undefined | null, assis
|
||||
return false
|
||||
}
|
||||
|
||||
// Kimi K2.5 doesn't support custom temperature
|
||||
if (isKimi25Model(model)) {
|
||||
// Kimi K2.5 / K2.6 don't support custom temperature
|
||||
if (isKimi25OrNewerModel(model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -117,8 +117,8 @@ export function isSupportTopPModel(model: Model | undefined | null, assistant?:
|
||||
return false
|
||||
}
|
||||
|
||||
// Kimi K2.5 only accepts top_p=0.95
|
||||
if (isKimi25Model(model)) {
|
||||
// Kimi K2.5 / K2.6 only accepts top_p=0.95
|
||||
if (isKimi25OrNewerModel(model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -155,12 +155,14 @@ export function isMoonshotModel(model: Model): boolean {
|
||||
return ['moonshot', 'kimi'].some((m) => modelId.includes(m))
|
||||
}
|
||||
|
||||
export function isKimi25Model(model: Model | undefined | null): boolean {
|
||||
export function isKimi25OrNewerModel(model: Model | undefined | null): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
}
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
return modelId.includes('kimi-k2.5')
|
||||
// Match Kimi K2.5+ (K2.5, K2.6, ..., K2.99) and K3+ (K3, K3.x, K4, ...).
|
||||
// Older K2 variants (kimi-k2, kimi-k2-thinking, kimi-k2-0711-preview, ...) are excluded.
|
||||
return /kimi-k(?:2\.[5-9]\d*|[3-9]\d*)/.test(modelId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,7 +44,7 @@ const visionAllowedModels = [
|
||||
'o3(?:-[\\w-]+)?',
|
||||
'o4(?:-[\\w-]+)?',
|
||||
'deepseek-vl(?:[\\w-]+)?',
|
||||
'kimi-k2.5',
|
||||
'kimi-k2\\.[56](?:-[\\w-]+)?',
|
||||
'kimi-latest',
|
||||
'gemma-?[3-4](?:[-.\\w]+)?',
|
||||
'doubao-seed-1[.-][68](?:-[\\w-]+)?',
|
||||
|
||||
Reference in New Issue
Block a user