From 941f86008bc6e1c3971f0887eb66aa8de533959f Mon Sep 17 00:00:00 2001
From: Phantom <59059173+EurFelux@users.noreply.github.com>
Date: Wed, 27 Aug 2025 14:37:46 +0800
Subject: [PATCH] fix: restrict using gemini native tools and mcp tools
simultaneously (#9361)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(providers): 添加对支持URL上下文的提供者类型的检查
新增 `isSupportUrlContextProvider` 函数用于检查提供者是否支持URL上下文功能
* fix(InputbarTools): 修复URL上下文按钮显示条件判断
添加对模型提供者是否支持URL上下文的检查
* fix(gemini): 修复原生工具与函数调用同时启用时的冲突
当同时启用web搜索和URL上下文工具时,如果已存在函数调用工具,则添加警告日志提示当前不支持同时使用
* feat(i18n): 限制 Gemini 同时使用网页上下文与 MCP 工具
添加多语言翻译文案和功能实现,当用户尝试同时启用网页上下文和 MCP 工具时,显示警告提示并自动禁用网页上下文
* perf(WebSearchButton): 使用定时器优化更新性能避免卡顿
移除startTransition并使用useTimer的setTimeoutTimer来延迟更新操作,解决updateAssistant导致的快捷面板关闭卡顿问题
* feat(i18n): 限制 Gemini 原生搜索工具与函数调用的同时使用
添加对 Gemini 原生搜索工具与函数调用同时使用时的冲突检测
更新相关国际化文案和功能实现
* fix(GeminiAPIClient): 修复工具使用模式判断逻辑
当工具使用模式为'prompt'时应该允许使用native tool
* reafactor: 简化 Gemini 模型下工具使用模式的 URL 上下文和网页搜索检查逻辑
* fix(WebSearchButton): 修复条件判断
* refactor(utils): 提取函数工具使用模式判断逻辑到单独函数
* test(assistant): 添加工具使用模式功能的单元测试
* refactor(InputbarTools): 使用isGeminiModel函数替代字符串检查
简化模型类型检查逻辑,提高代码可读性和维护性
* perf(Inputbar): 使用setTimeoutTimer替代startTransition解决性能问题
---
.../aiCore/clients/gemini/GeminiAPIClient.ts | 23 +++++----
src/renderer/src/config/models.ts | 5 ++
src/renderer/src/config/providers.ts | 14 ++++++
src/renderer/src/i18n/locales/en-us.json | 4 +-
src/renderer/src/i18n/locales/ja-jp.json | 4 +-
src/renderer/src/i18n/locales/ru-ru.json | 4 +-
src/renderer/src/i18n/locales/zh-cn.json | 4 +-
src/renderer/src/i18n/locales/zh-tw.json | 4 +-
src/renderer/src/i18n/translate/el-gr.json | 4 +-
src/renderer/src/i18n/translate/es-es.json | 4 +-
src/renderer/src/i18n/translate/fr-fr.json | 4 +-
src/renderer/src/i18n/translate/pt-pt.json | 4 +-
.../src/pages/home/Inputbar/InputbarTools.tsx | 6 ++-
.../pages/home/Inputbar/MCPToolsButton.tsx | 31 ++++++++++--
.../pages/home/Inputbar/UrlContextbutton.tsx | 17 ++++++-
.../pages/home/Inputbar/WebSearchButton.tsx | 50 ++++++++++++++-----
.../src/utils/__tests__/assistant.test.ts | 40 +++++++++++++++
src/renderer/src/utils/assistant.ts | 5 ++
src/renderer/src/utils/mcp-tools.ts | 5 +-
19 files changed, 192 insertions(+), 40 deletions(-)
create mode 100644 src/renderer/src/utils/__tests__/assistant.test.ts
create mode 100644 src/renderer/src/utils/assistant.ts
diff --git a/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts b/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts
index bdd7689d6f..b6e17398bf 100644
--- a/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts
+++ b/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts
@@ -52,6 +52,7 @@ import {
GeminiSdkRawOutput,
GeminiSdkToolCall
} from '@renderer/types/sdk'
+import { isToolUseModeFunction } from '@renderer/utils/assistant'
import {
geminiFunctionCallToMcpTool,
isEnabledToolUse,
@@ -476,16 +477,20 @@ export class GeminiAPIClient extends BaseApiClient<
}
}
- if (enableWebSearch) {
- tools.push({
- googleSearch: {}
- })
- }
+ if (tools.length === 0 || !isToolUseModeFunction(assistant)) {
+ if (enableWebSearch) {
+ tools.push({
+ googleSearch: {}
+ })
+ }
- if (enableUrlContext) {
- tools.push({
- urlContext: {}
- })
+ if (enableUrlContext) {
+ tools.push({
+ urlContext: {}
+ })
+ }
+ } else if (enableWebSearch || enableUrlContext) {
+ logger.warn('Native tools cannot be used with function calling for now.')
}
if (isGemmaModel(model) && assistant.prompt) {
diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts
index 19247e63d3..fb40d070d9 100644
--- a/src/renderer/src/config/models.ts
+++ b/src/renderer/src/config/models.ts
@@ -3307,6 +3307,11 @@ export const isGPT5SeriesModel = (model: Model) => {
return modelId.includes('gpt-5')
}
+export const isGeminiModel = (model: Model) => {
+ const modelId = getLowerBaseModelName(model.id)
+ return modelId.includes('gemini')
+}
+
export const isOpenAIOpenWeightModel = (model: Model) => {
const modelId = getLowerBaseModelName(model.id)
return modelId.includes('gpt-oss')
diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts
index ac73f47cc7..795b243c62 100644
--- a/src/renderer/src/config/providers.ts
+++ b/src/renderer/src/config/providers.ts
@@ -56,6 +56,7 @@ import {
isSystemProvider,
OpenAIServiceTiers,
Provider,
+ ProviderType,
SystemProvider,
SystemProviderId
} from '@renderer/types'
@@ -1303,3 +1304,16 @@ export const isSupportServiceTierProvider = (provider: Provider) => {
(isSystemProvider(provider) && !NOT_SUPPORT_SERVICE_TIER_PROVIDERS.some((pid) => pid === provider.id))
)
}
+
+const SUPPORT_GEMINI_URL_CONTEXT_PROVIDER_TYPES = ['gemini', 'vertexai'] as const satisfies ProviderType[]
+
+export const isSupportUrlContextProvider = (provider: Provider) => {
+ return SUPPORT_GEMINI_URL_CONTEXT_PROVIDER_TYPES.some((type) => type === provider.type)
+}
+
+const SUPPORT_GEMINI_NATIVE_WEB_SEARCH_PROVIDERS = ['gemini', 'vertexai'] as const satisfies SystemProviderId[]
+
+/** 判断是否是使用 Gemini 原生搜索工具的 provider. 目前假设只有官方 API 使用原生工具 */
+export const isGeminiWebSearchProvider = (provider: Provider) => {
+ return SUPPORT_GEMINI_NATIVE_WEB_SEARCH_PROVIDERS.some((id) => id === provider.id)
+}
diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json
index 6b3093e52f..19240331b4 100644
--- a/src/renderer/src/i18n/locales/en-us.json
+++ b/src/renderer/src/i18n/locales/en-us.json
@@ -390,8 +390,10 @@
"parse_tool_call": "Unable to convert to a valid tool call format: {{toolCall}}"
},
"warning": {
+ "gemini_web_search": "Gemini does not support using native web search tools and function calling simultaneously",
"multiple_tools": "Multiple matching MCP tools exist, {{tool}} has been selected",
- "no_tool": "No matching MCP tool found for {{tool}}"
+ "no_tool": "No matching MCP tool found for {{tool}}",
+ "url_context": "Gemini does not support using url context and function calling simultaneously"
}
},
"message": {
diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json
index 499ad0aa15..d56c0590b3 100644
--- a/src/renderer/src/i18n/locales/ja-jp.json
+++ b/src/renderer/src/i18n/locales/ja-jp.json
@@ -390,8 +390,10 @@
"parse_tool_call": "有効なツール呼び出し形式に変換できません:{{toolCall}}"
},
"warning": {
+ "gemini_web_search": "Geminiは、ネイティブのネットワーク検索ツールと関数呼び出しを同時に使用することをサポートしていません。",
"multiple_tools": "複数の一致するMCPツールが存在するため、{{tool}} が選択されました",
- "no_tool": "必要なMCPツール {{tool}} が見つかりません"
+ "no_tool": "必要なMCPツール {{tool}} が見つかりません",
+ "url_context": "Geminiは、URLコンテキストと関数呼び出しを同時に使用することをサポートしていません。"
}
},
"message": {
diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json
index 23c2a3efd4..43922086c3 100644
--- a/src/renderer/src/i18n/locales/ru-ru.json
+++ b/src/renderer/src/i18n/locales/ru-ru.json
@@ -390,8 +390,10 @@
"parse_tool_call": "Не удалось преобразовать в действительный формат вызова инструмента: {{toolCall}}"
},
"warning": {
+ "gemini_web_search": "Gemini не поддерживает одновременное использование встроенного инструмента поиска в сети и вызова функций",
"multiple_tools": "Существует несколько совпадающих инструментов MCP, выбран {{tool}}",
- "no_tool": "Не удалось сопоставить требуемый инструмент MCP {{tool}}"
+ "no_tool": "Не удалось сопоставить требуемый инструмент MCP {{tool}}",
+ "url_context": "Gemini не поддерживает одновременное использование контекста веб-страницы и вызова функций"
}
},
"message": {
diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json
index c500655869..d90ab78caa 100644
--- a/src/renderer/src/i18n/locales/zh-cn.json
+++ b/src/renderer/src/i18n/locales/zh-cn.json
@@ -390,8 +390,10 @@
"parse_tool_call": "无法转换为有效的工具调用格式:{{toolCall}}"
},
"warning": {
+ "gemini_web_search": "Gemini 不支持同时使用原生网络搜索工具与函数调用",
"multiple_tools": "存在多个匹配的MCP工具,已选择 {{tool}}",
- "no_tool": "未匹配到所需的MCP工具 {{tool}}"
+ "no_tool": "未匹配到所需的MCP工具 {{tool}}",
+ "url_context": "Gemini 不支持同时使用网页上下文与函数调用"
}
},
"message": {
diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json
index 0ef8fdd50a..6bd3bcf2b9 100644
--- a/src/renderer/src/i18n/locales/zh-tw.json
+++ b/src/renderer/src/i18n/locales/zh-tw.json
@@ -390,8 +390,10 @@
"parse_tool_call": "無法轉換為有效的工具呼叫格式:{{toolCall}}"
},
"warning": {
+ "gemini_web_search": "Gemini 不支援同時使用原生網路搜尋工具與函數呼叫",
"multiple_tools": "存在多個匹配的MCP工具,已選擇 {{tool}}",
- "no_tool": "未匹配到所需的MCP工具 {{tool}}"
+ "no_tool": "未匹配到所需的MCP工具 {{tool}}",
+ "url_context": "Gemini 不支援同時使用網頁內容與函數呼叫"
}
},
"message": {
diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json
index f9b54f88fc..c91c73d6e0 100644
--- a/src/renderer/src/i18n/translate/el-gr.json
+++ b/src/renderer/src/i18n/translate/el-gr.json
@@ -390,8 +390,10 @@
"parse_tool_call": "Δεν είναι δυνατή η μετατροπή σε έγκυρη μορφή κλήσης εργαλείου: {{toolCall}}"
},
"warning": {
+ "gemini_web_search": "Το Gemini δεν υποστηρίζει την ταυτόχρονη χρήση του εργαλείου αυτόματης αναζήτησης και της κλήσης συναρτήσεων",
"multiple_tools": "Υπάρχουν πολλαπλά εργαλεία MCP που ταιριάζουν, επιλέχθηκε το {{tool}}",
- "no_tool": "Δεν βρέθηκε το απαιτούμενο εργαλείο MCP {{tool}}"
+ "no_tool": "Δεν βρέθηκε το απαιτούμενο εργαλείο MCP {{tool}}",
+ "url_context": "Το Gemini δεν υποστηρίζει την ταυτόχρονη χρήση πλοήγησης στον ιστό και κλήσης συναρτήσεων"
}
},
"message": {
diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json
index 9565489985..ec6c42f7cc 100644
--- a/src/renderer/src/i18n/translate/es-es.json
+++ b/src/renderer/src/i18n/translate/es-es.json
@@ -390,8 +390,10 @@
"parse_tool_call": "No se puede convertir al formato de llamada de herramienta válido: {{toolCall}}"
},
"warning": {
+ "gemini_web_search": "Gemini no admite el uso simultáneo de herramientas de búsqueda nativa y llamadas de funciones",
"multiple_tools": "Existen múltiples herramientas MCP coincidentes, se ha seleccionado {{tool}}",
- "no_tool": "No se encontró la herramienta MCP requerida {{tool}}"
+ "no_tool": "No se encontró la herramienta MCP requerida {{tool}}",
+ "url_context": "Gemini no admite el uso simultáneo del contexto de la página web y las llamadas a funciones."
}
},
"message": {
diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json
index 87fd54daef..e3166691c9 100644
--- a/src/renderer/src/i18n/translate/fr-fr.json
+++ b/src/renderer/src/i18n/translate/fr-fr.json
@@ -390,8 +390,10 @@
"parse_tool_call": "Impossible de convertir au format d'appel d'outil valide : {{toolCall}}"
},
"warning": {
+ "gemini_web_search": "Gemini ne prend pas en charge l'utilisation simultanée de l'outil de recherche natif et de l'appel de fonctions",
"multiple_tools": "Il existe plusieurs outils MCP correspondants, {{tool}} a été sélectionné",
- "no_tool": "Aucun outil MCP requis {{tool}} n'a été trouvé"
+ "no_tool": "Aucun outil MCP requis {{tool}} n'a été trouvé",
+ "url_context": "Gemini ne prend pas en charge l'utilisation simultanée du contexte de la page Web et des appels de fonction"
}
},
"message": {
diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json
index 999476167c..fd06442a63 100644
--- a/src/renderer/src/i18n/translate/pt-pt.json
+++ b/src/renderer/src/i18n/translate/pt-pt.json
@@ -390,8 +390,10 @@
"parse_tool_call": "Não é possível converter para um formato de chamada de ferramenta válido: {{toolCall}}"
},
"warning": {
+ "gemini_web_search": "O Gemini não suporta o uso simultâneo da ferramenta de pesquisa nativa e da chamada de funções.",
"multiple_tools": "Existem várias ferramentas MCP correspondentes, a ferramenta {{tool}} foi selecionada",
- "no_tool": "Nenhuma ferramenta MCP necessária correspondente encontrada {{tool}}"
+ "no_tool": "Nenhuma ferramenta MCP necessária correspondente encontrada {{tool}}",
+ "url_context": "O Gemini não suporta o uso simultâneo de contexto da página da web e chamadas de função"
}
},
"message": {
diff --git a/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx b/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx
index 0f60ef9e7f..079b7801b1 100644
--- a/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx
+++ b/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx
@@ -1,6 +1,8 @@
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
import { QuickPanelListItem } from '@renderer/components/QuickPanel'
-import { isGenerateImageModel, isMandatoryWebSearchModel } from '@renderer/config/models'
+import { isGeminiModel, isGenerateImageModel, isMandatoryWebSearchModel } from '@renderer/config/models'
+import { isSupportUrlContextProvider } from '@renderer/config/providers'
+import { getProviderByModel } from '@renderer/services/AssistantService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setIsCollapsed, setToolOrder } from '@renderer/store/inputTools'
import { Assistant, FileType, KnowledgeBase, Model } from '@renderer/types'
@@ -347,7 +349,7 @@ const InputbarTools = ({
key: 'url_context',
label: t('chat.input.url_context'),
component: ,
- condition: model.id.toLowerCase().includes('gemini')
+ condition: isGeminiModel(model) && isSupportUrlContextProvider(getProviderByModel(model))
},
{
key: 'knowledge_base',
diff --git a/src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx b/src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx
index b4589fbc24..7d0b125b91 100644
--- a/src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx
+++ b/src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx
@@ -1,9 +1,13 @@
import { QuickPanelListItem, useQuickPanel } from '@renderer/components/QuickPanel'
+import { isGeminiModel } from '@renderer/config/models'
+import { isGeminiWebSearchProvider, isSupportUrlContextProvider } from '@renderer/config/providers'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { useTimer } from '@renderer/hooks/useTimer'
+import { getProviderByModel } from '@renderer/services/AssistantService'
import { EventEmitter } from '@renderer/services/EventService'
import { Assistant, MCPPrompt, MCPResource, MCPServer } from '@renderer/types'
+import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { Form, Input, Tooltip } from 'antd'
import { CircleX, Hammer, Plus } from 'lucide-react'
import React, { FC, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
@@ -117,6 +121,7 @@ const MCPToolsButton: FC = ({ ref, setInputValue, resizeTextArea, Toolbar
const [form] = Form.useForm()
const { updateAssistant, assistant } = useAssistant(props.assistant.id)
+ const model = assistant.model
const { setTimeoutTimer } = useTimer()
// 使用 useRef 存储不需要触发重渲染的值
@@ -135,13 +140,33 @@ const MCPToolsButton: FC = ({ ref, setInputValue, resizeTextArea, Toolbar
)
const handleMcpServerSelect = useCallback(
(server: MCPServer) => {
+ const update = { ...assistant }
if (assistantMcpServers.some((s) => s.id === server.id)) {
- updateAssistant({ ...assistant, mcpServers: mcpServers?.filter((s) => s.id !== server.id) })
+ update.mcpServers = mcpServers.filter((s) => s.id !== server.id)
} else {
- updateAssistant({ ...assistant, mcpServers: [...mcpServers, server] })
+ update.mcpServers = [...mcpServers, server]
}
+
+ // only for gemini
+ if (update.mcpServers.length > 0 && isGeminiModel(model) && isToolUseModeFunction(assistant)) {
+ const provider = getProviderByModel(model)
+ if (isSupportUrlContextProvider(provider) && assistant.enableUrlContext) {
+ window.message.warning(t('chat.mcp.warning.url_context'))
+ update.enableUrlContext = false
+ }
+ if (
+ // 非官方 API (openrouter etc.) 可能支持同时启用内置搜索和函数调用
+ // 这里先假设 gemini type 和 vertexai type 不支持
+ isGeminiWebSearchProvider(provider) &&
+ assistant.enableWebSearch
+ ) {
+ window.message.warning(t('chat.mcp.warning.gemini_web_search'))
+ update.enableWebSearch = false
+ }
+ }
+ updateAssistant(update)
},
- [assistant, assistantMcpServers, mcpServers, updateAssistant]
+ [assistant, assistantMcpServers, mcpServers, model, t, updateAssistant]
)
// 使用 useRef 缓存事件处理函数
diff --git a/src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx b/src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx
index cfbf12f538..ff92fe1161 100644
--- a/src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx
+++ b/src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx
@@ -1,6 +1,7 @@
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useTimer } from '@renderer/hooks/useTimer'
import { Assistant } from '@renderer/types'
+import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { Tooltip } from 'antd'
import { Link } from 'lucide-react'
import { FC, memo, useCallback } from 'react'
@@ -27,11 +28,23 @@ const UrlContextButton: FC = ({ assistant, ToolbarButton }) => {
setTimeoutTimer(
'handleToggle',
() => {
- updateAssistant({ ...assistant, enableUrlContext: urlContentNewState })
+ const update = { ...assistant }
+ if (
+ assistant.mcpServers &&
+ assistant.mcpServers.length > 0 &&
+ urlContentNewState === true &&
+ isToolUseModeFunction(assistant)
+ ) {
+ update.enableUrlContext = false
+ window.message.warning(t('chat.mcp.warning.url_context'))
+ } else {
+ update.enableUrlContext = urlContentNewState
+ }
+ updateAssistant(update)
},
100
)
- }, [setTimeoutTimer, updateAssistant, assistant, urlContentNewState])
+ }, [setTimeoutTimer, assistant, urlContentNewState, updateAssistant, t])
return (
diff --git a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx
index 3d84ef690a..9f71e0a84d 100644
--- a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx
+++ b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx
@@ -1,15 +1,20 @@
import { BaiduOutlined, GoogleOutlined } from '@ant-design/icons'
+import { loggerService } from '@logger'
import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo } from '@renderer/components/Icons'
import { QuickPanelListItem, useQuickPanel } from '@renderer/components/QuickPanel'
-import { isWebSearchModel } from '@renderer/config/models'
+import { isGeminiModel, isWebSearchModel } from '@renderer/config/models'
+import { isGeminiWebSearchProvider } from '@renderer/config/providers'
import { useAssistant } from '@renderer/hooks/useAssistant'
+import { useTimer } from '@renderer/hooks/useTimer'
import { useWebSearchProviders } from '@renderer/hooks/useWebSearchProviders'
+import { getProviderByModel } from '@renderer/services/AssistantService'
import WebSearchService from '@renderer/services/WebSearchService'
import { Assistant, WebSearchProvider, WebSearchProviderId } from '@renderer/types'
import { hasObjectKey } from '@renderer/utils'
+import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { Tooltip } from 'antd'
import { Globe } from 'lucide-react'
-import { FC, memo, startTransition, useCallback, useImperativeHandle, useMemo } from 'react'
+import { FC, memo, useCallback, useImperativeHandle, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
export interface WebSearchButtonRef {
@@ -22,11 +27,14 @@ interface Props {
ToolbarButton: any
}
+const logger = loggerService.withContext('WebSearchButton')
+
const WebSearchButton: FC = ({ ref, assistant, ToolbarButton }) => {
const { t } = useTranslation()
const quickPanel = useQuickPanel()
const { providers } = useWebSearchProviders()
const { updateAssistant } = useAssistant(assistant.id)
+ const { setTimeoutTimer } = useTimer()
// 注意:assistant.enableWebSearch 有不同的语义
/** 表示是否启用网络搜索 */
@@ -54,13 +62,12 @@ const WebSearchButton: FC = ({ ref, assistant, ToolbarButton }) => {
return
}
},
- [enableWebSearch]
+ []
)
const updateWebSearchProvider = useCallback(
async (providerId?: WebSearchProvider['id']) => {
- // TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
- startTransition(() => {
+ setTimeoutTimer('updateWebSearchProvider', () => {
updateAssistant({
...assistant,
webSearchProviderId: providerId,
@@ -68,12 +75,11 @@ const WebSearchButton: FC = ({ ref, assistant, ToolbarButton }) => {
})
})
},
- [assistant, updateAssistant]
+ [assistant, setTimeoutTimer, updateAssistant]
)
const updateQuickPanelItem = useCallback(
async (providerId?: WebSearchProvider['id']) => {
- // TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
if (providerId === assistant.webSearchProviderId) {
updateWebSearchProvider(undefined)
} else {
@@ -84,11 +90,31 @@ const WebSearchButton: FC = ({ ref, assistant, ToolbarButton }) => {
)
const updateToModelBuiltinWebSearch = useCallback(async () => {
- // TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
- startTransition(() => {
- updateAssistant({ ...assistant, webSearchProviderId: undefined, enableWebSearch: !assistant.enableWebSearch })
- })
- }, [assistant, updateAssistant])
+ const update = {
+ ...assistant,
+ webSearchProviderId: undefined,
+ enableWebSearch: !assistant.enableWebSearch
+ }
+ const model = assistant.model
+ const provider = getProviderByModel(model)
+ if (!model) {
+ logger.error('Model does not exist.')
+ window.message.error(t('error.model.not_exists'))
+ return
+ }
+ if (
+ isGeminiWebSearchProvider(provider) &&
+ isGeminiModel(model) &&
+ isToolUseModeFunction(assistant) &&
+ update.enableWebSearch &&
+ assistant.mcpServers &&
+ assistant.mcpServers.length > 0
+ ) {
+ update.enableWebSearch = false
+ window.message.warning(t('chat.mcp.warning.gemini_web_search'))
+ }
+ setTimeoutTimer('updateSelectedWebSearchBuiltin', () => updateAssistant(update), 200)
+ }, [assistant, setTimeoutTimer, t, updateAssistant])
const providerItems = useMemo(() => {
const isWebSearchModelEnabled = assistant.model && isWebSearchModel(assistant.model)
diff --git a/src/renderer/src/utils/__tests__/assistant.test.ts b/src/renderer/src/utils/__tests__/assistant.test.ts
new file mode 100644
index 0000000000..1bc6fca8e3
--- /dev/null
+++ b/src/renderer/src/utils/__tests__/assistant.test.ts
@@ -0,0 +1,40 @@
+import { Assistant } from '@renderer/types'
+import { cloneDeep } from 'lodash'
+import { describe, expect, it } from 'vitest'
+
+import { isToolUseModeFunction } from '../assistant'
+
+describe('assistant', () => {
+ const assistant: Assistant = {
+ id: 'assistant',
+ name: 'assistant',
+ prompt: '',
+ topics: [],
+ type: ''
+ }
+
+ describe('isToolUseModeFunction', () => {
+ it('should detect function tool use mode', () => {
+ const mockAssistant = cloneDeep(assistant)
+ mockAssistant.settings = { toolUseMode: 'function' }
+ expect(isToolUseModeFunction(mockAssistant)).toBe(true)
+ })
+
+ it('should detect non-function tool use mode', () => {
+ const mockAssistant = cloneDeep(assistant)
+ mockAssistant.settings = { toolUseMode: 'prompt' }
+ expect(isToolUseModeFunction(mockAssistant)).toBe(false)
+ })
+
+ it('should handle undefined settings', () => {
+ const mockAssistant = cloneDeep(assistant)
+ expect(isToolUseModeFunction(mockAssistant)).toBe(false)
+ })
+
+ it('should handle undefined toolUseMode', () => {
+ const mockAssistant = cloneDeep(assistant)
+ mockAssistant.settings = {}
+ expect(isToolUseModeFunction(mockAssistant)).toBe(false)
+ })
+ })
+})
diff --git a/src/renderer/src/utils/assistant.ts b/src/renderer/src/utils/assistant.ts
new file mode 100644
index 0000000000..e08214aee7
--- /dev/null
+++ b/src/renderer/src/utils/assistant.ts
@@ -0,0 +1,5 @@
+import { Assistant } from '@renderer/types'
+
+export const isToolUseModeFunction = (assistant: Assistant) => {
+ return assistant.settings?.toolUseMode === 'function'
+}
diff --git a/src/renderer/src/utils/mcp-tools.ts b/src/renderer/src/utils/mcp-tools.ts
index 6534c2e103..3be44b1091 100644
--- a/src/renderer/src/utils/mcp-tools.ts
+++ b/src/renderer/src/utils/mcp-tools.ts
@@ -28,6 +28,7 @@ import {
ChatCompletionTool
} from 'openai/resources'
+import { isToolUseModeFunction } from './assistant'
import { convertBase64ImageToAwsBedrockFormat } from './aws-bedrock-utils'
import { filterProperties, processSchemaForO3 } from './mcp-schema'
@@ -823,9 +824,7 @@ export function mcpToolCallResponseToAwsBedrockMessage(
export function isEnabledToolUse(assistant: Assistant) {
if (assistant.model) {
- if (isFunctionCallingModel(assistant.model)) {
- return assistant.settings?.toolUseMode === 'function'
- }
+ return isFunctionCallingModel(assistant.model) && isToolUseModeFunction(assistant)
}
return false