mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-04 11:49:02 +08:00
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.
This commit is contained in:
parent
4975c2d9e8
commit
483b4e090e
@ -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 累加器
|
||||
|
||||
@ -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<ReturnType<typeof anthropic.tools.webSearch_20250305>>
|
||||
|
||||
// OpenAI 工具 - 基于实际输出
|
||||
openaiWebSearch: {
|
||||
// TODO: 上游定义不规范,是unknown
|
||||
// openai: InferToolOutput<ReturnType<typeof openai.tools.webSearch>>
|
||||
openai: {
|
||||
status: 'completed' | 'failed'
|
||||
}
|
||||
'openai-chat': {
|
||||
status: 'completed' | 'failed'
|
||||
}
|
||||
|
||||
// Google 工具
|
||||
googleSearch: {
|
||||
// TODO: 上游定义不规范,是unknown
|
||||
// google: InferToolOutput<ReturnType<typeof google.tools.googleSearch>>
|
||||
google: {
|
||||
webSearchQueries?: string[]
|
||||
groundingChunks?: Array<{
|
||||
web?: { uri: string; title: string }
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export type WebSearchToolInputSchema = {
|
||||
anthropic: InferToolInput<ReturnType<typeof anthropic.tools.webSearch_20250305>>
|
||||
openai: InferToolInput<ReturnType<typeof openai.tools.webSearch>>
|
||||
google: InferToolInput<ReturnType<typeof google.tools.googleSearch>>
|
||||
'openai-chat': InferToolInput<ReturnType<typeof openai.tools.webSearchPreview>>
|
||||
}
|
||||
|
||||
@ -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' ? (
|
||||
|
||||
@ -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<Props> = ({ block }) => {
|
||||
const [progress, setProgress] = useState<number>(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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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: <MessageWebSearchToolTitle toolResponse={toolResponse} />,
|
||||
body: null
|
||||
}
|
||||
return toolType === 'provider'
|
||||
? null
|
||||
: {
|
||||
label: <MessageWebSearchToolTitle toolResponse={toolResponse} />,
|
||||
body: null
|
||||
}
|
||||
case 'knowledge_search':
|
||||
return {
|
||||
label: <MessageKnowledgeSearchToolTitle toolResponse={toolResponse} />,
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -10,6 +10,7 @@ import type {
|
||||
MemoryItem,
|
||||
Metrics,
|
||||
Model,
|
||||
NormalToolResponse,
|
||||
Topic,
|
||||
Usage,
|
||||
WebSearchResponse,
|
||||
@ -113,7 +114,7 @@ export interface ToolMessageBlock extends BaseMessageBlock {
|
||||
arguments?: Record<string, any>
|
||||
content?: string | object
|
||||
metadata?: BaseMessageBlock['metadata'] & {
|
||||
rawMcpToolResponse?: MCPToolResponse
|
||||
rawMcpToolResponse?: MCPToolResponse | NormalToolResponse
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user