fix(ai): fall back to heuristic for non-builtin custom models in inferReasoning

Custom models auto-saved by ensureCustomProvidersAndModels carry only
capabilities:['chat'], so inferReasoning was returning reasoning=false
even for models like qwen3:8b whose names match the reasoning heuristic.
The UI still showed thinking levels via getSupportedThinkingLevels, but
selections were silently ignored by the backend.

Fix: for non-builtin (builtin=false) model definitions, also apply the
name/provider heuristic as a fallback alongside the capabilities check.
Builtin catalog models remain authoritative.

Adds a regression test covering the exact findModelFn shape produced by
ensureCustomProvidersAndModels.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
diguaandClaude Sonnet 4.6 committed 2026-05-31 10:28:29 +08:00
1 parent 999ce58007
commit 7558eed5a5
2 files changed
+29 -1

No files matched your search

@@ -132,6 +132,30 @@ describe('buildPiModel', () => {
assert.deepEqual(model.compat, { thinkingFormat: 'qwen' })
})
it('auto-infers reasoning=true for custom model saved with only capabilities:["chat"]', () => {
// Regression: ensureCustomProvidersAndModels saves user-added models with capabilities:['chat']
// only. The heuristic must still fire for non-builtin model definitions so that thinking
// level selections are not silently ignored in the UI.
const model = buildPiModel(
{ provider: 'openai-compatible', model: 'qwen3:8b', baseUrl: 'http://localhost:11434/v1' },
{
findModelFn: () => ({
id: 'qwen3:8b',
providerId: 'openai-compatible',
name: 'qwen3:8b',
contextWindow: 32768,
capabilities: ['chat'] as const,
recommendedFor: ['chat'] as const,
status: 'stable' as const,
builtin: false,
editable: true,
}),
}
)
assert.equal(model.reasoning, true)
assert.deepEqual(model.compat, { thinkingFormat: 'qwen' })
})
it('auto-infers reasoning=false and compat=undefined for non-reasoning Anthropic model', () => {
const model = buildPiModel({
provider: 'openai-compatible',
+5 -1
View File
@@ -86,7 +86,11 @@ interface InferReasoningResult {
}
function inferReasoning(provider: string, modelId: string, modelDef: ModelDefinition | null): InferReasoningResult {
const reasoning = modelDef ? modelDef.capabilities.includes('reasoning') : isReasoningModel(provider, modelId)
// Builtin catalog models: trust capabilities array exclusively.
// Custom (builtin=false) or unlisted models: also check name heuristic as fallback because
// auto-saved custom model entries carry only capabilities:['chat'] even for reasoning models.
const reasoning =
modelDef?.capabilities.includes('reasoning') || (!modelDef?.builtin && isReasoningModel(provider, modelId))
if (!reasoning) return { reasoning: false, compat: undefined }