diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index 40a215fd..6ba42cbd 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -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 { diff --git a/src/renderer/src/config/tools.ts b/src/renderer/src/config/tools.ts index 12ff6676..dcb16b80 100644 --- a/src/renderer/src/config/tools.ts +++ b/src/renderer/src/config/tools.ts @@ -27,12 +27,15 @@ export function getWebSearchTools(model: Model): ChatCompletionTool[] { ] } - return [ - { - type: 'function', - function: { - name: 'googleSearch' + if (model?.id.includes('gemini')) { + return [ + { + type: 'function', + function: { + name: 'googleSearch' + } } - } - ] + ] + } + return [] } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 067476e0..2d5727a3 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -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", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index b08a31f4..0179a70c 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1291,7 +1291,9 @@ "description": "Tavily は、AI エージェントのために特別に開発された検索エンジンで、最新の結果、インテリジェントな検索提案、そして深い研究能力を提供します", "title": "Tavily" }, - "title": "ウェブ検索" + "title": "ウェブ検索", + "overwrite": "サービス検索を上書き", + "overwrite_tooltip": "大規模言語モデルではなく、サービス検索を使用する" }, "general.auto_check_update.title": "自動更新チェックを有効にする", "quickPhrase": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 83cc3087..31f2268c 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1291,7 +1291,9 @@ "description": "Tavily — это поисковая система, специально разработанная для ИИ-агентов, предоставляющая актуальные результаты, умные предложения по запросам и глубокие исследовательские возможности", "title": "Tavily" }, - "title": "Поиск в Интернете" + "title": "Поиск в Интернете", + "overwrite": "Переопределить поставщика поиска", + "overwrite_tooltip": "Использовать поставщика поиска вместо LLM" }, "general.auto_check_update.title": "Включить автоматическую проверку обновлений", "quickPhrase": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 09790eb0..b14951af 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1279,6 +1279,8 @@ "check_success": "验证成功", "enhance_mode": "搜索增强模式", "enhance_mode_tooltip": "使用默认模型提取关键词后搜索", + "overwrite": "覆盖服务商搜索", + "overwrite_tooltip": "强制使用搜索服务商而不是大语言模型进行搜索", "get_api_key": "点击这里获取密钥", "no_provider_selected": "请选择搜索服务商后再检查", "search_max_result": "搜索结果个数", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 4754300f..06e83d9d 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1291,7 +1291,9 @@ "description": "Tavily 是一個為 AI 代理量身訂製的搜尋引擎,提供即時、準確的結果、智慧查詢建議和深入的研究能力", "title": "Tavily" }, - "title": "網路搜尋" + "title": "網路搜尋", + "overwrite": "覆蓋搜尋服務商", + "overwrite_tooltip": "強制使用搜尋服務商而不是大語言模型進行搜尋" }, "general.auto_check_update.title": "啟用自動更新檢查", "quickPhrase": { diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelEditContent.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelEditContent.tsx index 7d458dbb..034455c7 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelEditContent.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelEditContent.tsx @@ -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 = ({ 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 = ({ 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', diff --git a/src/renderer/src/pages/settings/WebSearchSettings/BasicSettings.tsx b/src/renderer/src/pages/settings/WebSearchSettings/BasicSettings.tsx index 64a4ef3b..d3e15c58 100644 --- a/src/renderer/src/pages/settings/WebSearchSettings/BasicSettings.tsx +++ b/src/renderer/src/pages/settings/WebSearchSettings/BasicSettings.tsx @@ -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 = () => { dispatch(setSearchWithTime(checked))} /> + + + {t('settings.websearch.overwrite')} + + + + + dispatch(setOverwrite(checked))} /> + + {t('settings.websearch.enhance_mode')} diff --git a/src/renderer/src/providers/AiProvider/GeminiProvider.ts b/src/renderer/src/providers/AiProvider/GeminiProvider.ts index 520ace22..7717d6cb 100644 --- a/src/renderer/src/providers/AiProvider/GeminiProvider.ts +++ b/src/renderer/src/providers/AiProvider/GeminiProvider.ts @@ -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: {} diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index d2bceb79..22763f89 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -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') diff --git a/src/renderer/src/services/WebSearchService.ts b/src/renderer/src/services/WebSearchService.ts index 0de0e513..c4a06845 100644 --- a/src/renderer/src/services/WebSearchService.ts +++ b/src/renderer/src/services/WebSearchService.ts @@ -52,6 +52,16 @@ class WebSearchService { return enhanceMode } + /** + * 检查是否启用覆盖搜索 + * @public + * @returns 如果启用覆盖搜索则返回true,否则返回false + */ + public isOverwriteEnabled(): boolean { + const { overwrite } = this.getWebSearchState() + return overwrite + } + /** * 获取当前默认的网络搜索提供商 * @public diff --git a/src/renderer/src/store/websearch.ts b/src/renderer/src/store/websearch.ts index ac3f4627..d31edf51 100644 --- a/src/renderer/src/store/websearch.ts +++ b/src/renderer/src/store/websearch.ts @@ -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) => { state.enhanceMode = action.payload + }, + setOverwrite: (state, action: PayloadAction) => { + state.overwrite = action.payload } } }) @@ -83,7 +89,8 @@ export const { setSearchWithTime, setExcludeDomains, setMaxResult, - setEnhanceMode + setEnhanceMode, + setOverwrite } = websearchSlice.actions export default websearchSlice.reducer diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 7047c469..5c5df9c3 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -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