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:
SuYao
2026-04-25 20:23:26 +08:00
committed by GitHub
parent 63be624f7c
commit 1ae68c91de
8 changed files with 102 additions and 15 deletions

View File

@@ -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)
})
})

View File

@@ -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', () => {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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',

View File

@@ -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.

View File

@@ -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)
}
/**

View File

@@ -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-]+)?',