mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
feat(ai): upgrade thinking level control with default/auto options and broader model coverage
- Add 'default' option (no reasoning params sent, model uses native behavior) - Add 'auto' option for models that natively support it (deepseek_hybrid, kimi, doubao) - Expand model recognition with regex coverage for 15+ model families - Add Gemini 2.5+ thinking level support via native Google provider - Simplify deepseek_hybrid options to ['default', 'off', 'auto'] aligned with Cherry Studio - Use primary theme color for thinking selector instead of hardcoded blue - Update docs with thinking control architecture section Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
a31c672474
commit
783d61f742
10 files changed
+391
-173
No files matched your search
@@ -3,12 +3,19 @@ import { describe, it } from 'node:test'
|
||||
import { getSupportedThinkingLevels, getThinkingCompat, isReasoningModel } from '../thinking'
|
||||
|
||||
describe('isReasoningModel', () => {
|
||||
it('returns false for anthropic provider (out of scope v1)', () => {
|
||||
it('returns false for anthropic provider (out of scope)', () => {
|
||||
assert.equal(isReasoningModel('anthropic', 'claude-opus-4-6'), false)
|
||||
})
|
||||
|
||||
it('returns false for gemini provider (out of scope v1)', () => {
|
||||
assert.equal(isReasoningModel('gemini', 'gemini-2.5-pro'), false)
|
||||
it('returns true for gemini 2.5+ reasoning models', () => {
|
||||
assert.equal(isReasoningModel('gemini', 'gemini-2.5-pro'), true)
|
||||
assert.equal(isReasoningModel('gemini', 'gemini-2.5-flash-preview-05-20'), true)
|
||||
assert.equal(isReasoningModel('gemini', 'gemini-3-flash'), true)
|
||||
})
|
||||
|
||||
it('returns false for older gemini models', () => {
|
||||
assert.equal(isReasoningModel('gemini', 'gemini-2.0-flash'), false)
|
||||
assert.equal(isReasoningModel('gemini', 'gemini-1.5-pro'), false)
|
||||
})
|
||||
|
||||
it('returns true for qwen provider', () => {
|
||||
@@ -19,6 +26,11 @@ describe('isReasoningModel', () => {
|
||||
assert.equal(isReasoningModel('deepseek', 'deepseek-v4-pro'), true)
|
||||
})
|
||||
|
||||
it('returns true for deepseek hybrid (v3.x / chat)', () => {
|
||||
assert.equal(isReasoningModel('deepseek', 'deepseek-chat'), true)
|
||||
assert.equal(isReasoningModel('deepseek', 'deepseek-v3.1'), true)
|
||||
})
|
||||
|
||||
it('returns true for openai o-series', () => {
|
||||
assert.equal(isReasoningModel('openai', 'o3'), true)
|
||||
})
|
||||
@@ -34,6 +46,38 @@ describe('isReasoningModel', () => {
|
||||
it('returns false for non-reasoning model (gpt-4o)', () => {
|
||||
assert.equal(isReasoningModel('openai', 'gpt-4o'), false)
|
||||
})
|
||||
|
||||
it('returns false for models with -non-reasoning suffix', () => {
|
||||
assert.equal(isReasoningModel('xai', 'grok-4-fast-non-reasoning'), false)
|
||||
})
|
||||
|
||||
it('returns true for hunyuan reasoning models', () => {
|
||||
assert.equal(isReasoningModel('hunyuan', 'hunyuan-t1-latest'), true)
|
||||
assert.equal(isReasoningModel('tencent', 'hunyuan-a13b'), true)
|
||||
})
|
||||
|
||||
it('returns true for known reasoning model families', () => {
|
||||
assert.equal(isReasoningModel('minimax', 'minimax-m2'), true)
|
||||
assert.equal(isReasoningModel('baichuan', 'baichuan-m3'), true)
|
||||
assert.equal(isReasoningModel('mistral', 'mistral-small-2603'), true)
|
||||
assert.equal(isReasoningModel('step', 'step-3'), true)
|
||||
assert.equal(isReasoningModel('google', 'gemma-4-27b'), true)
|
||||
assert.equal(isReasoningModel('perplexity', 'sonar-deep-research'), true)
|
||||
assert.equal(isReasoningModel('xiaomi', 'mimo-v2-flash'), true)
|
||||
assert.equal(isReasoningModel('bytedance', 'seed-oss-36b'), true)
|
||||
})
|
||||
|
||||
it('returns true for kimi k2.5+ models', () => {
|
||||
assert.equal(isReasoningModel('kimi', 'kimi-k2.5'), true)
|
||||
assert.equal(isReasoningModel('kimi', 'kimi-k3'), true)
|
||||
})
|
||||
|
||||
it('returns true for grok reasoning variants', () => {
|
||||
assert.equal(isReasoningModel('xai', 'grok-3-mini'), true)
|
||||
assert.equal(isReasoningModel('xai', 'grok-4'), true)
|
||||
assert.equal(isReasoningModel('xai', 'grok-4-fast'), true)
|
||||
assert.equal(isReasoningModel('xai', 'grok-build'), true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSupportedThinkingLevels', () => {
|
||||
@@ -41,51 +85,110 @@ describe('getSupportedThinkingLevels', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai', 'gpt-4o'), [])
|
||||
})
|
||||
|
||||
it('returns [] for anthropic (out of scope v1)', () => {
|
||||
it('returns [] for anthropic (out of scope)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('anthropic', 'claude-opus-4-6'), [])
|
||||
})
|
||||
|
||||
it('returns off+high for qwen (on/off only)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('qwen', 'qwen3-max'), ['off', 'high'])
|
||||
it('always includes default as first option for reasoning models', () => {
|
||||
const levels = getSupportedThinkingLevels('qwen', 'qwen3-max')
|
||||
assert.equal(levels[0], 'default')
|
||||
})
|
||||
|
||||
it('returns off+high for glm (zai, same as qwen)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('glm', 'glm-5'), ['off', 'high'])
|
||||
it('returns default+off+high for qwen (on/off only)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('qwen', 'qwen3-max'), ['default', 'off', 'high'])
|
||||
})
|
||||
|
||||
it('returns off+high+xhigh for deepseek-v4', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('deepseek', 'deepseek-v4-pro'), ['off', 'high', 'xhigh'])
|
||||
it('returns default+off+high for glm (same as qwen)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('glm', 'glm-5'), ['default', 'off', 'high'])
|
||||
})
|
||||
|
||||
it('returns low+medium+high for o-series (cannot disable)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai', 'o3'), ['low', 'medium', 'high'])
|
||||
it('returns default+off+high+xhigh for deepseek-v4', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('deepseek', 'deepseek-v4-pro'), ['default', 'off', 'high', 'xhigh'])
|
||||
})
|
||||
|
||||
it('returns full range for gpt-5.2+', () => {
|
||||
assert.deepEqual(
|
||||
getSupportedThinkingLevels('openai', 'gpt-5.2'),
|
||||
['off', 'minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
)
|
||||
it('returns default+off+auto for deepseek hybrid', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('deepseek', 'deepseek-chat'), ['default', 'off', 'auto'])
|
||||
})
|
||||
|
||||
it('returns minimal+low+medium+high for gpt-5 base (cannot disable)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai', 'gpt-5'), ['minimal', 'low', 'medium', 'high'])
|
||||
it('returns default+low+medium+high for o-series (cannot disable)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai', 'o3'), ['default', 'low', 'medium', 'high'])
|
||||
})
|
||||
|
||||
it('returns off+high for self-hosted qwen3 (qwen type)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai-compatible', 'qwen3:8b'), ['off', 'high'])
|
||||
it('returns full range with default for gpt-5.2+', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai', 'gpt-5.2'), [
|
||||
'default',
|
||||
'off',
|
||||
'minimal',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'xhigh',
|
||||
])
|
||||
})
|
||||
|
||||
it('returns low+medium+high for kimi thinking models', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('kimi', 'kimi-k2-thinking'), ['low', 'medium', 'high'])
|
||||
it('returns default+minimal+low+medium+high for gpt-5 base (cannot disable)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai', 'gpt-5'), ['default', 'minimal', 'low', 'medium', 'high'])
|
||||
})
|
||||
|
||||
it('returns high only for doubao seed (no disable support)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('doubao', 'doubao-seed-2-0-pro-260215'), ['high'])
|
||||
it('returns default+off+high for self-hosted qwen3 (qwen type)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai-compatible', 'qwen3:8b'), ['default', 'off', 'high'])
|
||||
})
|
||||
|
||||
it('returns low+high for grok (cannot disable)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('xai', 'grok-4'), ['low', 'high'])
|
||||
it('returns default+off+auto+low+medium+high for kimi thinking models', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('kimi', 'kimi-k2-thinking'), [
|
||||
'default',
|
||||
'off',
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
])
|
||||
})
|
||||
|
||||
it('returns default+off+auto+high for doubao seed', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('doubao', 'doubao-seed-2-0-pro-260215'), [
|
||||
'default',
|
||||
'off',
|
||||
'auto',
|
||||
'high',
|
||||
])
|
||||
})
|
||||
|
||||
it('returns default+low+high for grok (cannot disable)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('xai', 'grok-4'), ['default', 'low', 'high'])
|
||||
})
|
||||
|
||||
it('returns default+off+high for hunyuan', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('hunyuan', 'hunyuan-t1-latest'), ['default', 'off', 'high'])
|
||||
})
|
||||
|
||||
it('returns default+off+low+medium+high for gemini 2.5+', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('gemini', 'gemini-2.5-flash-preview-05-20'), [
|
||||
'default',
|
||||
'off',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
])
|
||||
assert.deepEqual(getSupportedThinkingLevels('gemini', 'gemini-3-pro'), ['default', 'off', 'low', 'medium', 'high'])
|
||||
})
|
||||
|
||||
it('returns empty for older gemini models', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('gemini', 'gemini-2.0-flash'), [])
|
||||
})
|
||||
|
||||
it('returns default+off+auto for mimo (deepseek_hybrid type)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai-compatible', 'mimo-v2-flash'), ['default', 'off', 'auto'])
|
||||
})
|
||||
|
||||
it('returns default+off+low+medium+high for generic reasoning (fallback)', () => {
|
||||
assert.deepEqual(getSupportedThinkingLevels('openai-compatible', 'some-reasoning-model'), [
|
||||
'default',
|
||||
'off',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -98,17 +201,19 @@ describe('getThinkingCompat', () => {
|
||||
assert.deepEqual(getThinkingCompat('qwen', 'qwen3-max'), { thinkingFormat: 'qwen' })
|
||||
})
|
||||
|
||||
it('returns thinkingFormat:qwen for glm (zai)', () => {
|
||||
it('returns thinkingFormat:qwen for glm', () => {
|
||||
assert.deepEqual(getThinkingCompat('glm', 'glm-5'), { thinkingFormat: 'qwen' })
|
||||
})
|
||||
|
||||
it('returns thinkingFormat:qwen for hunyuan', () => {
|
||||
assert.deepEqual(getThinkingCompat('tencent', 'hunyuan-a13b'), { thinkingFormat: 'qwen' })
|
||||
})
|
||||
|
||||
it('returns thinkingFormat:qwen for self-hosted qwen3', () => {
|
||||
assert.deepEqual(getThinkingCompat('openai-compatible', 'qwen3:8b'), { thinkingFormat: 'qwen' })
|
||||
})
|
||||
|
||||
it('returns thinkingFormat:deepseek + thinkingLevelMap for deepseek-v4 (KEY FIX)', () => {
|
||||
// DeepSeek needs thinkingFormat:'deepseek' so pi-ai sends thinking:{type:"disabled"}
|
||||
// when 'off' is selected — NOT the generic supportsReasoningEffort path.
|
||||
it('returns thinkingFormat:deepseek + thinkingLevelMap for deepseek-v4', () => {
|
||||
const compat = getThinkingCompat('deepseek', 'deepseek-v4-pro')
|
||||
assert.equal(compat.thinkingFormat, 'deepseek')
|
||||
assert.equal(compat.supportsReasoningEffort, undefined)
|
||||
@@ -117,6 +222,14 @@ describe('getThinkingCompat', () => {
|
||||
assert.equal(compat.thinkingLevelMap?.minimal, null)
|
||||
})
|
||||
|
||||
it('returns thinkingFormat:deepseek + auto mapping for deepseek hybrid', () => {
|
||||
const compat = getThinkingCompat('deepseek', 'deepseek-chat')
|
||||
assert.equal(compat.thinkingFormat, 'deepseek')
|
||||
assert.equal(compat.thinkingLevelMap?.auto, 'auto')
|
||||
assert.equal(compat.thinkingLevelMap?.high, 'high')
|
||||
assert.equal(compat.thinkingLevelMap?.xhigh, null)
|
||||
})
|
||||
|
||||
it('returns supportsReasoningEffort for o-series', () => {
|
||||
const compat = getThinkingCompat('openai', 'o3')
|
||||
assert.equal(compat.supportsReasoningEffort, true)
|
||||
@@ -135,4 +248,28 @@ describe('getThinkingCompat', () => {
|
||||
assert.equal(compat.thinkingLevelMap?.off, 'none')
|
||||
assert.equal(compat.thinkingLevelMap?.xhigh, 'xhigh')
|
||||
})
|
||||
|
||||
it('returns supportsReasoningEffort + auto/off mappings for kimi', () => {
|
||||
const compat = getThinkingCompat('kimi', 'kimi-k2-thinking')
|
||||
assert.equal(compat.supportsReasoningEffort, true)
|
||||
assert.equal(compat.thinkingLevelMap?.auto, 'auto')
|
||||
assert.equal(compat.thinkingLevelMap?.off, 'none')
|
||||
})
|
||||
|
||||
it('returns supportsReasoningEffort + auto mapping for doubao', () => {
|
||||
const compat = getThinkingCompat('doubao', 'doubao-seed-2-0-pro-260215')
|
||||
assert.equal(compat.supportsReasoningEffort, true)
|
||||
assert.equal(compat.thinkingLevelMap?.auto, 'auto')
|
||||
assert.equal(compat.thinkingLevelMap?.high, 'high')
|
||||
})
|
||||
|
||||
it('returns thinkingFormat:deepseek for mimo/minimax/seed-oss (thinking:{type} format)', () => {
|
||||
const compat = getThinkingCompat('openai-compatible', 'mimo-v2-flash')
|
||||
assert.equal(compat.thinkingFormat, 'deepseek')
|
||||
assert.equal(compat.thinkingLevelMap?.auto, 'auto')
|
||||
assert.equal(compat.thinkingLevelMap?.high, 'high')
|
||||
|
||||
const compat2 = getThinkingCompat('minimax', 'minimax-m2')
|
||||
assert.equal(compat2.thinkingFormat, 'deepseek')
|
||||
})
|
||||
})
|
||||
@@ -6,52 +6,56 @@
|
||||
* - which models support thinking and at what granularity
|
||||
* - what `compat` fields pi-ai needs to actually inject the right request params
|
||||
*
|
||||
* Scope (v1): openai-completions API path only.
|
||||
* Scope: openai-completions API path only.
|
||||
* anthropic-messages / google-generative-ai are out of scope for this version.
|
||||
*/
|
||||
|
||||
// ── Types ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Ordered from off (weakest) to xhigh (strongest), mirrors pi-ai's enum. */
|
||||
export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
|
||||
/**
|
||||
* ThinkingLevel values the UI selector can display.
|
||||
* - 'default': no reasoning params sent — rely on model's native default behavior
|
||||
* - 'off': explicitly disable reasoning
|
||||
* - 'auto': let the model flexibly decide reasoning intensity
|
||||
* - others: specific intensity levels
|
||||
*/
|
||||
export type ThinkingLevel = 'default' | 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'auto'
|
||||
|
||||
/** Used internally to categorise a model into a thinking behaviour group. */
|
||||
type ThinkingType =
|
||||
| 'none' // model is not a reasoning model
|
||||
| 'qwen' // Qwen-family: boolean enable_thinking (off/on)
|
||||
| 'deepseek_v4' // DeepSeek V4+: thinkingFormat:'deepseek', off/high/xhigh
|
||||
| 'o_series' // OpenAI o1/o3: can't disable thinking; low/medium/high only
|
||||
| 'gpt5' // OpenAI gpt-5 base: can't disable; minimal/low/medium/high
|
||||
| 'gpt5_1' // OpenAI gpt-5.1: off→none supported; off/low/medium/high
|
||||
| 'gpt5_2plus' // OpenAI gpt-5.2+: off→none + xhigh; full range
|
||||
| 'grok' // xAI grok: can't disable; low/high only
|
||||
| 'kimi' // Kimi thinking: low/medium/high
|
||||
| 'doubao' // Doubao seed reasoning: high only (no disable support)
|
||||
| 'default' // generic reasoning: off / low / medium / high
|
||||
| 'none' // model is not a reasoning model
|
||||
| 'qwen' // Qwen/GLM-family: boolean enable_thinking (off/on)
|
||||
| 'deepseek_v4' // DeepSeek V4+: thinkingFormat:'deepseek', off/high/xhigh
|
||||
| 'deepseek_hybrid' // DeepSeek V3.x hybrid inference: supports auto
|
||||
| 'o_series' // OpenAI o1/o3: can't disable thinking; low/medium/high only
|
||||
| 'gpt5' // OpenAI gpt-5 base: can't disable; minimal/low/medium/high
|
||||
| 'gpt5_1' // OpenAI gpt-5.1: off→none supported; off/low/medium/high
|
||||
| 'gpt5_2plus' // OpenAI gpt-5.2+: off→none + xhigh; full range
|
||||
| 'grok' // xAI grok: can't disable; low/high only
|
||||
| 'kimi' // Kimi thinking: off/auto/low/medium/high
|
||||
| 'doubao' // Doubao seed reasoning: off/auto/high
|
||||
| 'hunyuan' // Hunyuan: enable_thinking boolean, similar to qwen
|
||||
| 'gemini' // Gemini 2.5+: thinking level via Google API (off/low/medium/high)
|
||||
| 'default' // generic reasoning: off / low / medium / high
|
||||
|
||||
// ── Internal: per-type level tables ──────────────────────────────────────────
|
||||
|
||||
/** UI options for each type — what the selector will show. */
|
||||
const TYPE_LEVELS: Record<ThinkingType, ThinkingLevel[]> = {
|
||||
none: [],
|
||||
// Qwen/GLM: boolean enable_thinking; 'off' sends false, 'high' sends true
|
||||
qwen: ['off', 'high'],
|
||||
// DeepSeek V4+: thinkingFormat:'deepseek', 'off' → thinking:{type:"disabled"}
|
||||
deepseek_v4: ['off', 'high', 'xhigh'],
|
||||
// o-series: always thinks, no API param to disable; don't show 'off'
|
||||
o_series: ['low', 'medium', 'high'],
|
||||
// gpt-5 base: API doesn't support reasoning_effort:'none'; no 'off'
|
||||
gpt5: ['minimal', 'low', 'medium', 'high'],
|
||||
// gpt-5.1+: supports reasoning_effort:'none' to disable thinking
|
||||
gpt5_1: ['off', 'low', 'medium', 'high'],
|
||||
gpt5_2plus: ['off', 'minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
// Grok: no disable support; only low/high
|
||||
grok: ['low', 'high'],
|
||||
// Kimi: low/medium/high (no confirmed disable API)
|
||||
kimi: ['low', 'medium', 'high'],
|
||||
// Doubao: only 'high' confirmed; no disable
|
||||
doubao: ['high'],
|
||||
default: ['off', 'low', 'medium', 'high'],
|
||||
none: [],
|
||||
qwen: ['default', 'off', 'high'],
|
||||
deepseek_v4: ['default', 'off', 'high', 'xhigh'],
|
||||
deepseek_hybrid: ['default', 'off', 'auto'],
|
||||
o_series: ['default', 'low', 'medium', 'high'],
|
||||
gpt5: ['default', 'minimal', 'low', 'medium', 'high'],
|
||||
gpt5_1: ['default', 'off', 'low', 'medium', 'high'],
|
||||
gpt5_2plus: ['default', 'off', 'minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
grok: ['default', 'low', 'high'],
|
||||
kimi: ['default', 'off', 'auto', 'low', 'medium', 'high'],
|
||||
doubao: ['default', 'off', 'auto', 'high'],
|
||||
hunyuan: ['default', 'off', 'high'],
|
||||
gemini: ['default', 'off', 'low', 'medium', 'high'],
|
||||
default: ['default', 'off', 'low', 'medium', 'high'],
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,69 +64,94 @@ const TYPE_LEVELS: Record<ThinkingType, ThinkingLevel[]> = {
|
||||
* `undefined` (key absent) means pass the level string verbatim.
|
||||
* Matches pi-ai's convention in models.generated.js.
|
||||
*/
|
||||
// thinkingLevelMap is only used by models on the supportsReasoningEffort path.
|
||||
// DeepSeek uses thinkingFormat:'deepseek' (its own pi-ai branch) and has no entry here.
|
||||
const TYPE_LEVEL_MAP: Partial<Record<ThinkingType, Partial<Record<ThinkingLevel, string | null>>>> = {
|
||||
gpt5: {
|
||||
minimal: 'minimal',
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'high',
|
||||
},
|
||||
// 'off' maps to reasoning_effort:'none' — API confirmed to disable thinking
|
||||
gpt5_1: {
|
||||
off: 'none',
|
||||
low: 'low',
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'high',
|
||||
xhigh: null,
|
||||
high: 'high',
|
||||
},
|
||||
gpt5_1: {
|
||||
off: 'none',
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'high',
|
||||
xhigh: null,
|
||||
},
|
||||
gpt5_2plus: {
|
||||
off: 'none',
|
||||
off: 'none',
|
||||
minimal: 'minimal',
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'high',
|
||||
xhigh: 'xhigh',
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'high',
|
||||
xhigh: 'xhigh',
|
||||
},
|
||||
grok: {
|
||||
minimal: null,
|
||||
low: 'low',
|
||||
medium: null,
|
||||
high: 'high',
|
||||
xhigh: null,
|
||||
low: 'low',
|
||||
medium: null,
|
||||
high: 'high',
|
||||
xhigh: null,
|
||||
},
|
||||
kimi: {
|
||||
off: 'none',
|
||||
auto: 'auto',
|
||||
minimal: null,
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'high',
|
||||
xhigh: null,
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'high',
|
||||
xhigh: null,
|
||||
},
|
||||
doubao: {
|
||||
off: null,
|
||||
auto: 'auto',
|
||||
high: 'high',
|
||||
},
|
||||
deepseek_hybrid: {
|
||||
off: null,
|
||||
auto: 'auto',
|
||||
high: 'high',
|
||||
},
|
||||
}
|
||||
|
||||
// ── Internal: model → ThinkingType classification ─────────────────────────────
|
||||
|
||||
/**
|
||||
* Broad regex matching common reasoning model naming patterns.
|
||||
* Used as a fallback when no specific provider/model rule matches.
|
||||
* Excludes explicit '-non-reasoning' variants.
|
||||
*/
|
||||
const REASONING_FALLBACK_REGEX =
|
||||
/^(?!.*-non-reasoning\b)(o\d+(?:-[\w-]+)?|.*\b(?:reasoning|reasoner|thinking|think)\b.*|.*-[rR]\d+.*|.*\bqwq(?:-[\w-]+)?\b.*|.*\bhunyuan-t1(?:-[\w-]+)?\b.*|.*\bgrok-(?:3-mini|4|4-fast|build)(?:-[\w-]+)?\b.*)$/i
|
||||
|
||||
/**
|
||||
* Classify a model into a ThinkingType based on provider + model id.
|
||||
* Kept intentionally simple: providers/ids are stable enough for direct matching.
|
||||
*/
|
||||
function classifyThinkingType(provider: string, modelId: string): ThinkingType {
|
||||
const id = modelId.toLowerCase()
|
||||
const prov = provider.toLowerCase()
|
||||
|
||||
// ── Non-reasoning paths handled by other API formats (out of scope v1) ──
|
||||
if (prov === 'anthropic' || prov === 'gemini') return 'none'
|
||||
// ── Explicit non-reasoning variants ──
|
||||
if (id.includes('-non-reasoning')) return 'none'
|
||||
|
||||
// ── Anthropic uses different API format (out of scope) ──
|
||||
if (prov === 'anthropic') return 'none'
|
||||
|
||||
// ── Gemini 2.5+ reasoning models (via Google Generative AI API) ──
|
||||
if (prov === 'gemini') {
|
||||
if (/gemini-(?:2\.5|3(?:\.\d+)?-(?:flash|pro)|flash-latest|pro-latest)/i.test(id)) return 'gemini'
|
||||
return 'none'
|
||||
}
|
||||
|
||||
// ── Qwen (official DashScope + self-hosted models containing qwen/qwq) ──
|
||||
if (prov === 'qwen' || /qwen|qwq/i.test(id)) return 'qwen'
|
||||
if (prov === 'qwen' || /\bqwen|qwq/i.test(id)) return 'qwen'
|
||||
|
||||
// ── DeepSeek V4+ ──
|
||||
if (/deepseek[_-]v([4-9]|\d{2,})/i.test(id) || /deepseek-ai\/deepseek-r1/i.test(id)) return 'deepseek_v4'
|
||||
|
||||
// ── DeepSeek V3.x hybrid inference (deepseek-chat, deepseek-v3.x) ──
|
||||
if (/deepseek[_-]chat|deepseek[_-]v3/i.test(id)) return 'deepseek_hybrid'
|
||||
|
||||
// ── OpenAI gpt-5.x family ──
|
||||
if (prov === 'openai' || prov === 'openai-compatible') {
|
||||
if (/gpt-5\.[2-9]|gpt-5\.[1-9]\d/.test(id)) return 'gpt5_2plus'
|
||||
@@ -133,35 +162,57 @@ function classifyThinkingType(provider: string, modelId: string): ThinkingType {
|
||||
// o-series on other providers (e.g., Azure, OpenRouter)
|
||||
if (/^o\d/.test(id)) return 'o_series'
|
||||
|
||||
// ── xAI Grok reasoning models ──
|
||||
if (prov === 'xai' && /grok-[34]/i.test(id) && /mini|4\b/i.test(id)) return 'grok'
|
||||
// ── xAI Grok reasoning models (grok-3-mini, grok-4, grok-4-fast, grok-build) ──
|
||||
if (/\bgrok-(?:3-mini|4|4-fast|build)\b/i.test(id)) return 'grok'
|
||||
|
||||
// ── Kimi thinking models ──
|
||||
// ── Kimi thinking models (k2-thinking, k2.5+, k3+) ──
|
||||
if (/\bkimi-k(?:2-thinking|2\.[5-9]|[3-9])/i.test(id)) return 'kimi'
|
||||
if (prov === 'kimi' && /thinking/i.test(id)) return 'kimi'
|
||||
|
||||
// ── Doubao seed reasoning ──
|
||||
if (prov === 'doubao' && /seed/i.test(id)) return 'doubao'
|
||||
if (/\bdoubao-.*seed/i.test(id) || (prov === 'doubao' && /seed/i.test(id))) return 'doubao'
|
||||
|
||||
// ── GLM (ZAI) — only boolean enable_thinking, same as Qwen ──
|
||||
if (prov === 'glm' || /glm[-_]/i.test(id)) return 'qwen'
|
||||
// ── Hunyuan reasoning models ──
|
||||
if (/\bhunyuan-(?:t1|a13b)/i.test(id)) return 'hunyuan'
|
||||
|
||||
// ── GLM (ZAI) — glm-5, glm-4.5/4.6/4.7, glm-z1, glm-zero-preview ──
|
||||
if (prov === 'glm' || /\bglm-?(?:5|4\.[5-7]|z1|zero)/i.test(id)) return 'qwen'
|
||||
|
||||
// ── SiliconFlow hosted reasoning models (DeepSeek R1 family) ──
|
||||
if (prov === 'siliconflow' && /r[1-9]/i.test(id)) return 'deepseek_v4'
|
||||
|
||||
// ── OpenRouter: wrap by inner model id if known ──
|
||||
// ── OpenRouter: classify by inner model id ──
|
||||
if (prov === 'openrouter') {
|
||||
if (/deepseek.*(v[4-9]|r\d)/i.test(id)) return 'deepseek_v4'
|
||||
return 'default'
|
||||
if (/deepseek[_-]?(chat|v3)/i.test(id)) return 'deepseek_hybrid'
|
||||
if (/\bgrok-(?:3-mini|4|4-fast|build)\b/i.test(id)) return 'grok'
|
||||
if (/\bkimi-k(?:2-thinking|2\.[5-9]|[3-9])/i.test(id)) return 'kimi'
|
||||
if (REASONING_FALLBACK_REGEX.test(id)) return 'default'
|
||||
return 'none'
|
||||
}
|
||||
|
||||
// ── Models using thinking:{type:'disabled'/'enabled'} format (same as DeepSeek) ──
|
||||
if (/\bmimo-v2/i.test(id)) return 'deepseek_hybrid'
|
||||
if (/\bminimax-m[123]/i.test(id)) return 'deepseek_hybrid'
|
||||
if (/\bseed-oss/i.test(id)) return 'deepseek_hybrid'
|
||||
|
||||
// ── Known reasoning model families → 'default' bucket (supportsReasoningEffort) ──
|
||||
if (/\bbaichuan-m[23]/i.test(id)) return 'default'
|
||||
if (/\bmagistral/i.test(id)) return 'default'
|
||||
if (/\bmistral-small-2603/i.test(id)) return 'default'
|
||||
if (/\bstep-(?:3|r1-v-mini)/i.test(id)) return 'default'
|
||||
if (/\bgemma-?4/i.test(id)) return 'default'
|
||||
if (/\bpangu-pro-moe/i.test(id)) return 'default'
|
||||
if (/\bsonar-deep-research/i.test(id)) return 'default'
|
||||
if (/\bring-(?:1t|mini|flash)/i.test(id)) return 'default'
|
||||
|
||||
// ── Self-hosted / unknown — heuristic from model name ──
|
||||
if (/qwen|qwq/i.test(id)) return 'qwen'
|
||||
if (/\bqwen|qwq/i.test(id)) return 'qwen'
|
||||
if (/deepseek[_-]?r\d|deepseek[_-]?v[4-9]/i.test(id)) return 'deepseek_v4'
|
||||
if (/^o\d/i.test(id)) return 'o_series'
|
||||
|
||||
// Only assign 'default' (reasoning bucket) when the model name itself
|
||||
// suggests thinking capability. Otherwise return 'none'.
|
||||
if (/reasoning|thinking|r1\b|r2\b|qwq|think/i.test(id)) return 'default'
|
||||
// Broad fallback: match common reasoning naming patterns
|
||||
if (REASONING_FALLBACK_REGEX.test(id)) return 'default'
|
||||
|
||||
return 'none'
|
||||
}
|
||||
@@ -184,7 +235,6 @@ export function getSupportedThinkingLevels(provider: string, modelId: string): T
|
||||
* on the openai-completions path. (anthropic/gemini always return false here.)
|
||||
*
|
||||
* Used by: buildPiModel to set `PiModel.reasoning`.
|
||||
* Replaces the ad-hoc inferReasoning logic in llm-builder.ts.
|
||||
*/
|
||||
export function isReasoningModel(provider: string, modelId: string): boolean {
|
||||
return classifyThinkingType(provider, modelId) !== 'none'
|
||||
@@ -197,8 +247,8 @@ export function isReasoningModel(provider: string, modelId: string): boolean {
|
||||
* Used by: buildPiModel (openai-completions branch).
|
||||
*/
|
||||
export interface ThinkingCompat {
|
||||
thinkingFormat?: 'qwen' | 'deepseek' // provider-specific injection format
|
||||
supportsReasoningEffort?: true // OpenAI-style reasoning_effort param
|
||||
thinkingFormat?: 'qwen' | 'deepseek'
|
||||
supportsReasoningEffort?: true
|
||||
thinkingLevelMap?: Partial<Record<ThinkingLevel, string | null>>
|
||||
}
|
||||
|
||||
@@ -207,23 +257,27 @@ export function getThinkingCompat(provider: string, modelId: string): ThinkingCo
|
||||
|
||||
if (type === 'none') return {}
|
||||
|
||||
if (type === 'qwen') {
|
||||
// Qwen/GLM: pi-ai injects enable_thinking boolean.
|
||||
// 'off' → reasoningEffort=undefined → enable_thinking:false
|
||||
// 'high' → reasoningEffort='high' → enable_thinking:true
|
||||
// Gemini uses pi-ai's native Google provider; no compat fields needed.
|
||||
if (type === 'gemini') return {}
|
||||
|
||||
if (type === 'qwen' || type === 'hunyuan') {
|
||||
return { thinkingFormat: 'qwen' }
|
||||
}
|
||||
|
||||
if (type === 'deepseek_v4') {
|
||||
// DeepSeek requires thinkingFormat:'deepseek' so pi-ai sends:
|
||||
// thinking:{type:"disabled"} when reasoningEffort=undefined (off)
|
||||
// thinking:{type:"enabled"} + reasoning_effort when set
|
||||
return {
|
||||
thinkingFormat: 'deepseek',
|
||||
thinkingLevelMap: { high: 'high', xhigh: 'max', minimal: null, low: null, medium: null },
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'deepseek_hybrid') {
|
||||
return {
|
||||
thinkingFormat: 'deepseek',
|
||||
thinkingLevelMap: { auto: 'auto', high: 'high', minimal: null, low: null, medium: null, xhigh: null },
|
||||
}
|
||||
}
|
||||
|
||||
// All remaining types use OpenAI-style reasoning_effort.
|
||||
const compat: ThinkingCompat = { supportsReasoningEffort: true }
|
||||
const levelMap = TYPE_LEVEL_MAP[type]
|
||||
|
||||
@@ -153,7 +153,8 @@ describe('buildPiModel', () => {
|
||||
// Must use thinkingFormat:'deepseek' so that 'off' sends thinking:{type:"disabled"}
|
||||
assert.equal(compat?.thinkingFormat, 'deepseek')
|
||||
assert.equal(compat?.supportsReasoningEffort, undefined)
|
||||
const map = compat?.thinkingLevelMap as Record<string, unknown> | undefined
|
||||
// thinkingLevelMap is on the model top-level (for clampThinkingLevel)
|
||||
const map = model.thinkingLevelMap as Record<string, unknown> | undefined
|
||||
assert.equal(map?.high, 'high')
|
||||
assert.equal(map?.xhigh, 'max')
|
||||
assert.equal(map?.minimal, null)
|
||||
|
||||
@@ -87,15 +87,44 @@ export async function runAgentCore(options: AgentCoreOptions): Promise<AgentCore
|
||||
return { usage: totalUsage, finalMessages: [], toolsUsed: [], toolRounds: 0 }
|
||||
}
|
||||
|
||||
// Resolve thinkingLevel for pi-agent-core:
|
||||
// - 'default'/undefined → strip reasoning from piModel so pi-ai sends a plain request
|
||||
// with NO reasoning params at all (model uses its native default behavior).
|
||||
// - 'off' → pi-ai sends disable signals (thinking:{type:'disabled'} / enable_thinking:false)
|
||||
// - 'auto' → for thinkingFormat models, use 'high' to enable; others no params
|
||||
// - Other levels → clamp to what pi-agent-core accepts.
|
||||
const isDefault = !options.thinkingLevel || options.thinkingLevel === 'default'
|
||||
const effectiveModel = isDefault
|
||||
? { ...piModel, reasoning: false, compat: undefined, thinkingLevelMap: undefined }
|
||||
: piModel
|
||||
|
||||
const resolvedThinkingLevel = (() => {
|
||||
if (isDefault) return 'off'
|
||||
const level = options.thinkingLevel!
|
||||
if (level === 'auto') {
|
||||
if (!piModel.reasoning) return 'off'
|
||||
const compat = piModel.compat as Record<string, unknown> | undefined
|
||||
if (compat?.thinkingFormat) return 'high'
|
||||
return undefined
|
||||
}
|
||||
return clampThinkingLevel(piModel, level as Exclude<typeof level, 'default' | 'auto'>)
|
||||
})()
|
||||
|
||||
const finalThinkingLevel = resolvedThinkingLevel ?? (piModel.reasoning ? undefined : 'off')
|
||||
console.debug('[agent-core] thinkingLevel resolution:', {
|
||||
input: options.thinkingLevel,
|
||||
modelId: piModel.id,
|
||||
modelReasoning: effectiveModel.reasoning,
|
||||
isDefault,
|
||||
resolved: resolvedThinkingLevel,
|
||||
final: finalThinkingLevel,
|
||||
})
|
||||
|
||||
const coreAgent = new PiAgentCore({
|
||||
initialState: {
|
||||
systemPrompt,
|
||||
model: piModel,
|
||||
thinkingLevel: options.thinkingLevel
|
||||
? clampThinkingLevel(piModel, options.thinkingLevel)
|
||||
: piModel.reasoning
|
||||
? 'medium'
|
||||
: 'off',
|
||||
model: effectiveModel,
|
||||
thinkingLevel: finalThinkingLevel,
|
||||
tools: maxToolRounds > 0 ? tools : [],
|
||||
messages: toPiHistoryMessages(history),
|
||||
},
|
||||
|
||||
@@ -79,26 +79,29 @@ const BUILTIN_PROVIDER_API: Record<string, PiApi> = {
|
||||
* carries supportsReasoningEffort + thinkingLevelMap so pi-ai can inject the
|
||||
* correct request-body param for each provider family.
|
||||
*/
|
||||
function inferReasoning(
|
||||
provider: string,
|
||||
modelId: string,
|
||||
modelDef: ModelDefinition | null,
|
||||
): { reasoning: boolean; compat: PiModel<PiApi>['compat'] } {
|
||||
// Catalog capabilities are authoritative; fall back to name-heuristic for
|
||||
// unlisted / self-hosted models.
|
||||
const reasoning = modelDef
|
||||
? modelDef.capabilities.includes('reasoning')
|
||||
: isReasoningModel(provider, modelId)
|
||||
interface InferReasoningResult {
|
||||
reasoning: boolean
|
||||
compat: PiModel<PiApi>['compat']
|
||||
thinkingLevelMap?: PiModel<PiApi>['thinkingLevelMap']
|
||||
}
|
||||
|
||||
function inferReasoning(provider: string, modelId: string, modelDef: ModelDefinition | null): InferReasoningResult {
|
||||
const reasoning = modelDef ? modelDef.capabilities.includes('reasoning') : isReasoningModel(provider, modelId)
|
||||
|
||||
if (!reasoning) return { reasoning: false, compat: undefined }
|
||||
|
||||
const thinkingCompat = getThinkingCompat(provider, modelId)
|
||||
// thinkingCompat may be an empty object {} for unknown reasoning models —
|
||||
// that's fine: pi-ai will still receive reasoning:true and default to
|
||||
// passing the effort level verbatim.
|
||||
if (Object.keys(thinkingCompat).length === 0) {
|
||||
return { reasoning: true, compat: undefined }
|
||||
}
|
||||
|
||||
// thinkingLevelMap belongs on the Model top-level (for clampThinkingLevel),
|
||||
// while thinkingFormat/supportsReasoningEffort belong in compat (for the provider).
|
||||
const { thinkingLevelMap, ...compatFields } = thinkingCompat
|
||||
return {
|
||||
reasoning: true,
|
||||
compat: Object.keys(thinkingCompat).length > 0 ? thinkingCompat : undefined,
|
||||
compat: Object.keys(compatFields).length > 0 ? compatFields : undefined,
|
||||
thinkingLevelMap: thinkingLevelMap as PiModel<PiApi>['thinkingLevelMap'],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +123,7 @@ export function buildPiModel(config: PiModelConfig, options?: BuildPiModelOption
|
||||
api: 'google-generative-ai',
|
||||
provider: 'google',
|
||||
baseUrl,
|
||||
reasoning: false,
|
||||
reasoning: isReasoningModel(config.provider, modelId),
|
||||
input: ['text'],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow,
|
||||
@@ -148,7 +151,7 @@ export function buildPiModel(config: PiModelConfig, options?: BuildPiModelOption
|
||||
? normalizeOpenAICompatibleBaseUrl(baseUrl)
|
||||
: baseUrl
|
||||
|
||||
const { reasoning, compat } = inferReasoning(config.provider, modelId, modelDef)
|
||||
const { reasoning, compat, thinkingLevelMap } = inferReasoning(config.provider, modelId, modelDef)
|
||||
|
||||
return {
|
||||
id: modelId,
|
||||
@@ -158,6 +161,7 @@ export function buildPiModel(config: PiModelConfig, options?: BuildPiModelOption
|
||||
baseUrl: resolvedBaseUrl,
|
||||
headers: options?.headers,
|
||||
reasoning,
|
||||
thinkingLevelMap,
|
||||
input: ['text'],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow,
|
||||
|
||||
@@ -233,11 +233,11 @@ const supportedThinkingLevels = computed<ThinkingLevel[]>(() => {
|
||||
/** Whether to show the selector at all. */
|
||||
const showThinkingSelector = computed(() => supportedThinkingLevels.value.length > 0)
|
||||
|
||||
/** The currently remembered level for this model slot (undefined → default). */
|
||||
const currentThinkingLevel = computed<ThinkingLevel | undefined>(() => {
|
||||
/** The currently remembered level for this model slot (undefined → 'default'). */
|
||||
const currentThinkingLevel = computed<ThinkingLevel>(() => {
|
||||
const cfg = llmStore.defaultAssistant
|
||||
if (!cfg?.configId || !cfg?.modelId) return undefined
|
||||
return promptStore.getThinkingLevel(cfg.configId, cfg.modelId)
|
||||
if (!cfg?.configId || !cfg?.modelId) return 'default'
|
||||
return promptStore.getThinkingLevel(cfg.configId, cfg.modelId) ?? 'default'
|
||||
})
|
||||
|
||||
function selectThinkingLevel(level: ThinkingLevel) {
|
||||
@@ -249,10 +249,7 @@ function selectThinkingLevel(level: ThinkingLevel) {
|
||||
|
||||
/** Label shown on the trigger button. */
|
||||
const thinkingLevelLabel = computed(() => {
|
||||
const level = currentThinkingLevel.value
|
||||
// undefined means no explicit preference set — core defaults to 'medium'
|
||||
if (level === undefined) return t('ai.chat.statusBar.thinking.auto')
|
||||
return t(`ai.chat.statusBar.thinking.level.${level}`)
|
||||
return t(`ai.chat.statusBar.thinking.level.${currentThinkingLevel.value}`)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -332,9 +329,9 @@ const thinkingLevelLabel = computed(() => {
|
||||
<button
|
||||
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
:class="
|
||||
!currentThinkingLevel || currentThinkingLevel === 'off'
|
||||
currentThinkingLevel === 'default' || currentThinkingLevel === 'off'
|
||||
? 'text-gray-400 dark:text-gray-500'
|
||||
: 'text-blue-500 dark:text-blue-400'
|
||||
: 'text-primary-500 dark:text-primary-400'
|
||||
"
|
||||
:title="t('ai.chat.statusBar.thinking.tooltip')"
|
||||
>
|
||||
@@ -351,20 +348,16 @@ const thinkingLevelLabel = computed(() => {
|
||||
:key="level"
|
||||
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
:class="
|
||||
(currentThinkingLevel ?? 'medium') === level
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
currentThinkingLevel === level
|
||||
? 'text-primary-600 dark:text-primary-400'
|
||||
: 'text-gray-700 dark:text-gray-300'
|
||||
"
|
||||
@click="selectThinkingLevel(level)"
|
||||
>
|
||||
<UIcon
|
||||
:name="
|
||||
(currentThinkingLevel ?? 'medium') === level
|
||||
? 'i-heroicons-check-circle-solid'
|
||||
: 'i-heroicons-light-bulb'
|
||||
"
|
||||
:name="currentThinkingLevel === level ? 'i-heroicons-check-circle-solid' : 'i-heroicons-light-bulb'"
|
||||
class="h-4 w-4 shrink-0"
|
||||
:class="(currentThinkingLevel ?? 'medium') === level ? 'text-blue-500' : 'text-gray-400'"
|
||||
:class="currentThinkingLevel === level ? 'text-primary-500' : 'text-gray-400'"
|
||||
/>
|
||||
<span>{{ t(`ai.chat.statusBar.thinking.level.${level}`) }}</span>
|
||||
</button>
|
||||
|
||||
@@ -181,15 +181,15 @@
|
||||
"thinking": {
|
||||
"title": "Thinking level",
|
||||
"tooltip": "Set the thinking intensity for this model",
|
||||
"auto": "Auto",
|
||||
"off": "Thinking off",
|
||||
"level": {
|
||||
"default": "Default",
|
||||
"off": "Off",
|
||||
"minimal": "Minimal",
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High",
|
||||
"xhigh": "Max"
|
||||
"xhigh": "Max",
|
||||
"auto": "Auto"
|
||||
}
|
||||
},
|
||||
"tokenUsageTitle": "Total token usage",
|
||||
@@ -517,4 +517,4 @@
|
||||
"truncated": "Result too large, truncated. Try reducing the limit parameter for complete data."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,15 +181,15 @@
|
||||
"thinking": {
|
||||
"title": "思考強度",
|
||||
"tooltip": "モデルの思考強度を設定",
|
||||
"auto": "自動",
|
||||
"off": "思考オフ",
|
||||
"level": {
|
||||
"default": "デフォルト",
|
||||
"off": "オフ",
|
||||
"minimal": "極小",
|
||||
"low": "低",
|
||||
"medium": "中",
|
||||
"high": "高",
|
||||
"xhigh": "最大"
|
||||
"xhigh": "最大",
|
||||
"auto": "自動"
|
||||
}
|
||||
},
|
||||
"tokenUsageTitle": "累計 Token 使用量",
|
||||
@@ -481,4 +481,4 @@
|
||||
"truncated": "結果が大きすぎるため、切り捨てて表示しています。limit パラメータを小さくしてください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,15 +181,15 @@
|
||||
"thinking": {
|
||||
"title": "思考强度",
|
||||
"tooltip": "设置模型的思考强度",
|
||||
"auto": "自动",
|
||||
"off": "关闭思考",
|
||||
"level": {
|
||||
"off": "关闭思考",
|
||||
"default": "默认",
|
||||
"off": "关闭",
|
||||
"minimal": "极低",
|
||||
"low": "低",
|
||||
"medium": "中",
|
||||
"high": "高",
|
||||
"xhigh": "最高"
|
||||
"xhigh": "最高",
|
||||
"auto": "自动"
|
||||
}
|
||||
},
|
||||
"tokenUsageTitle": "累计 Token 使用量",
|
||||
|
||||
@@ -181,15 +181,15 @@
|
||||
"thinking": {
|
||||
"title": "思考強度",
|
||||
"tooltip": "設定模型的思考強度",
|
||||
"auto": "自動",
|
||||
"off": "關閉思考",
|
||||
"level": {
|
||||
"off": "關閉思考",
|
||||
"default": "預設",
|
||||
"off": "關閉",
|
||||
"minimal": "極低",
|
||||
"low": "低",
|
||||
"medium": "中",
|
||||
"high": "高",
|
||||
"xhigh": "最高"
|
||||
"xhigh": "最高",
|
||||
"auto": "自動"
|
||||
}
|
||||
},
|
||||
"tokenUsageTitle": "累計 Token 用量",
|
||||
@@ -481,4 +481,4 @@
|
||||
"truncated": "結果過大,已截斷顯示。建議減小 limit 參數以獲取完整資料。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user