From 04821674764276e3d8ac7bb4fe7d2fda0f35fd41 Mon Sep 17 00:00:00 2001 From: Vaayne Date: Wed, 31 Dec 2025 11:51:25 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20move=20hub=20p?= =?UTF-8?q?rompts=20to=20dedicated=20prompts-code-mode.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create src/renderer/src/config/prompts-code-mode.ts - Move HUB_MODE_SYSTEM_PROMPT_BASE and AUTO_MODE_SYSTEM_PROMPT_BASE - Move getHubModeSystemPrompt() and getAutoModeSystemPrompt() - Extract shared buildToolsSection() helper - Update parameterBuilder.ts import --- .../aiCore/prepareParams/parameterBuilder.ts | 2 +- src/renderer/src/config/prompts-code-mode.ts | 266 ++++++++++++++++++ src/renderer/src/config/prompts.ts | 256 ----------------- 3 files changed, 267 insertions(+), 257 deletions(-) create mode 100644 src/renderer/src/config/prompts-code-mode.ts diff --git a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts index 21b07af811..0e33689d2d 100644 --- a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts +++ b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts @@ -26,7 +26,7 @@ import { isSupportedThinkingTokenModel, isWebSearchModel } from '@renderer/config/models' -import { getAutoModeSystemPrompt } from '@renderer/config/prompts' +import { getAutoModeSystemPrompt } from '@renderer/config/prompts-code-mode' import { fetchAllActiveServerTools } from '@renderer/services/ApiService' import { getDefaultModel } from '@renderer/services/AssistantService' import store from '@renderer/store' diff --git a/src/renderer/src/config/prompts-code-mode.ts b/src/renderer/src/config/prompts-code-mode.ts new file mode 100644 index 0000000000..37541a314f --- /dev/null +++ b/src/renderer/src/config/prompts-code-mode.ts @@ -0,0 +1,266 @@ +import { generateMcpToolFunctionName } from '@shared/mcp' + +export interface ToolInfo { + name: string + serverName?: string + description?: string +} + +/** + * Hub Mode System Prompt - For native MCP tool calling + * Used when model supports native function calling via MCP protocol + */ +const HUB_MODE_SYSTEM_PROMPT_BASE = ` +## Hub MCP Tools – Code Execution Mode + +You can discover and call MCP tools through the hub server using two meta-tools: **search** and **exec**. + +### Critical Rules (Read First) + +1. You MUST explicitly \`return\` the final value from your \`exec\` code. If you do not return a value, the result will be \`undefined\`. +2. All MCP tools are async functions. Always call them as \`await ToolName(params)\`. +3. Use the exact function names and parameter shapes returned by \`search\`. +4. You CANNOT call \`search\` or \`exec\` from inside \`exec\` code—use them only as MCP tools. +5. \`console.log\` output is NOT the result. Logs are separate; the final answer must come from \`return\`. + +### Workflow + +1. Call \`search\` with relevant keywords to discover tools. +2. Read the returned JavaScript function declarations and JSDoc to understand names and parameters. +3. Call \`exec\` with JavaScript code that uses the discovered tools and ends with an explicit \`return\`. +4. Use the \`exec\` result as your answer. + +### What \`search\` Does + +- Input: keyword string (comma-separated for OR-matching), plus optional \`limit\`. +- Output: JavaScript async function declarations with JSDoc showing exact function names, parameters, and return types. + +### What \`exec\` Does + +- Runs JavaScript code in an isolated async context (wrapped as \`(async () => { your code })())\`. +- All discovered tools are exposed as async functions: \`await ToolName(params)\`. +- Available helpers: + - \`parallel(...promises)\` → \`Promise.all(promises)\` + - \`settle(...promises)\` → \`Promise.allSettled(promises)\` + - \`console.log/info/warn/error/debug\` +- Returns JSON with: \`result\` (your returned value), \`logs\` (optional), \`error\` (optional), \`isError\` (optional). + +### Example: Single Tool Call + +\`\`\`javascript +// Step 1: search({ query: "browser,fetch" }) +// Step 2: exec with: +const page = await CherryBrowser_fetch({ url: "https://example.com" }) +return page +\`\`\` + +### Example: Multiple Tools with Parallel + +\`\`\`javascript +const [forecast, time] = await parallel( + Weather_getForecast({ city: "Paris" }), + Time_getLocalTime({ city: "Paris" }) +) +return { city: "Paris", forecast, time } +\`\`\` + +### Example: Handle Partial Failures with Settle + +\`\`\`javascript +const results = await settle( + Weather_getForecast({ city: "Paris" }), + Weather_getForecast({ city: "Tokyo" }) +) +const successful = results.filter(r => r.status === "fulfilled").map(r => r.value) +return { results, successful } +\`\`\` + +### Example: Error Handling + +\`\`\`javascript +try { + const user = await User_lookup({ email: "user@example.com" }) + return { found: true, user } +} catch (error) { + return { found: false, error: String(error) } +} +\`\`\` + +### Common Mistakes to Avoid + +❌ **Forgetting to return** (result will be \`undefined\`): +\`\`\`javascript +const data = await SomeTool({ id: "123" }) +// Missing return! +\`\`\` + +✅ **Always return**: +\`\`\`javascript +const data = await SomeTool({ id: "123" }) +return data +\`\`\` + +❌ **Only logging, not returning**: +\`\`\`javascript +const data = await SomeTool({ id: "123" }) +console.log(data) // Logs are NOT the result! +\`\`\` + +❌ **Missing await**: +\`\`\`javascript +const data = SomeTool({ id: "123" }) // Returns Promise, not value! +return data +\`\`\` + +❌ **Awaiting before parallel**: +\`\`\`javascript +await parallel(await ToolA(), await ToolB()) // Wrong: runs sequentially +\`\`\` + +✅ **Pass promises directly to parallel**: +\`\`\`javascript +await parallel(ToolA(), ToolB()) // Correct: runs in parallel +\`\`\` + +### Best Practices + +- Always call \`search\` first to discover tools and confirm signatures. +- Always use an explicit \`return\` at the end of \`exec\` code. +- Use \`parallel\` for independent operations that can run at the same time. +- Use \`settle\` when some calls may fail but you still want partial results. +- Prefer a single \`exec\` call for multi-step flows. +- Treat \`console.*\` as debugging only, never as the primary result. +` + +/** + * Auto Mode System Prompt - For XML tool_use format + * Used when model needs explicit XML format to invoke tools + * Only teaches search and exec tools + */ +const AUTO_MODE_SYSTEM_PROMPT_BASE = ` +You can discover and invoke MCP tools through a hub using TWO meta-tools: \`search\` and \`exec\`. + +## Tool Invocation Format + +When you want to call a tool, output exactly one XML block: + + + {tool_name} + {json_arguments} + + +Rules: +- \`{tool_name}\` MUST be either \`search\` or \`exec\` +- \`\` MUST contain valid JSON (no comments, no trailing commas) +- Do NOT include extra text before or after the \`\` block + +## Available Tools + +1. **search** - Discover MCP tools by keyword + \`\`\`json + { "query": "keyword1,keyword2", "limit": 10 } + \`\`\` + Returns JavaScript function declarations with JSDoc showing names, parameters, and return types. + +2. **exec** - Execute JavaScript that calls discovered tools + \`\`\`json + { "code": "const r = await ToolName({...}); return r;" } + \`\`\` + **CRITICAL:** You MUST \`return\` the final value, or result will be \`undefined\`. + +## Workflow + +1. Call \`search\` with keywords to discover tools +2. Read the returned function signatures carefully +3. Call \`exec\` with JavaScript code that: + - Uses ONLY functions returned by \`search\` + - Calls them with \`await\` + - Ends with explicit \`return\` +4. Answer the user based on the result + +## Example + +User: "Calculate 15 * 7" + +Assistant calls search: + + search + {"query": "python,calculator"} + + +Hub returns function signature: +\`\`\`js +async function CherryPython_pythonExecute(params: { code: string }): Promise +\`\`\` + +Assistant calls exec: + + exec + {"code": "const result = await CherryPython_pythonExecute({ code: '15 * 7' }); return result;"} + + +Hub returns: { "result": 105 } + +Assistant answers: "15 × 7 = 105" + +## Common Mistakes + +❌ Forgetting to return (result will be undefined): +\`\`\`js +await SomeTool({ id: "123" }) +\`\`\` + +✅ Always return: +\`\`\`js +const data = await SomeTool({ id: "123" }); return data; +\`\`\` + +❌ Calling exec before search - you must discover tools first + +❌ Using functions not returned by search +` + +function buildToolsSection(tools: ToolInfo[]): string { + const existingNames = new Set() + return tools + .map((t) => { + const functionName = generateMcpToolFunctionName(t.serverName, t.name, existingNames) + const desc = t.description || '' + const normalizedDesc = desc.replace(/\s+/g, ' ').trim() + const truncatedDesc = normalizedDesc.length > 50 ? `${normalizedDesc.slice(0, 50)}...` : normalizedDesc + return `- ${functionName}: ${truncatedDesc}` + }) + .join('\n') +} + +/** + * Get system prompt for Hub Mode (native MCP tool calling) + */ +export function getHubModeSystemPrompt(tools: ToolInfo[] = []): string { + if (tools.length === 0) { + return '' + } + + const toolsSection = buildToolsSection(tools) + + return `${HUB_MODE_SYSTEM_PROMPT_BASE} +### Available Tools +${toolsSection} +` +} + +/** + * Get system prompt for Auto Mode (XML tool_use format) + */ +export function getAutoModeSystemPrompt(tools: ToolInfo[] = []): string { + if (tools.length === 0) { + return '' + } + + const toolsSection = buildToolsSection(tools) + + return `${AUTO_MODE_SYSTEM_PROMPT_BASE} +## Discoverable Tools (use search to get full signatures) +${toolsSection} +` +} diff --git a/src/renderer/src/config/prompts.ts b/src/renderer/src/config/prompts.ts index db0128633d..815eb7d113 100644 --- a/src/renderer/src/config/prompts.ts +++ b/src/renderer/src/config/prompts.ts @@ -1,4 +1,3 @@ -import { generateMcpToolFunctionName } from '@shared/mcp' import dayjs from 'dayjs' export const AGENT_PROMPT = ` @@ -469,258 +468,3 @@ Example: [nytimes.com](https://nytimes.com/some-page). If have multiple citations, please directly list them like this: [www.nytimes.com](https://nytimes.com/some-page)[www.bbc.com](https://bbc.com/some-page) ` - -const HUB_MODE_SYSTEM_PROMPT_BASE = ` -## Hub MCP Tools – Code Execution Mode - -You can discover and call MCP tools through the hub server using two meta-tools: **search** and **exec**. - -### Critical Rules (Read First) - -1. You MUST explicitly \`return\` the final value from your \`exec\` code. If you do not return a value, the result will be \`undefined\`. -2. All MCP tools are async functions. Always call them as \`await ToolName(params)\`. -3. Use the exact function names and parameter shapes returned by \`search\`. -4. You CANNOT call \`search\` or \`exec\` from inside \`exec\` code—use them only as MCP tools. -5. \`console.log\` output is NOT the result. Logs are separate; the final answer must come from \`return\`. - -### Workflow - -1. Call \`search\` with relevant keywords to discover tools. -2. Read the returned JavaScript function declarations and JSDoc to understand names and parameters. -3. Call \`exec\` with JavaScript code that uses the discovered tools and ends with an explicit \`return\`. -4. Use the \`exec\` result as your answer. - -### What \`search\` Does - -- Input: keyword string (comma-separated for OR-matching), plus optional \`limit\`. -- Output: JavaScript async function declarations with JSDoc showing exact function names, parameters, and return types. - -### What \`exec\` Does - -- Runs JavaScript code in an isolated async context (wrapped as \`(async () => { your code })())\`. -- All discovered tools are exposed as async functions: \`await ToolName(params)\`. -- Available helpers: - - \`parallel(...promises)\` → \`Promise.all(promises)\` - - \`settle(...promises)\` → \`Promise.allSettled(promises)\` - - \`console.log/info/warn/error/debug\` -- Returns JSON with: \`result\` (your returned value), \`logs\` (optional), \`error\` (optional), \`isError\` (optional). - -### Example: Single Tool Call - -\`\`\`javascript -// Step 1: search({ query: "browser,fetch" }) -// Step 2: exec with: -const page = await CherryBrowser_fetch({ url: "https://example.com" }) -return page -\`\`\` - -### Example: Multiple Tools with Parallel - -\`\`\`javascript -const [forecast, time] = await parallel( - Weather_getForecast({ city: "Paris" }), - Time_getLocalTime({ city: "Paris" }) -) -return { city: "Paris", forecast, time } -\`\`\` - -### Example: Handle Partial Failures with Settle - -\`\`\`javascript -const results = await settle( - Weather_getForecast({ city: "Paris" }), - Weather_getForecast({ city: "Tokyo" }) -) -const successful = results.filter(r => r.status === "fulfilled").map(r => r.value) -return { results, successful } -\`\`\` - -### Example: Error Handling - -\`\`\`javascript -try { - const user = await User_lookup({ email: "user@example.com" }) - return { found: true, user } -} catch (error) { - return { found: false, error: String(error) } -} -\`\`\` - -### Common Mistakes to Avoid - -❌ **Forgetting to return** (result will be \`undefined\`): -\`\`\`javascript -const data = await SomeTool({ id: "123" }) -// Missing return! -\`\`\` - -✅ **Always return**: -\`\`\`javascript -const data = await SomeTool({ id: "123" }) -return data -\`\`\` - -❌ **Only logging, not returning**: -\`\`\`javascript -const data = await SomeTool({ id: "123" }) -console.log(data) // Logs are NOT the result! -\`\`\` - -❌ **Missing await**: -\`\`\`javascript -const data = SomeTool({ id: "123" }) // Returns Promise, not value! -return data -\`\`\` - -❌ **Awaiting before parallel**: -\`\`\`javascript -await parallel(await ToolA(), await ToolB()) // Wrong: runs sequentially -\`\`\` - -✅ **Pass promises directly to parallel**: -\`\`\`javascript -await parallel(ToolA(), ToolB()) // Correct: runs in parallel -\`\`\` - -### Best Practices - -- Always call \`search\` first to discover tools and confirm signatures. -- Always use an explicit \`return\` at the end of \`exec\` code. -- Use \`parallel\` for independent operations that can run at the same time. -- Use \`settle\` when some calls may fail but you still want partial results. -- Prefer a single \`exec\` call for multi-step flows. -- Treat \`console.*\` as debugging only, never as the primary result. -` - -interface ToolInfo { - name: string - serverName?: string - description?: string -} - -export function getHubModeSystemPrompt(tools: ToolInfo[] = []): string { - if (tools.length === 0) { - return '' - } - - const existingNames = new Set() - const toolsSection = tools - .map((t) => { - const functionName = generateMcpToolFunctionName(t.serverName, t.name, existingNames) - const desc = t.description || '' - const normalizedDesc = desc.replace(/\s+/g, ' ').trim() - const truncatedDesc = normalizedDesc.length > 50 ? `${normalizedDesc.slice(0, 50)}...` : normalizedDesc - return `- ${functionName}: ${truncatedDesc}` - }) - .join('\n') - - return `${HUB_MODE_SYSTEM_PROMPT_BASE} -### Available Tools -${toolsSection} -` -} - -const AUTO_MODE_SYSTEM_PROMPT_BASE = ` -You can discover and invoke MCP tools through a hub using TWO meta-tools: \`search\` and \`exec\`. - -## Tool Invocation Format - -When you want to call a tool, output exactly one XML block: - - - {tool_name} - {json_arguments} - - -Rules: -- \`{tool_name}\` MUST be either \`search\` or \`exec\` -- \`\` MUST contain valid JSON (no comments, no trailing commas) -- Do NOT include extra text before or after the \`\` block - -## Available Tools - -1. **search** - Discover MCP tools by keyword - \`\`\`json - { "query": "keyword1,keyword2", "limit": 10 } - \`\`\` - Returns JavaScript function declarations with JSDoc showing names, parameters, and return types. - -2. **exec** - Execute JavaScript that calls discovered tools - \`\`\`json - { "code": "const r = await ToolName({...}); return r;" } - \`\`\` - **CRITICAL:** You MUST \`return\` the final value, or result will be \`undefined\`. - -## Workflow - -1. Call \`search\` with keywords to discover tools -2. Read the returned function signatures carefully -3. Call \`exec\` with JavaScript code that: - - Uses ONLY functions returned by \`search\` - - Calls them with \`await\` - - Ends with explicit \`return\` -4. Answer the user based on the result - -## Example - -User: "Calculate 15 * 7" - -Assistant calls search: - - search - {"query": "python,calculator"} - - -Hub returns function signature: -\`\`\`js -async function CherryPython_pythonExecute(params: { code: string }): Promise -\`\`\` - -Assistant calls exec: - - exec - {"code": "const result = await CherryPython_pythonExecute({ code: '15 * 7' }); return result;"} - - -Hub returns: { "result": 105 } - -Assistant answers: "15 × 7 = 105" - -## Common Mistakes - -❌ Forgetting to return (result will be undefined): -\`\`\`js -await SomeTool({ id: "123" }) -\`\`\` - -✅ Always return: -\`\`\`js -const data = await SomeTool({ id: "123" }); return data; -\`\`\` - -❌ Calling exec before search - you must discover tools first - -❌ Using functions not returned by search -` - -export function getAutoModeSystemPrompt(tools: ToolInfo[] = []): string { - if (tools.length === 0) { - return '' - } - - const existingNames = new Set() - const toolsSection = tools - .map((t) => { - const functionName = generateMcpToolFunctionName(t.serverName, t.name, existingNames) - const desc = t.description || '' - const normalizedDesc = desc.replace(/\s+/g, ' ').trim() - const truncatedDesc = normalizedDesc.length > 50 ? `${normalizedDesc.slice(0, 50)}...` : normalizedDesc - return `- ${functionName}: ${truncatedDesc}` - }) - .join('\n') - - return `${AUTO_MODE_SYSTEM_PROMPT_BASE} -## Discoverable Tools (use search to get full signatures) -${toolsSection} -` -}