feat: support system prompt variables (#5995)

* feat: support system prompt variables

* feat: add tip

* fix ci test fail
This commit is contained in:
purefkh 2025-05-27 21:49:25 +08:00 committed by GitHub
parent f171839830
commit 65273b055c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 82 additions and 16 deletions

View File

@ -8,6 +8,7 @@
"add.name.placeholder": "Enter name", "add.name.placeholder": "Enter name",
"add.prompt": "Prompt", "add.prompt": "Prompt",
"add.prompt.placeholder": "Enter prompt", "add.prompt.placeholder": "Enter prompt",
"add.prompt.variables.tip": "Available variables: {{date}}, {{time}}, {{datetime}}, {{system}}, {{arch}}, {{language}}, {{model_name}}",
"add.title": "Create Agent", "add.title": "Create Agent",
"import": { "import": {
"title": "Import from External", "title": "Import from External",

View File

@ -8,6 +8,7 @@
"add.name.placeholder": "名前を入力", "add.name.placeholder": "名前を入力",
"add.prompt": "プロンプト", "add.prompt": "プロンプト",
"add.prompt.placeholder": "プロンプトを入力", "add.prompt.placeholder": "プロンプトを入力",
"add.prompt.variables.tip": "利用可能な変数:{{date}}, {{time}}, {{datetime}}, {{system}}, {{arch}}, {{language}}, {{model_name}}",
"add.title": "エージェントを作成", "add.title": "エージェントを作成",
"import": { "import": {
"title": "外部からインポート", "title": "外部からインポート",

View File

@ -8,6 +8,7 @@
"add.name.placeholder": "Введите имя", "add.name.placeholder": "Введите имя",
"add.prompt": "Промпт", "add.prompt": "Промпт",
"add.prompt.placeholder": "Введите промпт", "add.prompt.placeholder": "Введите промпт",
"add.prompt.variables.tip": "Доступные переменные: {{date}}, {{time}}, {{datetime}}, {{system}}, {{arch}}, {{language}}, {{model_name}}",
"add.title": "Создать агента", "add.title": "Создать агента",
"delete.popup.content": "Вы уверены, что хотите удалить этого агента?", "delete.popup.content": "Вы уверены, что хотите удалить этого агента?",
"edit.message.add.title": "Добавить", "edit.message.add.title": "Добавить",

View File

@ -8,6 +8,7 @@
"add.name.placeholder": "输入名称", "add.name.placeholder": "输入名称",
"add.prompt": "提示词", "add.prompt": "提示词",
"add.prompt.placeholder": "输入提示词", "add.prompt.placeholder": "输入提示词",
"add.prompt.variables.tip": "可用的变量:{{date}}, {{time}}, {{datetime}}, {{system}}, {{arch}}, {{language}}, {{model_name}}",
"add.title": "创建智能体", "add.title": "创建智能体",
"import": { "import": {
"title": "从外部导入", "title": "从外部导入",

View File

@ -8,6 +8,7 @@
"add.name.placeholder": "輸入名稱", "add.name.placeholder": "輸入名稱",
"add.prompt": "提示詞", "add.prompt": "提示詞",
"add.prompt.placeholder": "輸入提示詞", "add.prompt.placeholder": "輸入提示詞",
"add.prompt.variables.tip": "可用的變數:{{date}}, {{time}}, {{datetime}}, {{system}}, {{arch}}, {{language}}, {{model_name}}",
"add.title": "建立智慧代理人", "add.title": "建立智慧代理人",
"import": { "import": {
"title": "從外部導入", "title": "從外部導入",

View File

@ -1,12 +1,12 @@
import 'emoji-picker-element' import 'emoji-picker-element'
import { CloseCircleFilled } from '@ant-design/icons' import { CloseCircleFilled, QuestionCircleOutlined } from '@ant-design/icons'
import EmojiPicker from '@renderer/components/EmojiPicker' import EmojiPicker from '@renderer/components/EmojiPicker'
import { Box, HSpaceBetweenStack, HStack } from '@renderer/components/Layout' import { Box, HSpaceBetweenStack, HStack } from '@renderer/components/Layout'
import { estimateTextTokens } from '@renderer/services/TokenService' import { estimateTextTokens } from '@renderer/services/TokenService'
import { Assistant, AssistantSettings } from '@renderer/types' import { Assistant, AssistantSettings } from '@renderer/types'
import { getLeadingEmoji } from '@renderer/utils' import { getLeadingEmoji } from '@renderer/utils'
import { Button, Input, Popover } from 'antd' import { Button, Input, Popover, Tooltip } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -90,9 +90,12 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant }
style={{ flex: 1 }} style={{ flex: 1 }}
/> />
</HStack> </HStack>
<Box mt={8} mb={8} style={{ fontWeight: 'bold' }}> <HStack mt={8} mb={8} alignItems="center" gap={4}>
{t('common.prompt')} <Box style={{ fontWeight: 'bold' }}>{t('common.prompt')}</Box>
</Box> <Tooltip title={t('agents.add.prompt.variables.tip')}>
<QuestionCircleOutlined size={14} color="var(--color-text-2)" />
</Tooltip>
</HStack>
<TextAreaContainer> <TextAreaContainer>
{showMarkdown ? ( {showMarkdown ? (
<MarkdownContainer onClick={() => setShowMarkdown(false)}> <MarkdownContainer onClick={() => setShowMarkdown(false)}>

View File

@ -243,7 +243,7 @@ export default class AnthropicProvider extends BaseProvider {
}) })
if (this.useSystemPromptForTools && mcpTools && mcpTools.length) { if (this.useSystemPromptForTools && mcpTools && mcpTools.length) {
systemPrompt = buildSystemPrompt(systemPrompt, mcpTools) systemPrompt = await buildSystemPrompt(systemPrompt, mcpTools)
} }
let systemMessage: TextBlockParam | undefined = undefined let systemMessage: TextBlockParam | undefined = undefined

View File

@ -370,7 +370,7 @@ export default class GeminiProvider extends BaseProvider {
}) })
if (this.useSystemPromptForTools) { if (this.useSystemPromptForTools) {
systemInstruction = buildSystemPrompt(assistant.prompt || '', mcpTools) systemInstruction = await buildSystemPrompt(assistant.prompt || '', mcpTools)
} }
const toolResponses: MCPToolResponse[] = [] const toolResponses: MCPToolResponse[] = []

View File

@ -399,7 +399,7 @@ export default class OpenAIProvider extends BaseOpenAIProvider {
}) })
if (this.useSystemPromptForTools) { if (this.useSystemPromptForTools) {
systemMessage.content = buildSystemPrompt(systemMessage.content || '', mcpTools) systemMessage.content = await buildSystemPrompt(systemMessage.content || '', mcpTools)
} }
const userMessages: ChatCompletionMessageParam[] = [] const userMessages: ChatCompletionMessageParam[] = []

View File

@ -352,7 +352,7 @@ export abstract class BaseOpenAIProvider extends BaseProvider {
tools = tools.concat(extraTools) tools = tools.concat(extraTools)
if (this.useSystemPromptForTools) { if (this.useSystemPromptForTools) {
systemMessageInput.text = buildSystemPrompt(systemMessageInput.text || '', mcpTools) systemMessageInput.text = await buildSystemPrompt(systemMessageInput.text || '', mcpTools)
} }
systemMessageContent.push(systemMessageInput) systemMessageContent.push(systemMessageInput)
systemMessage.content = systemMessageContent systemMessage.content = systemMessageContent

View File

@ -29,26 +29,26 @@ describe('prompt', () => {
}) })
describe('buildSystemPrompt', () => { describe('buildSystemPrompt', () => {
it('should build prompt with tools', () => { it('should build prompt with tools', async () => {
const userPrompt = 'Custom user system prompt' const userPrompt = 'Custom user system prompt'
const tools = [ const tools = [
{ id: 'test-tool', description: 'Test tool description', inputSchema: { type: 'object' } } as MCPTool { id: 'test-tool', description: 'Test tool description', inputSchema: { type: 'object' } } as MCPTool
] ]
const result = buildSystemPrompt(userPrompt, tools) const result = await buildSystemPrompt(userPrompt, tools)
expect(result).toContain(userPrompt) expect(result).toContain(userPrompt)
expect(result).toContain('test-tool') expect(result).toContain('test-tool')
expect(result).toContain('Test tool description') expect(result).toContain('Test tool description')
}) })
it('should return user prompt without tools', () => { it('should return user prompt without tools', async () => {
const userPrompt = 'Custom user system prompt' const userPrompt = 'Custom user system prompt'
const result = buildSystemPrompt(userPrompt, []) const result = await buildSystemPrompt(userPrompt, [])
expect(result).toBe(userPrompt) expect(result).toBe(userPrompt)
}) })
it('should handle null or undefined user prompt', () => { it('should handle null or undefined user prompt', async () => {
const tools = [ const tools = [
{ id: 'test-tool', description: 'Test tool description', inputSchema: { type: 'object' } } as MCPTool { id: 'test-tool', description: 'Test tool description', inputSchema: { type: 'object' } } as MCPTool
] ]

View File

@ -1,5 +1,5 @@
import store from '@renderer/store'
import { MCPTool } from '@renderer/types' import { MCPTool } from '@renderer/types'
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 tool 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 tool 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.
@ -147,7 +147,65 @@ ${availableTools}
</tools>` </tools>`
} }
export const buildSystemPrompt = (userSystemPrompt: string, tools?: MCPTool[]): string => { export const buildSystemPrompt = async (userSystemPrompt: string, tools?: MCPTool[]): Promise<string> => {
if (typeof userSystemPrompt === 'string') {
const now = new Date()
if (userSystemPrompt.includes('{{date}}')) {
const date = now.toLocaleDateString()
userSystemPrompt = userSystemPrompt.replace(/{{date}}/g, date)
}
if (userSystemPrompt.includes('{{time}}')) {
const time = now.toLocaleTimeString()
userSystemPrompt = userSystemPrompt.replace(/{{time}}/g, time)
}
if (userSystemPrompt.includes('{{datetime}}')) {
const datetime = now.toLocaleString()
userSystemPrompt = userSystemPrompt.replace(/{{datetime}}/g, datetime)
}
if (userSystemPrompt.includes('{{system}}')) {
try {
const systemType = await window.api.system.getDeviceType()
userSystemPrompt = userSystemPrompt.replace(/{{system}}/g, systemType)
} catch (error) {
console.error('Failed to get system type:', error)
userSystemPrompt = userSystemPrompt.replace(/{{system}}/g, 'Unknown System')
}
}
if (userSystemPrompt.includes('{{language}}')) {
try {
const language = store.getState().settings.language
userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, language)
} catch (error) {
console.error('Failed to get language:', error)
userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, 'Unknown System Language')
}
}
if (userSystemPrompt.includes('{{arch}}')) {
try {
const appInfo = await window.api.getAppInfo()
userSystemPrompt = userSystemPrompt.replace(/{{arch}}/g, appInfo.arch)
} catch (error) {
console.error('Failed to get architecture:', error)
userSystemPrompt = userSystemPrompt.replace(/{{arch}}/g, 'Unknown Architecture')
}
}
if (userSystemPrompt.includes('{{model_name}}')) {
try {
const modelName = store.getState().llm.defaultModel.name
userSystemPrompt = userSystemPrompt.replace(/{{model_name}}/g, modelName)
} catch (error) {
console.error('Failed to get model name:', error)
userSystemPrompt = userSystemPrompt.replace(/{{model_name}}/g, 'Unknown Model')
}
}
}
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)