feat(websearch): add overwrite functionality for search service (#4530)

* feat(websearch): add overwrite functionality for search service

- Introduced new settings to allow users to override the default search service.
- Updated localization files for English, Japanese, Russian, Simplified Chinese, and Traditional Chinese to include new overwrite options and tooltips.
- Modified relevant components and services to support the new overwrite feature in the web search settings.

* feat(websearch): enhance web search model integration

* chore(websearch): unnecessary return
This commit is contained in:
SuYao 2025-04-08 20:07:00 +08:00 committed by GitHub
parent 2a0d6eb08a
commit ab1a5f18c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 77 additions and 20 deletions

View File

@ -2244,7 +2244,7 @@ export function isWebSearchModel(model: Model): boolean {
return true
}
return false
return model.type?.includes('web_search') || false
}
export function isGenerateImageModel(model: Model): boolean {

View File

@ -27,6 +27,7 @@ export function getWebSearchTools(model: Model): ChatCompletionTool[] {
]
}
if (model?.id.includes('gemini')) {
return [
{
type: 'function',
@ -36,3 +37,5 @@ export function getWebSearchTools(model: Model): ChatCompletionTool[] {
}
]
}
return []
}

View File

@ -1292,7 +1292,9 @@
"description": "Tavily is a search engine tailored for AI agents, delivering real-time, accurate results, intelligent query suggestions, and in-depth research capabilities.",
"title": "Tavily"
},
"title": "Web Search"
"title": "Web Search",
"overwrite": "Override search service",
"overwrite_tooltip": "Force use search service instead of LLM"
},
"quickPhrase": {
"title": "Quick Phrases",

View File

@ -1291,7 +1291,9 @@
"description": "Tavily は、AI エージェントのために特別に開発された検索エンジンで、最新の結果、インテリジェントな検索提案、そして深い研究能力を提供します",
"title": "Tavily"
},
"title": "ウェブ検索"
"title": "ウェブ検索",
"overwrite": "サービス検索を上書き",
"overwrite_tooltip": "大規模言語モデルではなく、サービス検索を使用する"
},
"general.auto_check_update.title": "自動更新チェックを有効にする",
"quickPhrase": {

View File

@ -1291,7 +1291,9 @@
"description": "Tavily — это поисковая система, специально разработанная для ИИ-агентов, предоставляющая актуальные результаты, умные предложения по запросам и глубокие исследовательские возможности",
"title": "Tavily"
},
"title": "Поиск в Интернете"
"title": "Поиск в Интернете",
"overwrite": "Переопределить поставщика поиска",
"overwrite_tooltip": "Использовать поставщика поиска вместо LLM"
},
"general.auto_check_update.title": "Включить автоматическую проверку обновлений",
"quickPhrase": {

View File

@ -1279,6 +1279,8 @@
"check_success": "验证成功",
"enhance_mode": "搜索增强模式",
"enhance_mode_tooltip": "使用默认模型提取关键词后搜索",
"overwrite": "覆盖服务商搜索",
"overwrite_tooltip": "强制使用搜索服务商而不是大语言模型进行搜索",
"get_api_key": "点击这里获取密钥",
"no_provider_selected": "请选择搜索服务商后再检查",
"search_max_result": "搜索结果个数",

View File

@ -1291,7 +1291,9 @@
"description": "Tavily 是一個為 AI 代理量身訂製的搜尋引擎,提供即時、準確的結果、智慧查詢建議和深入的研究能力",
"title": "Tavily"
},
"title": "網路搜尋"
"title": "網路搜尋",
"overwrite": "覆蓋搜尋服務商",
"overwrite_tooltip": "強制使用搜尋服務商而不是大語言模型進行搜尋"
},
"general.auto_check_update.title": "啟用自動更新檢查",
"quickPhrase": {

View File

@ -1,6 +1,12 @@
import { DownOutlined, UpOutlined } from '@ant-design/icons'
import CopyIcon from '@renderer/components/Icons/CopyIcon'
import { isEmbeddingModel, isFunctionCallingModel, isReasoningModel, isVisionModel } from '@renderer/config/models'
import {
isEmbeddingModel,
isFunctionCallingModel,
isReasoningModel,
isVisionModel,
isWebSearchModel
} from '@renderer/config/models'
import { Model, ModelType } from '@renderer/types'
import { getDefaultGroupName } from '@renderer/utils'
import { Button, Checkbox, Divider, Flex, Form, Input, message, Modal } from 'antd'
@ -121,7 +127,8 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ model, onUpdateModel, ope
...(isVisionModel(model) ? ['vision'] : []),
...(isEmbeddingModel(model) ? ['embedding'] : []),
...(isReasoningModel(model) ? ['reasoning'] : []),
...(isFunctionCallingModel(model) ? ['function_calling'] : [])
...(isFunctionCallingModel(model) ? ['function_calling'] : []),
...(isWebSearchModel(model) ? ['web_search'] : [])
] as ModelType[]
// 合并现有选择和默认类型
@ -161,6 +168,11 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ model, onUpdateModel, ope
value: 'vision',
disabled: isVisionModel(model) && !selectedTypes.includes('vision')
},
{
label: t('models.type.websearch'),
value: 'web_search',
disabled: isWebSearchModel(model) && !selectedTypes.includes('web_search')
},
{
label: t('models.type.embedding'),
value: 'embedding',

View File

@ -1,7 +1,7 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setEnhanceMode, setMaxResult, setSearchWithTime } from '@renderer/store/websearch'
import { setEnhanceMode, setMaxResult, setOverwrite, setSearchWithTime } from '@renderer/store/websearch'
import { Slider, Switch, Tooltip } from 'antd'
import { t } from 'i18next'
import { FC } from 'react'
@ -12,6 +12,7 @@ const BasicSettings: FC = () => {
const { theme } = useTheme()
const searchWithTime = useAppSelector((state) => state.websearch.searchWithTime)
const enhanceMode = useAppSelector((state) => state.websearch.enhanceMode)
const overwrite = useAppSelector((state) => state.websearch.overwrite)
const maxResults = useAppSelector((state) => state.websearch.maxResults)
const dispatch = useAppDispatch()
@ -26,6 +27,16 @@ const BasicSettings: FC = () => {
<Switch checked={searchWithTime} onChange={(checked) => dispatch(setSearchWithTime(checked))} />
</SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
<SettingRow>
<SettingRowTitle>
{t('settings.websearch.overwrite')}
<Tooltip title={t('settings.websearch.overwrite_tooltip')} placement="right">
<InfoCircleOutlined style={{ marginLeft: 5, color: 'var(--color-icon)', cursor: 'pointer' }} />
</Tooltip>
</SettingRowTitle>
<Switch checked={overwrite} onChange={(checked) => dispatch(setOverwrite(checked))} />
</SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
<SettingRow>
<SettingRowTitle>
{t('settings.websearch.enhance_mode')}

View File

@ -30,6 +30,7 @@ import {
filterEmptyMessages,
filterUserRoleStartMessages
} from '@renderer/services/MessagesService'
import WebSearchService from '@renderer/services/WebSearchService'
import { Assistant, FileType, FileTypes, MCPToolResponse, Message, Model, Provider, Suggestion } from '@renderer/types'
import { removeSpecialCharactersForTopicName } from '@renderer/utils'
import {
@ -232,7 +233,7 @@ export default class GeminiProvider extends BaseProvider {
const tools = mcpToolsToGeminiTools(mcpTools)
const toolResponses: MCPToolResponse[] = []
if (assistant.enableWebSearch && isWebSearchModel(model)) {
if (!WebSearchService.isOverwriteEnabled() && assistant.enableWebSearch && isWebSearchModel(model)) {
tools.push({
// @ts-ignore googleSearch is not a valid tool for Gemini
googleSearch: {}

View File

@ -58,7 +58,10 @@ export async function fetchChatCompletion({
// Search web
if (WebSearchService.isWebSearchEnabled() && assistant.enableWebSearch && assistant.model) {
const webSearchParams = getOpenAIWebSearchParams(assistant, assistant.model)
let webSearchParams = getOpenAIWebSearchParams(assistant, assistant.model)
if (WebSearchService.isOverwriteEnabled()) {
webSearchParams = {}
}
if (isEmpty(webSearchParams) && !isOpenAIWebSearch(assistant.model)) {
const lastMessage = findLast(messages, (m) => m.role === 'user')

View File

@ -52,6 +52,16 @@ class WebSearchService {
return enhanceMode
}
/**
*
* @public
* @returns truefalse
*/
public isOverwriteEnabled(): boolean {
const { overwrite } = this.getWebSearchState()
return overwrite
}
/**
*
* @public

View File

@ -14,6 +14,8 @@ export interface WebSearchState {
excludeDomains: string[]
// 是否启用搜索增强模式
enhanceMode: boolean
// 是否覆盖服务商搜索
overwrite: boolean
}
const initialState: WebSearchState = {
@ -38,7 +40,8 @@ const initialState: WebSearchState = {
searchWithTime: true,
maxResults: 5,
excludeDomains: [],
enhanceMode: false
enhanceMode: false,
overwrite: true
}
const websearchSlice = createSlice({
@ -71,6 +74,9 @@ const websearchSlice = createSlice({
},
setEnhanceMode: (state, action: PayloadAction<boolean>) => {
state.enhanceMode = action.payload
},
setOverwrite: (state, action: PayloadAction<boolean>) => {
state.overwrite = action.payload
}
}
})
@ -83,7 +89,8 @@ export const {
setSearchWithTime,
setExcludeDomains,
setMaxResult,
setEnhanceMode
setEnhanceMode,
setOverwrite
} = websearchSlice.actions
export default websearchSlice.reducer

View File

@ -136,7 +136,7 @@ export type Provider = {
export type ProviderType = 'openai' | 'anthropic' | 'gemini' | 'qwenlm' | 'azure-openai'
export type ModelType = 'text' | 'vision' | 'embedding' | 'reasoning' | 'function_calling'
export type ModelType = 'text' | 'vision' | 'embedding' | 'reasoning' | 'function_calling' | 'web_search'
export type Model = {
id: string