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 true
} }
return false return model.type?.includes('web_search') || false
} }
export function isGenerateImageModel(model: Model): boolean { export function isGenerateImageModel(model: Model): boolean {

View File

@ -27,12 +27,15 @@ export function getWebSearchTools(model: Model): ChatCompletionTool[] {
] ]
} }
return [ if (model?.id.includes('gemini')) {
{ return [
type: 'function', {
function: { type: 'function',
name: 'googleSearch' function: {
name: 'googleSearch'
}
} }
} ]
] }
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.", "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": "Tavily"
}, },
"title": "Web Search" "title": "Web Search",
"overwrite": "Override search service",
"overwrite_tooltip": "Force use search service instead of LLM"
}, },
"quickPhrase": { "quickPhrase": {
"title": "Quick Phrases", "title": "Quick Phrases",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { InfoCircleOutlined } from '@ant-design/icons' import { InfoCircleOutlined } from '@ant-design/icons'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useAppDispatch, useAppSelector } from '@renderer/store' 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 { Slider, Switch, Tooltip } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { FC } from 'react' import { FC } from 'react'
@ -12,6 +12,7 @@ const BasicSettings: FC = () => {
const { theme } = useTheme() const { theme } = useTheme()
const searchWithTime = useAppSelector((state) => state.websearch.searchWithTime) const searchWithTime = useAppSelector((state) => state.websearch.searchWithTime)
const enhanceMode = useAppSelector((state) => state.websearch.enhanceMode) const enhanceMode = useAppSelector((state) => state.websearch.enhanceMode)
const overwrite = useAppSelector((state) => state.websearch.overwrite)
const maxResults = useAppSelector((state) => state.websearch.maxResults) const maxResults = useAppSelector((state) => state.websearch.maxResults)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@ -26,6 +27,16 @@ const BasicSettings: FC = () => {
<Switch checked={searchWithTime} onChange={(checked) => dispatch(setSearchWithTime(checked))} /> <Switch checked={searchWithTime} onChange={(checked) => dispatch(setSearchWithTime(checked))} />
</SettingRow> </SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} /> <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> <SettingRow>
<SettingRowTitle> <SettingRowTitle>
{t('settings.websearch.enhance_mode')} {t('settings.websearch.enhance_mode')}

View File

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

View File

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

View File

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

View File

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

View File

@ -136,7 +136,7 @@ export type Provider = {
export type ProviderType = 'openai' | 'anthropic' | 'gemini' | 'qwenlm' | 'azure-openai' 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 = { export type Model = {
id: string id: string