From 483b4e090e767801971b1da1dab943f5bd0ba088 Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Sun, 28 Sep 2025 16:27:26 +0800 Subject: [PATCH] feat(toolUsePlugin): separate provider-defined tools from prompt tool (#10428) * feat(toolUsePlugin): separate provider-defined tools from prompt tools in context - Enhanced the `createPromptToolUsePlugin` function to distinguish between provider-defined tools and other tools, ensuring only non-provider-defined tools are saved in the context. - Updated the handling of tools in the transformed parameters to retain provider-defined tools while removing others. - Improved error handling in `ToolExecutor` by logging tool and tool use details for better debugging. - Refactored various components to use `NormalToolResponse` instead of `MCPToolResponse`, aligning with the new response structure across multiple message components. * refactor(toolUsePlugin): streamline tool handling in createPromptToolUsePlugin - Updated the `createPromptToolUsePlugin` function to improve type handling for tools, ensuring proper type inference and reducing the use of type assertions. - Enhanced clarity in the separation of provider-defined tools and prompt tools, maintaining functionality while improving code readability. * refactor(ToolExecutor): remove debug logging for tool and tool use - Removed console logging for tool and tool use details in the ToolExecutor class to clean up the code and improve performance. This change enhances the clarity of the code without affecting functionality. --- .../toolUsePlugin/promptToolUsePlugin.ts | 30 +++++++++++++++---- .../built-in/webSearchPlugin/helper.ts | 28 ++++++++++------- .../Messages/Tools/MessageKnowledgeSearch.tsx | 6 ++-- .../home/Messages/Tools/MessageMcpTool.tsx | 3 +- .../Messages/Tools/MessageMemorySearch.tsx | 4 +-- .../pages/home/Messages/Tools/MessageTool.tsx | 17 ++++++----- .../home/Messages/Tools/MessageWebSearch.tsx | 4 +-- src/renderer/src/types/newMessage.ts | 3 +- 8 files changed, 63 insertions(+), 32 deletions(-) diff --git a/packages/aiCore/src/core/plugins/built-in/toolUsePlugin/promptToolUsePlugin.ts b/packages/aiCore/src/core/plugins/built-in/toolUsePlugin/promptToolUsePlugin.ts index fce028f5cd..a2cc7d9aff 100644 --- a/packages/aiCore/src/core/plugins/built-in/toolUsePlugin/promptToolUsePlugin.ts +++ b/packages/aiCore/src/core/plugins/built-in/toolUsePlugin/promptToolUsePlugin.ts @@ -261,22 +261,39 @@ export const createPromptToolUsePlugin = (config: PromptToolUseConfig = {}) => { return params } - context.mcpTools = params.tools + // 分离 provider-defined 和其他类型的工具 + const providerDefinedTools: ToolSet = {} + const promptTools: ToolSet = {} - // 构建系统提示符 + for (const [toolName, tool] of Object.entries(params.tools as ToolSet)) { + if (tool.type === 'provider-defined') { + // provider-defined 类型的工具保留在 tools 参数中 + providerDefinedTools[toolName] = tool + } else { + // 其他工具转换为 prompt 模式 + promptTools[toolName] = tool + } + } + + // 只有当有非 provider-defined 工具时才保存到 context + if (Object.keys(promptTools).length > 0) { + context.mcpTools = promptTools + } + + // 构建系统提示符(只包含非 provider-defined 工具) const userSystemPrompt = typeof params.system === 'string' ? params.system : '' - const systemPrompt = buildSystemPrompt(userSystemPrompt, params.tools) + const systemPrompt = buildSystemPrompt(userSystemPrompt, promptTools) let systemMessage: string | null = systemPrompt if (config.createSystemMessage) { // 🎯 如果用户提供了自定义处理函数,使用它 systemMessage = config.createSystemMessage(systemPrompt, params, context) } - // 移除 tools,改为 prompt 模式 + // 保留 provider-defined tools,移除其他 tools const transformedParams = { ...params, ...(systemMessage ? { system: systemMessage } : {}), - tools: undefined + tools: Object.keys(providerDefinedTools).length > 0 ? providerDefinedTools : undefined } context.originalParams = transformedParams return transformedParams @@ -285,8 +302,9 @@ export const createPromptToolUsePlugin = (config: PromptToolUseConfig = {}) => { let textBuffer = '' // let stepId = '' + // 如果没有需要 prompt 模式处理的工具,直接返回原始流 if (!context.mcpTools) { - throw new Error('No tools available') + return new TransformStream() } // 从 context 中获取或初始化 usage 累加器 diff --git a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts index 4845ce4ace..95c2cdda2c 100644 --- a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts +++ b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts @@ -1,6 +1,7 @@ import { anthropic } from '@ai-sdk/anthropic' import { google } from '@ai-sdk/google' import { openai } from '@ai-sdk/openai' +import { InferToolInput, InferToolOutput } from 'ai' import { ProviderOptionsMap } from '../../../options/types' import { OpenRouterSearchConfig } from './openrouter' @@ -58,24 +59,31 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = { export type WebSearchToolOutputSchema = { // Anthropic 工具 - 手动定义 - anthropicWebSearch: Array<{ - url: string - title: string - pageAge: string | null - encryptedContent: string - type: string - }> + anthropic: InferToolOutput> // OpenAI 工具 - 基于实际输出 - openaiWebSearch: { + // TODO: 上游定义不规范,是unknown + // openai: InferToolOutput> + openai: { + status: 'completed' | 'failed' + } + 'openai-chat': { status: 'completed' | 'failed' } - // Google 工具 - googleSearch: { + // TODO: 上游定义不规范,是unknown + // google: InferToolOutput> + google: { webSearchQueries?: string[] groundingChunks?: Array<{ web?: { uri: string; title: string } }> } } + +export type WebSearchToolInputSchema = { + anthropic: InferToolInput> + openai: InferToolInput> + google: InferToolInput> + 'openai-chat': InferToolInput> +} diff --git a/src/renderer/src/pages/home/Messages/Tools/MessageKnowledgeSearch.tsx b/src/renderer/src/pages/home/Messages/Tools/MessageKnowledgeSearch.tsx index 72a3f6e36c..19c3a135d7 100644 --- a/src/renderer/src/pages/home/Messages/Tools/MessageKnowledgeSearch.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/MessageKnowledgeSearch.tsx @@ -1,13 +1,13 @@ import { KnowledgeSearchToolInput, KnowledgeSearchToolOutput } from '@renderer/aiCore/tools/KnowledgeSearchTool' import Spinner from '@renderer/components/Spinner' import i18n from '@renderer/i18n' -import { MCPToolResponse } from '@renderer/types' +import { NormalToolResponse } from '@renderer/types' import { Typography } from 'antd' import { FileSearch } from 'lucide-react' import styled from 'styled-components' const { Text } = Typography -export function MessageKnowledgeSearchToolTitle({ toolResponse }: { toolResponse: MCPToolResponse }) { +export function MessageKnowledgeSearchToolTitle({ toolResponse }: { toolResponse: NormalToolResponse }) { const toolInput = toolResponse.arguments as KnowledgeSearchToolInput const toolOutput = toolResponse.response as KnowledgeSearchToolOutput @@ -28,7 +28,7 @@ export function MessageKnowledgeSearchToolTitle({ toolResponse }: { toolResponse ) } -export function MessageKnowledgeSearchToolBody({ toolResponse }: { toolResponse: MCPToolResponse }) { +export function MessageKnowledgeSearchToolBody({ toolResponse }: { toolResponse: NormalToolResponse }) { const toolOutput = toolResponse.response as KnowledgeSearchToolOutput return toolResponse.status === 'done' ? ( diff --git a/src/renderer/src/pages/home/Messages/Tools/MessageMcpTool.tsx b/src/renderer/src/pages/home/Messages/Tools/MessageMcpTool.tsx index be5b21104a..11f29221d6 100644 --- a/src/renderer/src/pages/home/Messages/Tools/MessageMcpTool.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/MessageMcpTool.tsx @@ -4,6 +4,7 @@ import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import { useMCPServers } from '@renderer/hooks/useMCPServers' import { useSettings } from '@renderer/hooks/useSettings' import { useTimer } from '@renderer/hooks/useTimer' +import { MCPToolResponse } from '@renderer/types' import type { ToolMessageBlock } from '@renderer/types/newMessage' import { isToolAutoApproved } from '@renderer/utils/mcp-tools' import { cancelToolAction, confirmToolAction } from '@renderer/utils/userConfirmation' @@ -57,7 +58,7 @@ const MessageMcpTool: FC = ({ block }) => { const [progress, setProgress] = useState(0) const { setTimeoutTimer } = useTimer() - const toolResponse = block.metadata?.rawMcpToolResponse + const toolResponse = block.metadata?.rawMcpToolResponse as MCPToolResponse const { id, tool, status, response } = toolResponse! const isPending = status === 'pending' diff --git a/src/renderer/src/pages/home/Messages/Tools/MessageMemorySearch.tsx b/src/renderer/src/pages/home/Messages/Tools/MessageMemorySearch.tsx index cb86d8a259..2d49144633 100644 --- a/src/renderer/src/pages/home/Messages/Tools/MessageMemorySearch.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/MessageMemorySearch.tsx @@ -1,6 +1,6 @@ import { MemorySearchToolInput, MemorySearchToolOutput } from '@renderer/aiCore/tools/MemorySearchTool' import Spinner from '@renderer/components/Spinner' -import { MCPToolResponse } from '@renderer/types' +import { NormalToolResponse } from '@renderer/types' import { Typography } from 'antd' import { ChevronRight } from 'lucide-react' import { useTranslation } from 'react-i18next' @@ -8,7 +8,7 @@ import styled from 'styled-components' const { Text } = Typography -export const MessageMemorySearchToolTitle = ({ toolResponse }: { toolResponse: MCPToolResponse }) => { +export const MessageMemorySearchToolTitle = ({ toolResponse }: { toolResponse: NormalToolResponse }) => { const { t } = useTranslation() const toolInput = toolResponse.arguments as MemorySearchToolInput const toolOutput = toolResponse.response as MemorySearchToolOutput diff --git a/src/renderer/src/pages/home/Messages/Tools/MessageTool.tsx b/src/renderer/src/pages/home/Messages/Tools/MessageTool.tsx index 38ae73e95e..704fdafd0d 100644 --- a/src/renderer/src/pages/home/Messages/Tools/MessageTool.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/MessageTool.tsx @@ -1,4 +1,4 @@ -import { MCPToolResponse } from '@renderer/types' +import { NormalToolResponse } from '@renderer/types' import type { ToolMessageBlock } from '@renderer/types/newMessage' import { Collapse } from 'antd' @@ -11,8 +11,9 @@ interface Props { } const prefix = 'builtin_' -const ChooseTool = (toolResponse: MCPToolResponse): { label: React.ReactNode; body: React.ReactNode } | null => { +const ChooseTool = (toolResponse: NormalToolResponse): { label: React.ReactNode; body: React.ReactNode } | null => { let toolName = toolResponse.tool.name + const toolType = toolResponse.tool.type if (toolName.startsWith(prefix)) { toolName = toolName.slice(prefix.length) } @@ -20,10 +21,12 @@ const ChooseTool = (toolResponse: MCPToolResponse): { label: React.ReactNode; bo switch (toolName) { case 'web_search': case 'web_search_preview': - return { - label: , - body: null - } + return toolType === 'provider' + ? null + : { + label: , + body: null + } case 'knowledge_search': return { label: , @@ -41,7 +44,7 @@ const ChooseTool = (toolResponse: MCPToolResponse): { label: React.ReactNode; bo export default function MessageTool({ block }: Props) { // FIXME: 语义错误,这里已经不是 MCP tool 了,更改rawMcpToolResponse需要改用户数据, 所以暂时保留 - const toolResponse = block.metadata?.rawMcpToolResponse + const toolResponse = block.metadata?.rawMcpToolResponse as NormalToolResponse if (!toolResponse) return null diff --git a/src/renderer/src/pages/home/Messages/Tools/MessageWebSearch.tsx b/src/renderer/src/pages/home/Messages/Tools/MessageWebSearch.tsx index cd04de3a24..5fe71bbae8 100644 --- a/src/renderer/src/pages/home/Messages/Tools/MessageWebSearch.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/MessageWebSearch.tsx @@ -1,6 +1,6 @@ import { WebSearchToolInput, WebSearchToolOutput } from '@renderer/aiCore/tools/WebSearchTool' import Spinner from '@renderer/components/Spinner' -import { MCPToolResponse } from '@renderer/types' +import { NormalToolResponse } from '@renderer/types' import { Typography } from 'antd' import { Search } from 'lucide-react' import { useTranslation } from 'react-i18next' @@ -8,7 +8,7 @@ import styled from 'styled-components' const { Text } = Typography -export const MessageWebSearchToolTitle = ({ toolResponse }: { toolResponse: MCPToolResponse }) => { +export const MessageWebSearchToolTitle = ({ toolResponse }: { toolResponse: NormalToolResponse }) => { const { t } = useTranslation() const toolInput = toolResponse.arguments as WebSearchToolInput const toolOutput = toolResponse.response as WebSearchToolOutput diff --git a/src/renderer/src/types/newMessage.ts b/src/renderer/src/types/newMessage.ts index 74e2b8266a..7ac6ab5bcb 100644 --- a/src/renderer/src/types/newMessage.ts +++ b/src/renderer/src/types/newMessage.ts @@ -10,6 +10,7 @@ import type { MemoryItem, Metrics, Model, + NormalToolResponse, Topic, Usage, WebSearchResponse, @@ -113,7 +114,7 @@ export interface ToolMessageBlock extends BaseMessageBlock { arguments?: Record content?: string | object metadata?: BaseMessageBlock['metadata'] & { - rawMcpToolResponse?: MCPToolResponse + rawMcpToolResponse?: MCPToolResponse | NormalToolResponse } }