mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 23:10:20 +08:00
refactor: streamline system prompt handling and introduce built-in tools (#7714)
* refactor: streamline system prompt handling and introduce built-in tools - Removed the static SYSTEM_PROMPT_THRESHOLD from BaseApiClient and replaced it with a constant in constant.ts. - Updated API clients (AnthropicAPIClient, GeminiAPIClient, OpenAIApiClient, OpenAIResponseAPIClient) to simplify system prompt logic by eliminating unnecessary checks and using new utility functions for prompt building. - Introduced built-in tools functionality, including a new 'think' tool, to enhance the tool usage experience. - Refactored ApiService to integrate built-in tools and adjust system prompt modifications accordingly. - Added utility functions for managing built-in tools in mcp-tools.ts and created a new tools index for better organization. * refactor(tests): update prompt tests to use new buildSystemPromptWithTools function - Renamed the function used in prompt tests from buildSystemPrompt to buildSystemPromptWithTools to reflect recent changes in prompt handling. - Adjusted test cases to ensure compatibility with the updated function, maintaining the integrity of user prompt handling. * refactor(ApiService, mcp-tools, prompt): enhance tool usage and prompt handling - Updated ApiService to improve system prompt construction based on tool usage mode, ensuring clearer logic for tool integration. - Enhanced mcp-tools with a new response structure for the 'think' tool, allowing for better handling of tool responses. - Expanded prompt utility functions to include detailed instructions for using the 'think' tool, improving user guidance. - Refactored tests to validate new prompt building logic and tool integration, ensuring robust functionality across scenarios. * fix: enhance prompt * feat(McpToolChunkMiddleware): enhance tool call handling with built-in tool support - Added support for built-in tools in the parseAndCallTools function, allowing for conditional tool invocation based on tool type. - Implemented a check to return early if the tool call response is null, improving error handling.
This commit is contained in:
parent
82bdbaa0f4
commit
84e78560f4
@ -62,12 +62,10 @@ export abstract class BaseApiClient<
|
|||||||
TSdkSpecificTool extends SdkTool = SdkTool
|
TSdkSpecificTool extends SdkTool = SdkTool
|
||||||
> implements ApiClient<TSdkInstance, TSdkParams, TRawOutput, TRawChunk, TMessageParam, TToolCall, TSdkSpecificTool>
|
> implements ApiClient<TSdkInstance, TSdkParams, TRawOutput, TRawChunk, TMessageParam, TToolCall, TSdkSpecificTool>
|
||||||
{
|
{
|
||||||
private static readonly SYSTEM_PROMPT_THRESHOLD: number = 128
|
|
||||||
public provider: Provider
|
public provider: Provider
|
||||||
protected host: string
|
protected host: string
|
||||||
protected apiKey: string
|
protected apiKey: string
|
||||||
protected sdkInstance?: TSdkInstance
|
protected sdkInstance?: TSdkInstance
|
||||||
public useSystemPromptForTools: boolean = true
|
|
||||||
|
|
||||||
constructor(provider: Provider) {
|
constructor(provider: Provider) {
|
||||||
this.provider = provider
|
this.provider = provider
|
||||||
@ -415,16 +413,9 @@ export abstract class BaseApiClient<
|
|||||||
return { tools }
|
return { tools }
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the number of tools exceeds the threshold, use the system prompt
|
|
||||||
if (mcpTools.length > BaseApiClient.SYSTEM_PROMPT_THRESHOLD) {
|
|
||||||
this.useSystemPromptForTools = true
|
|
||||||
return { tools }
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the model supports function calling and tool usage is enabled
|
// If the model supports function calling and tool usage is enabled
|
||||||
if (isFunctionCallingModel(model) && enableToolUse) {
|
if (isFunctionCallingModel(model) && enableToolUse) {
|
||||||
tools = this.convertMcpToolsToSdkTools(mcpTools)
|
tools = this.convertMcpToolsToSdkTools(mcpTools)
|
||||||
this.useSystemPromptForTools = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { tools }
|
return { tools }
|
||||||
|
|||||||
@ -69,7 +69,6 @@ import {
|
|||||||
mcpToolsToAnthropicTools
|
mcpToolsToAnthropicTools
|
||||||
} from '@renderer/utils/mcp-tools'
|
} from '@renderer/utils/mcp-tools'
|
||||||
import { findFileBlocks, findImageBlocks } from '@renderer/utils/messageUtils/find'
|
import { findFileBlocks, findImageBlocks } from '@renderer/utils/messageUtils/find'
|
||||||
import { buildSystemPrompt } from '@renderer/utils/prompt'
|
|
||||||
|
|
||||||
import { BaseApiClient } from '../BaseApiClient'
|
import { BaseApiClient } from '../BaseApiClient'
|
||||||
import { AnthropicStreamListener, RawStreamListener, RequestTransformer, ResponseChunkTransformer } from '../types'
|
import { AnthropicStreamListener, RawStreamListener, RequestTransformer, ResponseChunkTransformer } from '../types'
|
||||||
@ -450,7 +449,7 @@ export class AnthropicAPIClient extends BaseApiClient<
|
|||||||
}> => {
|
}> => {
|
||||||
const { messages, mcpTools, maxTokens, streamOutput, enableWebSearch } = coreRequest
|
const { messages, mcpTools, maxTokens, streamOutput, enableWebSearch } = coreRequest
|
||||||
// 1. 处理系统消息
|
// 1. 处理系统消息
|
||||||
let systemPrompt = assistant.prompt
|
const systemPrompt = assistant.prompt
|
||||||
|
|
||||||
// 2. 设置工具
|
// 2. 设置工具
|
||||||
const { tools } = this.setupToolsConfig({
|
const { tools } = this.setupToolsConfig({
|
||||||
@ -459,10 +458,6 @@ export class AnthropicAPIClient extends BaseApiClient<
|
|||||||
enableToolUse: isEnabledToolUse(assistant)
|
enableToolUse: isEnabledToolUse(assistant)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.useSystemPromptForTools) {
|
|
||||||
systemPrompt = await buildSystemPrompt(systemPrompt, mcpTools, assistant)
|
|
||||||
}
|
|
||||||
|
|
||||||
const systemMessage: TextBlockParam | undefined = systemPrompt
|
const systemMessage: TextBlockParam | undefined = systemPrompt
|
||||||
? { type: 'text', text: systemPrompt }
|
? { type: 'text', text: systemPrompt }
|
||||||
: undefined
|
: undefined
|
||||||
|
|||||||
@ -59,7 +59,6 @@ import {
|
|||||||
mcpToolsToGeminiTools
|
mcpToolsToGeminiTools
|
||||||
} from '@renderer/utils/mcp-tools'
|
} from '@renderer/utils/mcp-tools'
|
||||||
import { findFileBlocks, findImageBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
import { findFileBlocks, findImageBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||||
import { buildSystemPrompt } from '@renderer/utils/prompt'
|
|
||||||
import { defaultTimeout, MB } from '@shared/config/constant'
|
import { defaultTimeout, MB } from '@shared/config/constant'
|
||||||
|
|
||||||
import { BaseApiClient } from '../BaseApiClient'
|
import { BaseApiClient } from '../BaseApiClient'
|
||||||
@ -448,7 +447,7 @@ export class GeminiAPIClient extends BaseApiClient<
|
|||||||
}> => {
|
}> => {
|
||||||
const { messages, mcpTools, maxTokens, enableWebSearch, enableUrlContext, enableGenerateImage } = coreRequest
|
const { messages, mcpTools, maxTokens, enableWebSearch, enableUrlContext, enableGenerateImage } = coreRequest
|
||||||
// 1. 处理系统消息
|
// 1. 处理系统消息
|
||||||
let systemInstruction = assistant.prompt
|
const systemInstruction = assistant.prompt
|
||||||
|
|
||||||
// 2. 设置工具
|
// 2. 设置工具
|
||||||
const { tools } = this.setupToolsConfig({
|
const { tools } = this.setupToolsConfig({
|
||||||
@ -457,10 +456,6 @@ export class GeminiAPIClient extends BaseApiClient<
|
|||||||
enableToolUse: isEnabledToolUse(assistant)
|
enableToolUse: isEnabledToolUse(assistant)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.useSystemPromptForTools) {
|
|
||||||
systemInstruction = await buildSystemPrompt(assistant.prompt || '', mcpTools, assistant)
|
|
||||||
}
|
|
||||||
|
|
||||||
let messageContents: Content = { role: 'user', parts: [] } // Initialize messageContents
|
let messageContents: Content = { role: 'user', parts: [] } // Initialize messageContents
|
||||||
const history: Content[] = []
|
const history: Content[] = []
|
||||||
// 3. 处理用户消息
|
// 3. 处理用户消息
|
||||||
|
|||||||
@ -52,7 +52,6 @@ import {
|
|||||||
openAIToolsToMcpTool
|
openAIToolsToMcpTool
|
||||||
} from '@renderer/utils/mcp-tools'
|
} from '@renderer/utils/mcp-tools'
|
||||||
import { findFileBlocks, findImageBlocks } from '@renderer/utils/messageUtils/find'
|
import { findFileBlocks, findImageBlocks } from '@renderer/utils/messageUtils/find'
|
||||||
import { buildSystemPrompt } from '@renderer/utils/prompt'
|
|
||||||
import OpenAI, { AzureOpenAI } from 'openai'
|
import OpenAI, { AzureOpenAI } from 'openai'
|
||||||
import { ChatCompletionContentPart, ChatCompletionContentPartRefusal, ChatCompletionTool } from 'openai/resources'
|
import { ChatCompletionContentPart, ChatCompletionContentPartRefusal, ChatCompletionTool } from 'openai/resources'
|
||||||
|
|
||||||
@ -494,10 +493,6 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
enableToolUse: isEnabledToolUse(assistant)
|
enableToolUse: isEnabledToolUse(assistant)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.useSystemPromptForTools) {
|
|
||||||
systemMessage.content = await buildSystemPrompt(systemMessage.content || '', mcpTools, assistant)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 处理用户消息
|
// 3. 处理用户消息
|
||||||
const userMessages: OpenAISdkMessageParam[] = []
|
const userMessages: OpenAISdkMessageParam[] = []
|
||||||
if (typeof messages === 'string') {
|
if (typeof messages === 'string') {
|
||||||
|
|||||||
@ -36,7 +36,6 @@ import {
|
|||||||
openAIToolsToMcpTool
|
openAIToolsToMcpTool
|
||||||
} from '@renderer/utils/mcp-tools'
|
} from '@renderer/utils/mcp-tools'
|
||||||
import { findFileBlocks, findImageBlocks } from '@renderer/utils/messageUtils/find'
|
import { findFileBlocks, findImageBlocks } from '@renderer/utils/messageUtils/find'
|
||||||
import { buildSystemPrompt } from '@renderer/utils/prompt'
|
|
||||||
import { MB } from '@shared/config/constant'
|
import { MB } from '@shared/config/constant'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import OpenAI, { AzureOpenAI } from 'openai'
|
import OpenAI, { AzureOpenAI } from 'openai'
|
||||||
@ -377,9 +376,6 @@ export class OpenAIResponseAPIClient extends OpenAIBaseClient<
|
|||||||
enableToolUse: isEnabledToolUse(assistant)
|
enableToolUse: isEnabledToolUse(assistant)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.useSystemPromptForTools) {
|
|
||||||
systemMessageInput.text = await buildSystemPrompt(systemMessageInput.text || '', mcpTools, assistant)
|
|
||||||
}
|
|
||||||
systemMessageContent.push(systemMessageInput)
|
systemMessageContent.push(systemMessageInput)
|
||||||
systemMessage.content = systemMessageContent
|
systemMessage.content = systemMessageContent
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { MCPCallToolResponse, MCPTool, MCPToolResponse, Model, ToolCallResponse
|
|||||||
import { ChunkType, MCPToolCreatedChunk } from '@renderer/types/chunk'
|
import { ChunkType, MCPToolCreatedChunk } from '@renderer/types/chunk'
|
||||||
import { SdkMessageParam, SdkRawOutput, SdkToolCall } from '@renderer/types/sdk'
|
import { SdkMessageParam, SdkRawOutput, SdkToolCall } from '@renderer/types/sdk'
|
||||||
import {
|
import {
|
||||||
|
callBuiltInTool,
|
||||||
callMCPTool,
|
callMCPTool,
|
||||||
getMcpServerByTool,
|
getMcpServerByTool,
|
||||||
isToolAutoApproved,
|
isToolAutoApproved,
|
||||||
@ -469,7 +470,10 @@ export async function parseAndCallTools<R>(
|
|||||||
// 执行工具调用
|
// 执行工具调用
|
||||||
try {
|
try {
|
||||||
const images: string[] = []
|
const images: string[] = []
|
||||||
const toolCallResponse = await callMCPTool(toolResponse, topicId, model.name)
|
// 根据工具类型选择不同的调用方式
|
||||||
|
const toolCallResponse = toolResponse.tool.isBuiltIn
|
||||||
|
? await callBuiltInTool(toolResponse)
|
||||||
|
: await callMCPTool(toolResponse, topicId, model.name)
|
||||||
|
|
||||||
// 立即更新为done状态
|
// 立即更新为done状态
|
||||||
upsertMCPToolResponse(
|
upsertMCPToolResponse(
|
||||||
@ -482,6 +486,10 @@ export async function parseAndCallTools<R>(
|
|||||||
onChunk!
|
onChunk!
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!toolCallResponse) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 处理图片
|
// 处理图片
|
||||||
for (const content of toolCallResponse.content) {
|
for (const content of toolCallResponse.content) {
|
||||||
if (content.type === 'image' && content.data) {
|
if (content.type === 'image' && content.data) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export const DEFAULT_MAX_TOKENS = 4096
|
|||||||
export const DEFAULT_KNOWLEDGE_DOCUMENT_COUNT = 6
|
export const DEFAULT_KNOWLEDGE_DOCUMENT_COUNT = 6
|
||||||
export const DEFAULT_KNOWLEDGE_THRESHOLD = 0.0
|
export const DEFAULT_KNOWLEDGE_THRESHOLD = 0.0
|
||||||
export const DEFAULT_WEBSEARCH_RAG_DOCUMENT_COUNT = 1
|
export const DEFAULT_WEBSEARCH_RAG_DOCUMENT_COUNT = 1
|
||||||
|
export const SYSTEM_PROMPT_THRESHOLD = 128
|
||||||
|
|
||||||
export const platform = window.electron?.process?.platform
|
export const platform = window.electron?.process?.platform
|
||||||
export const isMac = platform === 'darwin'
|
export const isMac = platform === 'darwin'
|
||||||
|
|||||||
@ -335,7 +335,6 @@
|
|||||||
"provider": "Παρέχων",
|
"provider": "Παρέχων",
|
||||||
"reasoning_content": "Έχει σκεφτεί πολύ καλά",
|
"reasoning_content": "Έχει σκεφτεί πολύ καλά",
|
||||||
"regenerate": "Ξαναπαραγωγή",
|
"regenerate": "Ξαναπαραγωγή",
|
||||||
"trace": "ίχνος",
|
|
||||||
"rename": "Μετονομασία",
|
"rename": "Μετονομασία",
|
||||||
"reset": "Επαναφορά",
|
"reset": "Επαναφορά",
|
||||||
"save": "Αποθήκευση",
|
"save": "Αποθήκευση",
|
||||||
@ -347,6 +346,7 @@
|
|||||||
"pinyin.desc": "Φθίνουσα ταξινόμηση κατά Πινγίν"
|
"pinyin.desc": "Φθίνουσα ταξινόμηση κατά Πινγίν"
|
||||||
},
|
},
|
||||||
"topics": "Θέματα",
|
"topics": "Θέματα",
|
||||||
|
"trace": "ίχνος",
|
||||||
"warning": "Προσοχή",
|
"warning": "Προσοχή",
|
||||||
"you": "Εσείς"
|
"you": "Εσείς"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -336,7 +336,6 @@
|
|||||||
"provider": "Proveedor",
|
"provider": "Proveedor",
|
||||||
"reasoning_content": "Pensamiento profundo",
|
"reasoning_content": "Pensamiento profundo",
|
||||||
"regenerate": "Regenerar",
|
"regenerate": "Regenerar",
|
||||||
"trace": "Rastro",
|
|
||||||
"rename": "Renombrar",
|
"rename": "Renombrar",
|
||||||
"reset": "Restablecer",
|
"reset": "Restablecer",
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
@ -348,6 +347,7 @@
|
|||||||
"pinyin.desc": "Ordenar por pinyin descendente"
|
"pinyin.desc": "Ordenar por pinyin descendente"
|
||||||
},
|
},
|
||||||
"topics": "Temas",
|
"topics": "Temas",
|
||||||
|
"trace": "Rastro",
|
||||||
"warning": "Advertencia",
|
"warning": "Advertencia",
|
||||||
"you": "Usuario"
|
"you": "Usuario"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -335,7 +335,6 @@
|
|||||||
"provider": "Fournisseur",
|
"provider": "Fournisseur",
|
||||||
"reasoning_content": "Réflexion approfondie",
|
"reasoning_content": "Réflexion approfondie",
|
||||||
"regenerate": "Regénérer",
|
"regenerate": "Regénérer",
|
||||||
"trace": "Tracer",
|
|
||||||
"rename": "Renommer",
|
"rename": "Renommer",
|
||||||
"reset": "Réinitialiser",
|
"reset": "Réinitialiser",
|
||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
@ -347,6 +346,7 @@
|
|||||||
"pinyin.desc": "Сортировать по пиньинь в порядке убывания"
|
"pinyin.desc": "Сортировать по пиньинь в порядке убывания"
|
||||||
},
|
},
|
||||||
"topics": "Sujets",
|
"topics": "Sujets",
|
||||||
|
"trace": "Tracer",
|
||||||
"warning": "Avertissement",
|
"warning": "Avertissement",
|
||||||
"you": "Vous"
|
"you": "Vous"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -337,7 +337,6 @@
|
|||||||
"provider": "Fornecedor",
|
"provider": "Fornecedor",
|
||||||
"reasoning_content": "Pensamento profundo concluído",
|
"reasoning_content": "Pensamento profundo concluído",
|
||||||
"regenerate": "Regenerar",
|
"regenerate": "Regenerar",
|
||||||
"trace": "Regenerar",
|
|
||||||
"rename": "Renomear",
|
"rename": "Renomear",
|
||||||
"reset": "Redefinir",
|
"reset": "Redefinir",
|
||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
@ -349,6 +348,7 @@
|
|||||||
"pinyin.desc": "Ordenar por Pinyin em ordem decrescente"
|
"pinyin.desc": "Ordenar por Pinyin em ordem decrescente"
|
||||||
},
|
},
|
||||||
"topics": "Tópicos",
|
"topics": "Tópicos",
|
||||||
|
"trace": "Regenerar",
|
||||||
"warning": "Aviso",
|
"warning": "Aviso",
|
||||||
"you": "Você"
|
"you": "Você"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { CompletionsParams } from '@renderer/aiCore/middleware/schemas'
|
import { CompletionsParams } from '@renderer/aiCore/middleware/schemas'
|
||||||
|
import { SYSTEM_PROMPT_THRESHOLD } from '@renderer/config/constant'
|
||||||
import {
|
import {
|
||||||
isEmbeddingModel,
|
isEmbeddingModel,
|
||||||
isGenerateImageModel,
|
isGenerateImageModel,
|
||||||
@ -39,6 +40,7 @@ import { removeSpecialCharactersForTopicName } from '@renderer/utils'
|
|||||||
import { isAbortError } from '@renderer/utils/error'
|
import { isAbortError } from '@renderer/utils/error'
|
||||||
import { extractInfoFromXML, ExtractResults } from '@renderer/utils/extract'
|
import { extractInfoFromXML, ExtractResults } from '@renderer/utils/extract'
|
||||||
import { findFileBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
import { findFileBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||||
|
import { buildSystemPromptWithThinkTool, buildSystemPromptWithTools } from '@renderer/utils/prompt'
|
||||||
import { findLast, isEmpty, takeRight } from 'lodash'
|
import { findLast, isEmpty, takeRight } from 'lodash'
|
||||||
|
|
||||||
import AiProvider from '../aiCore'
|
import AiProvider from '../aiCore'
|
||||||
@ -345,7 +347,7 @@ async function fetchExternalTool(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get MCP tools (Fix duplicate declaration)
|
// Get MCP tools (Fix duplicate declaration)
|
||||||
let mcpTools: MCPTool[] = [] // Initialize as empty array
|
let mcpTools: MCPTool[] = []
|
||||||
const allMcpServers = store.getState().mcp.servers || []
|
const allMcpServers = store.getState().mcp.servers || []
|
||||||
const activedMcpServers = allMcpServers.filter((s) => s.isActive)
|
const activedMcpServers = allMcpServers.filter((s) => s.isActive)
|
||||||
const assistantMcpServers = assistant.mcpServers || []
|
const assistantMcpServers = assistant.mcpServers || []
|
||||||
@ -369,6 +371,19 @@ async function fetchExternalTool(
|
|||||||
.filter((result): result is PromiseFulfilledResult<MCPTool[]> => result.status === 'fulfilled')
|
.filter((result): result is PromiseFulfilledResult<MCPTool[]> => result.status === 'fulfilled')
|
||||||
.map((result) => result.value)
|
.map((result) => result.value)
|
||||||
.flat()
|
.flat()
|
||||||
|
// 添加内置工具
|
||||||
|
const { BUILT_IN_TOOLS } = await import('../tools')
|
||||||
|
mcpTools.push(...BUILT_IN_TOOLS)
|
||||||
|
|
||||||
|
// 根据toolUseMode决定如何构建系统提示词
|
||||||
|
const basePrompt = assistant.prompt
|
||||||
|
if (assistant.settings?.toolUseMode === 'prompt' || mcpTools.length > SYSTEM_PROMPT_THRESHOLD) {
|
||||||
|
// 提示词模式:需要完整的工具定义和思考指令
|
||||||
|
assistant.prompt = buildSystemPromptWithTools(basePrompt, mcpTools)
|
||||||
|
} else {
|
||||||
|
// 原生函数调用模式:仅需要注入思考指令
|
||||||
|
assistant.prompt = buildSystemPromptWithThinkTool(basePrompt)
|
||||||
|
}
|
||||||
} catch (toolError) {
|
} catch (toolError) {
|
||||||
logger.error('Error fetching MCP tools:', toolError as Error)
|
logger.error('Error fetching MCP tools:', toolError as Error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
createTranslationBlock,
|
createTranslationBlock,
|
||||||
resetAssistantMessage
|
resetAssistantMessage
|
||||||
} from '@renderer/utils/messageUtils/create'
|
} from '@renderer/utils/messageUtils/create'
|
||||||
|
import { buildSystemPrompt } from '@renderer/utils/prompt'
|
||||||
import { getTopicQueue } from '@renderer/utils/queue'
|
import { getTopicQueue } from '@renderer/utils/queue'
|
||||||
import { waitForTopicQueue } from '@renderer/utils/queue'
|
import { waitForTopicQueue } from '@renderer/utils/queue'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
@ -877,6 +878,8 @@ const fetchAndProcessAssistantResponseImpl = async (
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
assistant.prompt = await buildSystemPrompt(assistant.prompt || '', assistant)
|
||||||
|
|
||||||
callbacks = createCallbacks({
|
callbacks = createCallbacks({
|
||||||
blockManager,
|
blockManager,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
|||||||
15
src/renderer/src/tools/index.ts
Normal file
15
src/renderer/src/tools/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { MCPTool } from '@renderer/types'
|
||||||
|
|
||||||
|
import { thinkTool } from './think'
|
||||||
|
|
||||||
|
export const BUILT_IN_TOOLS: MCPTool[] = [thinkTool]
|
||||||
|
|
||||||
|
export function getBuiltInTool(name: string): MCPTool | undefined {
|
||||||
|
return BUILT_IN_TOOLS.find((tool) => tool.name === name || tool.id === name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isBuiltInTool(tool: MCPTool): boolean {
|
||||||
|
return tool.isBuiltIn === true
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from './think'
|
||||||
23
src/renderer/src/tools/think.ts
Normal file
23
src/renderer/src/tools/think.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { MCPTool } from '@renderer/types'
|
||||||
|
|
||||||
|
export const thinkTool: MCPTool = {
|
||||||
|
id: 'dummy-server-think',
|
||||||
|
serverId: 'dummy-server',
|
||||||
|
serverName: 'Dummy Server',
|
||||||
|
name: 'think',
|
||||||
|
description:
|
||||||
|
'Use the tool to think about something. It will not obtain new information or make any changes to the repository, but just log the thought. Use it when complex reasoning or brainstorming is needed. For example, if you explore the repo and discover the source of a bug, call this tool to brainstorm several unique ways of fixing the bug, and assess which change(s) are likely to be simplest and most effective. Alternatively, if you receive some test results, call this tool to brainstorm ways to fix the failing tests.',
|
||||||
|
isBuiltIn: true,
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
title: 'Think Tool Input',
|
||||||
|
description: 'Input for the think tool',
|
||||||
|
required: ['thought'],
|
||||||
|
properties: {
|
||||||
|
thought: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Your thoughts.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -672,6 +672,7 @@ export interface MCPTool {
|
|||||||
description?: string
|
description?: string
|
||||||
inputSchema: MCPToolInputSchema
|
inputSchema: MCPToolInputSchema
|
||||||
outputSchema?: z.infer<typeof MCPToolOutputSchema>
|
outputSchema?: z.infer<typeof MCPToolOutputSchema>
|
||||||
|
isBuiltIn?: boolean // 标识是否为内置工具,内置工具不需要通过MCP协议调用
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MCPPromptArguments {
|
export interface MCPPromptArguments {
|
||||||
|
|||||||
@ -1,67 +1,260 @@
|
|||||||
import { type MCPTool } from '@renderer/types'
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { type Assistant, type MCPTool, type Model } from '@renderer/types'
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { AvailableTools, buildSystemPrompt } from '../prompt'
|
import {
|
||||||
|
AvailableTools,
|
||||||
|
buildSystemPrompt,
|
||||||
|
buildSystemPromptWithThinkTool,
|
||||||
|
buildSystemPromptWithTools,
|
||||||
|
SYSTEM_PROMPT,
|
||||||
|
THINK_TOOL_PROMPT,
|
||||||
|
ToolUseExamples
|
||||||
|
} from '../prompt'
|
||||||
|
|
||||||
|
// Mock window.api
|
||||||
|
const mockApi = {
|
||||||
|
system: {
|
||||||
|
getDeviceType: vi.fn()
|
||||||
|
},
|
||||||
|
getAppInfo: vi.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mock('@renderer/store', () => {
|
||||||
|
const mockStore = configureStore({
|
||||||
|
reducer: {
|
||||||
|
settings: (
|
||||||
|
state = {
|
||||||
|
language: 'zh-CN',
|
||||||
|
userName: 'MockUser'
|
||||||
|
}
|
||||||
|
) => state
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
default: mockStore,
|
||||||
|
__esModule: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Helper to create a mock MCPTool
|
||||||
|
const createMockTool = (id: string, description: string, inputSchema: any = {}): MCPTool => ({
|
||||||
|
id,
|
||||||
|
serverId: 'test-server',
|
||||||
|
serverName: 'Test Server',
|
||||||
|
name: id,
|
||||||
|
description,
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
title: `${id}-schema`,
|
||||||
|
properties: {},
|
||||||
|
...inputSchema
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Helper to create a mock Assistant
|
||||||
|
const createMockAssistant = (name: string, modelName: string): Assistant => ({
|
||||||
|
id: 'asst_mock_123',
|
||||||
|
name,
|
||||||
|
prompt: 'You are a helpful assistant.',
|
||||||
|
topics: [],
|
||||||
|
type: 'assistant',
|
||||||
|
model: {
|
||||||
|
id: modelName,
|
||||||
|
name: modelName,
|
||||||
|
provider: 'mock'
|
||||||
|
} as unknown as Model
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置全局 mocks
|
||||||
|
Object.defineProperty(window, 'api', {
|
||||||
|
value: mockApi,
|
||||||
|
writable: true
|
||||||
|
})
|
||||||
|
|
||||||
describe('prompt', () => {
|
describe('prompt', () => {
|
||||||
describe('AvailableTools', () => {
|
const mockDate = new Date('2024-01-01T12:00:00Z')
|
||||||
it('should generate XML format for tools', () => {
|
|
||||||
const tools = [
|
|
||||||
{ id: 'test-tool', description: 'Test tool description', inputSchema: { type: 'object' } } as MCPTool
|
|
||||||
]
|
|
||||||
const result = AvailableTools(tools)
|
|
||||||
|
|
||||||
expect(result).toContain('<tools>')
|
beforeEach(() => {
|
||||||
expect(result).toContain('</tools>')
|
// 重置所有 mocks
|
||||||
expect(result).toContain('<tool>')
|
vi.clearAllMocks()
|
||||||
expect(result).toContain('test-tool')
|
vi.useFakeTimers()
|
||||||
expect(result).toContain('Test tool description')
|
vi.setSystemTime(mockDate)
|
||||||
expect(result).toContain('{"type":"object"}')
|
|
||||||
|
// 设置默认的 mock 返回值
|
||||||
|
mockApi.system.getDeviceType.mockResolvedValue('macOS')
|
||||||
|
mockApi.getAppInfo.mockResolvedValue({ arch: 'darwin64' })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers()
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('AvailableTools', () => {
|
||||||
|
it('should generate XML format for tools with strict equality', () => {
|
||||||
|
const tools = [createMockTool('test-tool', 'Test tool description')]
|
||||||
|
const result = AvailableTools(tools)
|
||||||
|
const expectedXml = `<tools>
|
||||||
|
|
||||||
|
<tool>
|
||||||
|
<name>test-tool</name>
|
||||||
|
<description>Test tool description</description>
|
||||||
|
<arguments>
|
||||||
|
{"type":"object","title":"test-tool-schema","properties":{}}
|
||||||
|
</arguments>
|
||||||
|
</tool>
|
||||||
|
|
||||||
|
</tools>`
|
||||||
|
expect(result).toEqual(expectedXml)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle empty tools array', () => {
|
it('should handle empty tools array and return just the container tags', () => {
|
||||||
const result = AvailableTools([])
|
const result = AvailableTools([])
|
||||||
|
const expectedXml = `<tools>
|
||||||
|
|
||||||
expect(result).toContain('<tools>')
|
</tools>`
|
||||||
expect(result).toContain('</tools>')
|
expect(result).toEqual(expectedXml)
|
||||||
expect(result).not.toContain('<tool>')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('buildSystemPrompt', () => {
|
describe('buildSystemPrompt', () => {
|
||||||
it('should build prompt with tools', async () => {
|
it('should replace all variables correctly with strict equality', async () => {
|
||||||
const userPrompt = 'Custom user system prompt'
|
const userPrompt = `
|
||||||
const tools = [
|
以下是一些辅助信息:
|
||||||
{ id: 'test-tool', description: 'Test tool description', inputSchema: { type: 'object' } } as MCPTool
|
- 日期和时间: {{datetime}};
|
||||||
]
|
- 操作系统: {{system}};
|
||||||
const result = await buildSystemPrompt(userPrompt, tools)
|
- 中央处理器架构: {{arch}};
|
||||||
|
- 语言: {{language}};
|
||||||
expect(result).toContain(userPrompt)
|
- 模型名称: {{model_name}};
|
||||||
expect(result).toContain('test-tool')
|
- 用户名称: {{username}};
|
||||||
expect(result).toContain('Test tool description')
|
`
|
||||||
|
const assistant = createMockAssistant('MyAssistant', 'Super-Model-X')
|
||||||
|
const result = await buildSystemPrompt(userPrompt, assistant)
|
||||||
|
const expectedPrompt = `
|
||||||
|
以下是一些辅助信息:
|
||||||
|
- 日期和时间: ${mockDate.toLocaleString()};
|
||||||
|
- 操作系统: macOS;
|
||||||
|
- 中央处理器架构: darwin64;
|
||||||
|
- 语言: zh-CN;
|
||||||
|
- 模型名称: Super-Model-X;
|
||||||
|
- 用户名称: MockUser;
|
||||||
|
`
|
||||||
|
expect(result).toEqual(expectedPrompt)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return user prompt without tools', async () => {
|
it('should handle API errors gracefully and use fallback values', async () => {
|
||||||
const userPrompt = 'Custom user system prompt'
|
mockApi.system.getDeviceType.mockRejectedValue(new Error('API Error'))
|
||||||
const result = await buildSystemPrompt(userPrompt, [])
|
mockApi.getAppInfo.mockRejectedValue(new Error('API Error'))
|
||||||
|
|
||||||
expect(result).toBe(userPrompt)
|
const userPrompt = 'System: {{system}}, Architecture: {{arch}}'
|
||||||
|
const result = await buildSystemPrompt(userPrompt)
|
||||||
|
const expectedPrompt = 'System: Unknown System, Architecture: Unknown Architecture'
|
||||||
|
expect(result).toEqual(expectedPrompt)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle null or undefined user prompt', async () => {
|
it('should handle non-string input gracefully', async () => {
|
||||||
const tools = [
|
const result = await buildSystemPrompt(null as any)
|
||||||
{ id: 'test-tool', description: 'Test tool description', inputSchema: { type: 'object' } } as MCPTool
|
expect(result).toBe(null)
|
||||||
]
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// 测试 userPrompt 为 null 的情况
|
describe('Tool prompt composition', () => {
|
||||||
const resultNull = buildSystemPrompt(null as any, tools)
|
let basePrompt: string
|
||||||
expect(resultNull).toBeDefined()
|
let expectedBasePrompt: string
|
||||||
expect(resultNull).not.toContain('{{ USER_SYSTEM_PROMPT }}')
|
let tools: MCPTool[]
|
||||||
|
|
||||||
// 测试 userPrompt 为 undefined 的情况
|
beforeEach(async () => {
|
||||||
const resultUndefined = buildSystemPrompt(undefined as any, tools)
|
const initialPrompt = `
|
||||||
expect(resultUndefined).toBeDefined()
|
System Information:
|
||||||
expect(resultUndefined).not.toContain('{{ USER_SYSTEM_PROMPT }}')
|
- Date: {{date}}
|
||||||
|
- User: {{username}}
|
||||||
|
|
||||||
|
Instructions: Be helpful.
|
||||||
|
`
|
||||||
|
const assistant = createMockAssistant('Test Assistant', 'Advanced-AI-Model')
|
||||||
|
basePrompt = await buildSystemPrompt(initialPrompt, assistant)
|
||||||
|
expectedBasePrompt = `
|
||||||
|
System Information:
|
||||||
|
- Date: ${mockDate.toLocaleDateString()}
|
||||||
|
- User: MockUser
|
||||||
|
|
||||||
|
Instructions: Be helpful.
|
||||||
|
`
|
||||||
|
tools = [createMockTool('web_search', 'Search the web')]
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should build a full prompt for "prompt" toolUseMode', () => {
|
||||||
|
const finalPrompt = buildSystemPromptWithTools(basePrompt, tools)
|
||||||
|
const expectedFinalPrompt = SYSTEM_PROMPT.replace('{{ USER_SYSTEM_PROMPT }}', expectedBasePrompt)
|
||||||
|
.replace('{{ TOOL_USE_EXAMPLES }}', ToolUseExamples)
|
||||||
|
.replace('{{ AVAILABLE_TOOLS }}', AvailableTools(tools))
|
||||||
|
|
||||||
|
expect(finalPrompt).toEqual(expectedFinalPrompt)
|
||||||
|
expect(finalPrompt).toContain('## Tool Use Formatting')
|
||||||
|
expect(finalPrompt).toContain('## Using the think tool')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should build a think-only prompt for native function calling mode', () => {
|
||||||
|
const finalPrompt = buildSystemPromptWithThinkTool(basePrompt)
|
||||||
|
const expectedFinalPrompt = THINK_TOOL_PROMPT.replace('{{ USER_SYSTEM_PROMPT }}', expectedBasePrompt)
|
||||||
|
|
||||||
|
expect(finalPrompt).toEqual(expectedFinalPrompt)
|
||||||
|
expect(finalPrompt).not.toContain('## Tool Use Formatting')
|
||||||
|
expect(finalPrompt).toContain('## Using the think tool')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the original prompt if no tools are provided to buildSystemPromptWithTools', () => {
|
||||||
|
const result = buildSystemPromptWithTools(basePrompt, [])
|
||||||
|
expect(result).toBe(basePrompt)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('buildSystemPromptWithTools', () => {
|
||||||
|
it('should build a full prompt for "prompt" toolUseMode', async () => {
|
||||||
|
const assistant = createMockAssistant('Test Assistant', 'Advanced-AI-Model')
|
||||||
|
const basePrompt = await buildSystemPrompt('Be helpful.', assistant)
|
||||||
|
const tools = [createMockTool('web_search', 'Search the web')]
|
||||||
|
|
||||||
|
const finalPrompt = buildSystemPromptWithTools(basePrompt, tools)
|
||||||
|
const expectedFinalPrompt = SYSTEM_PROMPT.replace('{{ USER_SYSTEM_PROMPT }}', basePrompt)
|
||||||
|
.replace('{{ TOOL_USE_EXAMPLES }}', ToolUseExamples)
|
||||||
|
.replace('{{ AVAILABLE_TOOLS }}', AvailableTools(tools))
|
||||||
|
|
||||||
|
expect(finalPrompt).toEqual(expectedFinalPrompt)
|
||||||
|
expect(finalPrompt).toContain('## Tool Use Formatting')
|
||||||
|
expect(finalPrompt).toContain('## Using the think tool')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('buildSystemPromptWithThinkTool', () => {
|
||||||
|
it('should combine a template prompt with think tool instructions for native function calling', async () => {
|
||||||
|
// 1. 创建一个带变量的模板提示词,并处理它
|
||||||
|
const initialPrompt = `
|
||||||
|
System Information:
|
||||||
|
- Date: {{date}}
|
||||||
|
- User: {{username}}
|
||||||
|
|
||||||
|
Instructions: Be helpful.
|
||||||
|
`
|
||||||
|
const assistant = createMockAssistant('Test Assistant', 'Advanced-AI-Model')
|
||||||
|
const basePrompt = await buildSystemPrompt(initialPrompt, assistant)
|
||||||
|
const expectedBasePrompt = `
|
||||||
|
System Information:
|
||||||
|
- Date: ${mockDate.toLocaleDateString()}
|
||||||
|
- User: MockUser
|
||||||
|
|
||||||
|
Instructions: Be helpful.
|
||||||
|
`
|
||||||
|
|
||||||
|
// 2. 将处理过的提示词与思考工具结合
|
||||||
|
const finalPrompt = buildSystemPromptWithThinkTool(basePrompt)
|
||||||
|
const expectedFinalPrompt = THINK_TOOL_PROMPT.replace('{{ USER_SYSTEM_PROMPT }}', expectedBasePrompt)
|
||||||
|
|
||||||
|
// 3. 验证结果
|
||||||
|
expect(finalPrompt).toEqual(expectedFinalPrompt)
|
||||||
|
expect(finalPrompt).not.toContain('## Tool Use Formatting') // 验证不包含工具定义
|
||||||
|
expect(finalPrompt).toContain('## Using the think tool') // 验证包含思考指令
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -266,6 +266,25 @@ export function openAIToolsToMcpTool(
|
|||||||
return tool
|
return tool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function callBuiltInTool(toolResponse: MCPToolResponse): Promise<MCPCallToolResponse | undefined> {
|
||||||
|
logger.info(`[BuiltIn] Calling Built-in Tool: ${toolResponse.tool.name}`, toolResponse.tool)
|
||||||
|
|
||||||
|
if (toolResponse.tool.name === 'think') {
|
||||||
|
const thought = toolResponse.arguments?.thought
|
||||||
|
return {
|
||||||
|
isError: false,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: (thought as string) || ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
export async function callMCPTool(
|
export async function callMCPTool(
|
||||||
toolResponse: MCPToolResponse,
|
toolResponse: MCPToolResponse,
|
||||||
topicId?: string,
|
topicId?: string,
|
||||||
|
|||||||
@ -7,6 +7,47 @@ const logger = loggerService.withContext('Utils:Prompt')
|
|||||||
export const SYSTEM_PROMPT = `In this environment you have access to a set of tools you can use to answer the user's question. \
|
export const SYSTEM_PROMPT = `In this environment you have access to a set of tools you can use to answer the user's question. \
|
||||||
You can use one or more tools per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
You can use one or more tools per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||||
|
|
||||||
|
## Using the think tool
|
||||||
|
|
||||||
|
Before taking any action or responding to the user after receiving tool results, use the think tool as a scratchpad to:
|
||||||
|
- List the specific rules that apply to the current request
|
||||||
|
- Check if all required information is collected
|
||||||
|
- Verify that the planned action complies with all policies
|
||||||
|
- Iterate over tool results for correctness
|
||||||
|
|
||||||
|
Here are some examples of what to iterate over inside the think tool:
|
||||||
|
<think_tool_example_1>
|
||||||
|
User wants to cancel flight ABC123
|
||||||
|
- Need to verify: user ID, reservation ID, reason
|
||||||
|
- Check cancellation rules:
|
||||||
|
* Is it within 24h of booking?
|
||||||
|
* If not, check ticket class and insurance
|
||||||
|
- Verify no segments flown or are in the past
|
||||||
|
- Plan: collect missing info, verify rules, get confirmation
|
||||||
|
</think_tool_example_1>
|
||||||
|
|
||||||
|
<think_tool_example_2>
|
||||||
|
User wants to book 3 tickets to NYC with 2 checked bags each
|
||||||
|
- Need user ID to check:
|
||||||
|
* Membership tier for baggage allowance
|
||||||
|
* Which payments methods exist in profile
|
||||||
|
- Baggage calculation:
|
||||||
|
* Economy class × 3 passengers
|
||||||
|
* If regular member: 1 free bag each → 3 extra bags = $150
|
||||||
|
* If silver member: 2 free bags each → 0 extra bags = $0
|
||||||
|
* If gold member: 3 free bags each → 0 extra bags = $0
|
||||||
|
- Payment rules to verify:
|
||||||
|
* Max 1 travel certificate, 1 credit card, 3 gift cards
|
||||||
|
* All payment methods must be in profile
|
||||||
|
* Travel certificate remainder goes to waste
|
||||||
|
- Plan:
|
||||||
|
1. Get user ID
|
||||||
|
2. Verify membership level for bag fees
|
||||||
|
3. Check which payment methods in profile and if their combination is allowed
|
||||||
|
4. Calculate total: ticket price + any bag fees
|
||||||
|
5. Get explicit confirmation for booking
|
||||||
|
</think_tool_example_2>
|
||||||
|
|
||||||
## Tool Use Formatting
|
## Tool Use Formatting
|
||||||
|
|
||||||
Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure:
|
Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure:
|
||||||
@ -56,10 +97,55 @@ Here are the rules you should always follow to solve your task:
|
|||||||
|
|
||||||
# User Instructions
|
# User Instructions
|
||||||
{{ USER_SYSTEM_PROMPT }}
|
{{ USER_SYSTEM_PROMPT }}
|
||||||
|
Response in user query language.
|
||||||
Now Begin! If you solve the task correctly, you will receive a reward of $1,000,000.
|
Now Begin! If you solve the task correctly, you will receive a reward of $1,000,000.
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const THINK_TOOL_PROMPT = `{{ USER_SYSTEM_PROMPT }}
|
||||||
|
|
||||||
|
## Using the think tool
|
||||||
|
|
||||||
|
Before taking any action or responding to the user after receiving tool results, use the think tool as a scratchpad to:
|
||||||
|
- List the specific rules that apply to the current request
|
||||||
|
- Check if all required information is collected
|
||||||
|
- Verify that the planned action complies with all policies
|
||||||
|
- Iterate over tool results for correctness
|
||||||
|
- Response in user query language
|
||||||
|
|
||||||
|
Here are some examples of what to iterate over inside the think tool:
|
||||||
|
<think_tool_example_1>
|
||||||
|
User wants to cancel flight ABC123
|
||||||
|
- Need to verify: user ID, reservation ID, reason
|
||||||
|
- Check cancellation rules:
|
||||||
|
* Is it within 24h of booking?
|
||||||
|
* If not, check ticket class and insurance
|
||||||
|
- Verify no segments flown or are in the past
|
||||||
|
- Plan: collect missing info, verify rules, get confirmation
|
||||||
|
</think_tool_example_1>
|
||||||
|
|
||||||
|
<think_tool_example_2>
|
||||||
|
User wants to book 3 tickets to NYC with 2 checked bags each
|
||||||
|
- Need user ID to check:
|
||||||
|
* Membership tier for baggage allowance
|
||||||
|
* Which payments methods exist in profile
|
||||||
|
- Baggage calculation:
|
||||||
|
* Economy class × 3 passengers
|
||||||
|
* If regular member: 1 free bag each → 3 extra bags = $150
|
||||||
|
* If silver member: 2 free bags each → 0 extra bags = $0
|
||||||
|
* If gold member: 3 free bags each → 0 extra bags = $0
|
||||||
|
- Payment rules to verify:
|
||||||
|
* Max 1 travel certificate, 1 credit card, 3 gift cards
|
||||||
|
* All payment methods must be in profile
|
||||||
|
* Travel certificate remainder goes to waste
|
||||||
|
- Plan:
|
||||||
|
1. Get user ID
|
||||||
|
2. Verify membership level for bag fees
|
||||||
|
3. Check which payment methods in profile and if their combination is allowed
|
||||||
|
4. Calculate total: ticket price + any bag fees
|
||||||
|
5. Get explicit confirmation for booking
|
||||||
|
</think_tool_example_2>
|
||||||
|
`
|
||||||
|
|
||||||
export const ToolUseExamples = `
|
export const ToolUseExamples = `
|
||||||
Here are a few examples using notional tools:
|
Here are a few examples using notional tools:
|
||||||
---
|
---
|
||||||
@ -151,11 +237,7 @@ ${availableTools}
|
|||||||
</tools>`
|
</tools>`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildSystemPrompt = async (
|
export const buildSystemPrompt = async (userSystemPrompt: string, assistant?: Assistant): Promise<string> => {
|
||||||
userSystemPrompt: string,
|
|
||||||
tools?: MCPTool[],
|
|
||||||
assistant?: Assistant
|
|
||||||
): Promise<string> => {
|
|
||||||
if (typeof userSystemPrompt === 'string') {
|
if (typeof userSystemPrompt === 'string') {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
if (userSystemPrompt.includes('{{date}}')) {
|
if (userSystemPrompt.includes('{{date}}')) {
|
||||||
@ -223,11 +305,18 @@ export const buildSystemPrompt = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return userSystemPrompt
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildSystemPromptWithTools = (userSystemPrompt: string, tools?: MCPTool[]): string => {
|
||||||
if (tools && tools.length > 0) {
|
if (tools && tools.length > 0) {
|
||||||
return SYSTEM_PROMPT.replace('{{ USER_SYSTEM_PROMPT }}', userSystemPrompt)
|
return SYSTEM_PROMPT.replace('{{ USER_SYSTEM_PROMPT }}', userSystemPrompt || '')
|
||||||
.replace('{{ TOOL_USE_EXAMPLES }}', ToolUseExamples)
|
.replace('{{ TOOL_USE_EXAMPLES }}', ToolUseExamples)
|
||||||
.replace('{{ AVAILABLE_TOOLS }}', AvailableTools(tools))
|
.replace('{{ AVAILABLE_TOOLS }}', AvailableTools(tools))
|
||||||
}
|
}
|
||||||
|
|
||||||
return userSystemPrompt
|
return userSystemPrompt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const buildSystemPromptWithThinkTool = (userSystemPrompt: string): string => {
|
||||||
|
return THINK_TOOL_PROMPT.replace('{{ USER_SYSTEM_PROMPT }}', userSystemPrompt || '')
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html,
|
||||||
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -26,7 +27,7 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1
|
z-index: 1;
|
||||||
}
|
}
|
||||||
footer p {
|
footer p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -37,4 +38,4 @@
|
|||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/trace/traceWindow.tsx"></script>
|
<script type="module" src="/src/trace/traceWindow.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user