mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-27 04:31:27 +08:00
fix: restrict using gemini native tools and mcp tools simultaneously (#9361)
* 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解决性能问题
This commit is contained in:
parent
fac8e91d3a
commit
941f86008b
@ -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) {
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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: <UrlContextButton ref={urlContextButtonRef} assistant={assistant} ToolbarButton={ToolbarButton} />,
|
||||
condition: model.id.toLowerCase().includes('gemini')
|
||||
condition: isGeminiModel(model) && isSupportUrlContextProvider(getProviderByModel(model))
|
||||
},
|
||||
{
|
||||
key: 'knowledge_base',
|
||||
|
||||
@ -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<Props> = ({ 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<Props> = ({ 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 缓存事件处理函数
|
||||
|
||||
@ -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<Props> = ({ 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 (
|
||||
<Tooltip placement="top" title={t('chat.input.url_context')} arrow>
|
||||
|
||||
@ -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<Props> = ({ 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<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
return <Globe size={size} style={{ color, fontSize: size }} />
|
||||
}
|
||||
},
|
||||
[enableWebSearch]
|
||||
[]
|
||||
)
|
||||
|
||||
const updateWebSearchProvider = useCallback(
|
||||
async (providerId?: WebSearchProvider['id']) => {
|
||||
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
||||
startTransition(() => {
|
||||
setTimeoutTimer('updateWebSearchProvider', () => {
|
||||
updateAssistant({
|
||||
...assistant,
|
||||
webSearchProviderId: providerId,
|
||||
@ -68,12 +75,11 @@ const WebSearchButton: FC<Props> = ({ 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<Props> = ({ 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<QuickPanelListItem[]>(() => {
|
||||
const isWebSearchModelEnabled = assistant.model && isWebSearchModel(assistant.model)
|
||||
|
||||
40
src/renderer/src/utils/__tests__/assistant.test.ts
Normal file
40
src/renderer/src/utils/__tests__/assistant.test.ts
Normal file
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
5
src/renderer/src/utils/assistant.ts
Normal file
5
src/renderer/src/utils/assistant.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Assistant } from '@renderer/types'
|
||||
|
||||
export const isToolUseModeFunction = (assistant: Assistant) => {
|
||||
return assistant.settings?.toolUseMode === 'function'
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user