diff --git a/web/src/pages/next-search/llm-setting-defaults.test.ts b/web/src/pages/next-search/llm-setting-defaults.test.ts new file mode 100644 index 0000000000..ab867f9545 --- /dev/null +++ b/web/src/pages/next-search/llm-setting-defaults.test.ts @@ -0,0 +1,55 @@ +import { llmSettingDefaults, resolveInitialLlmSetting } from './llm-setting-defaults'; + +// The four generation parameters must fall back to the backend defaults +// (LLM_SETTING_DEFAULTS in api/db/services/llm_service.py), not to 0: an +// unset top_p persisted as 0 would collapse nucleus sampling on the next +// save because the enabled flags default to true. + +describe('resolveInitialLlmSetting', () => { + test('unset llm_setting falls back to the backend defaults', () => { + expect(resolveInitialLlmSetting(undefined)).toEqual(llmSettingDefaults); + expect(resolveInitialLlmSetting(null)).toEqual(llmSettingDefaults); + expect(resolveInitialLlmSetting({})).toEqual(llmSettingDefaults); + }); + + test('stored values are kept', () => { + expect( + resolveInitialLlmSetting({ + temperature: 0.8, + top_p: 0.9, + frequency_penalty: 0.2, + presence_penalty: 0.5, + }), + ).toEqual({ + temperature: 0.8, + top_p: 0.9, + frequency_penalty: 0.2, + presence_penalty: 0.5, + }); + }); + + test('a stored 0 is a deliberate choice and must not be replaced', () => { + expect( + resolveInitialLlmSetting({ + temperature: 0, + top_p: 0, + frequency_penalty: 0, + presence_penalty: 0, + }), + ).toEqual({ + temperature: 0, + top_p: 0, + frequency_penalty: 0, + presence_penalty: 0, + }); + }); + + test('partially stored values only fill the missing ones', () => { + expect(resolveInitialLlmSetting({ temperature: 0.5 })).toEqual({ + temperature: 0.5, + top_p: llmSettingDefaults.top_p, + frequency_penalty: llmSettingDefaults.frequency_penalty, + presence_penalty: llmSettingDefaults.presence_penalty, + }); + }); +}); diff --git a/web/src/pages/next-search/llm-setting-defaults.ts b/web/src/pages/next-search/llm-setting-defaults.ts new file mode 100644 index 0000000000..abe9d59875 --- /dev/null +++ b/web/src/pages/next-search/llm-setting-defaults.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2026 The InfiniFlow Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Mirrors LLM_SETTING_DEFAULTS in api/db/services/llm_service.py: when a +// generation parameter was never saved, the backend resolves it to these +// defaults. Falling back to 0 in the form would persist 0 as an explicit +// value and override the backend defaults on the next save (e.g. top_p 0 +// collapses nucleus sampling). +export const llmSettingDefaults = { + temperature: 0.1, + top_p: 0.3, + frequency_penalty: 0.7, + presence_penalty: 0.4, +} as const; + +type LlmSettingNumericFields = { + temperature?: number | null; + top_p?: number | null; + frequency_penalty?: number | null; + presence_penalty?: number | null; +}; + +/** + * Fill the four generation parameters for the form's initial values. A stored + * 0 is a deliberate choice and must be kept; only unset values fall back to + * the backend defaults. + */ +export const resolveInitialLlmSetting = ( + llm_setting?: LlmSettingNumericFields | null, +) => ({ + temperature: llm_setting?.temperature ?? llmSettingDefaults.temperature, + top_p: llm_setting?.top_p ?? llmSettingDefaults.top_p, + frequency_penalty: + llm_setting?.frequency_penalty ?? llmSettingDefaults.frequency_penalty, + presence_penalty: + llm_setting?.presence_penalty ?? llmSettingDefaults.presence_penalty, +}); diff --git a/web/src/pages/next-search/search-setting.tsx b/web/src/pages/next-search/search-setting.tsx index 8ed55d3c31..9d80285a4c 100644 --- a/web/src/pages/next-search/search-setting.tsx +++ b/web/src/pages/next-search/search-setting.tsx @@ -53,6 +53,7 @@ import { useUpdateSearch, } from '../next-searches/hooks'; import { RerankFormFields } from '@/components/rerank'; +import { resolveInitialLlmSetting } from './llm-setting-defaults'; import { SearchSettingFormData, useRevalidatePersistedModels, @@ -116,10 +117,7 @@ function SearchSetting({ llm_setting: { llm_id: search_config?.chat_id || '', parameter: llm_setting?.parameter || '', - temperature: llm_setting?.temperature || 0, - top_p: llm_setting?.top_p || 0, - frequency_penalty: llm_setting?.frequency_penalty || 0, - presence_penalty: llm_setting?.presence_penalty || 0, + ...resolveInitialLlmSetting(llm_setting), }, chat_settingcross_languages: [], highlight: false,