From 4ac6a9e798a0e234ec26ab6af680e0ac10e338f1 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Mon, 8 Jun 2026 20:41:45 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(ai-tools):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20AI=20=E6=8F=90=E7=A4=BA=E4=B8=8E=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=8E=A2=E9=92=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/AIChatPanel.tsx | 1 + .../ai/AIBuiltinToolsCatalog.test.tsx | 2 + .../components/ai/AIBuiltinToolsCatalog.tsx | 5 ++ .../components/ai/aiLocalToolExecutor.test.ts | 35 +++++++++++ .../src/components/ai/aiLocalToolExecutor.ts | 4 ++ .../components/ai/aiPromptInsights.test.ts | 42 +++++++++++++ .../src/components/ai/aiPromptInsights.ts | 60 +++++++++++++++++++ .../ai/aiSnapshotInspectionToolExecutor.ts | 13 ++++ .../ai/aiSystemContextMessages.test.ts | 3 +- .../components/ai/aiSystemContextMessages.ts | 14 +++++ .../messageBubble/AIMessageStatusBlocks.tsx | 1 + frontend/src/utils/aiToolRegistry.test.ts | 8 +++ frontend/src/utils/aiToolRegistry.ts | 17 ++++++ 13 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/ai/aiPromptInsights.test.ts create mode 100644 frontend/src/components/ai/aiPromptInsights.ts diff --git a/frontend/src/components/AIChatPanel.tsx b/frontend/src/components/AIChatPanel.tsx index b5dc992..f0d6184 100644 --- a/frontend/src/components/AIChatPanel.tsx +++ b/frontend/src/components/AIChatPanel.tsx @@ -879,6 +879,7 @@ export const AIChatPanel: React.FC = ({ savedQueries: useStore.getState().savedQueries, sqlSnippets: useStore.getState().sqlSnippets, skills, + userPromptSettings, dynamicModels, }); const toolResultMsg: AIChatMessage = buildToolResultMessage({ diff --git a/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx b/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx index f0fabe9..2c93d98 100644 --- a/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx +++ b/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx @@ -30,6 +30,8 @@ describe('AIBuiltinToolsCatalog', () => { expect(markup).toContain('inspect_ai_runtime'); expect(markup).toContain('排查 MCP 接入状态'); expect(markup).toContain('inspect_mcp_setup'); + expect(markup).toContain('查看当前提示与 Skills'); + expect(markup).toContain('inspect_ai_guidance'); expect(markup).toContain('查看当前 AI 上下文'); expect(markup).toContain('inspect_ai_context'); expect(markup).toContain('查看当前连接'); diff --git a/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx b/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx index bd7f414..62891ff 100644 --- a/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx +++ b/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx @@ -47,6 +47,11 @@ const BUILTIN_TOOL_FLOWS = [ steps: 'inspect_mcp_setup → inspect_ai_runtime', description: '适合先确认当前配置了哪些 MCP 服务、哪些已启用、外部客户端有没有写入当前 GoNavi 路径,再结合运行时工具列表判断为什么某个工具没暴露出来。', }, + { + title: '查看当前提示与 Skills', + steps: 'inspect_ai_guidance → inspect_ai_runtime', + description: '适合先确认当前自定义提示词、启用的 Skills、依赖工具和生效范围,再解释为什么 AI 当前会这样回答或为什么某个规则没有触发。', + }, { title: '查看当前 AI 上下文', steps: 'inspect_ai_context → inspect_table_bundle / get_columns', diff --git a/frontend/src/components/ai/aiLocalToolExecutor.test.ts b/frontend/src/components/ai/aiLocalToolExecutor.test.ts index 4ea227c..7c7665c 100644 --- a/frontend/src/components/ai/aiLocalToolExecutor.test.ts +++ b/frontend/src/components/ai/aiLocalToolExecutor.test.ts @@ -280,6 +280,41 @@ describe('aiLocalToolExecutor', () => { expect(result.content).toContain('"launchCommandPreview":"gonavi-mcp-server stdio"'); }); + it('returns the current ai guidance snapshot so the model can inspect active prompts and enabled skills', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_ai_guidance', {}), + connections: [buildConnection()], + mcpTools: [], + toolContextMap: new Map(), + userPromptSettings: { + global: '回答前先核对上下文。', + database: '生成 SQL 时只读优先。', + jvm: '', + jvmDiagnostic: '', + }, + skills: [{ + id: 'skill-1', + name: '结构审查', + description: '优先核对字段', + systemPrompt: '先看字段和索引,再给结论。', + enabled: true, + scopes: ['database'], + requiredTools: ['get_columns'], + }], + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"customPromptCount":2'); + expect(result.content).toContain('"scope":"global"'); + expect(result.content).toContain('回答前先核对上下文'); + expect(result.content).toContain('"enabledSkillCount":1'); + expect(result.content).toContain('"name":"结构审查"'); + }); + it('returns the current connection snapshot so the model can inspect host, db, and ssh state', async () => { const result = await executeLocalAIToolCall({ toolCall: buildToolCall('inspect_current_connection', {}), diff --git a/frontend/src/components/ai/aiLocalToolExecutor.ts b/frontend/src/components/ai/aiLocalToolExecutor.ts index ac8d7fe..dfeb84f 100644 --- a/frontend/src/components/ai/aiLocalToolExecutor.ts +++ b/frontend/src/components/ai/aiLocalToolExecutor.ts @@ -7,6 +7,7 @@ import type { AIMCPToolDescriptor, AISkillConfig, AIToolCall, + AIUserPromptSettings, SavedConnection, SavedQuery, SqlSnippet, @@ -54,6 +55,7 @@ export interface ExecuteLocalAIToolCallOptions { savedQueries?: SavedQuery[]; sqlSnippets?: SqlSnippet[]; skills?: AISkillConfig[]; + userPromptSettings?: AIUserPromptSettings; dynamicModels?: string[]; runtime?: Partial; } @@ -217,6 +219,7 @@ export async function executeLocalAIToolCall({ savedQueries = [], sqlSnippets = [], skills = [], + userPromptSettings, dynamicModels = [], runtime, }: ExecuteLocalAIToolCallOptions): Promise { @@ -240,6 +243,7 @@ export async function executeLocalAIToolCall({ savedQueries, sqlSnippets, skills, + userPromptSettings, dynamicModels, runtime: mergedRuntime, }); diff --git a/frontend/src/components/ai/aiPromptInsights.test.ts b/frontend/src/components/ai/aiPromptInsights.test.ts new file mode 100644 index 0000000..cd3d07b --- /dev/null +++ b/frontend/src/components/ai/aiPromptInsights.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest'; + +import { buildAIGuidanceSnapshot } from './aiPromptInsights'; + +describe('aiPromptInsights', () => { + it('summarizes active custom prompts and enabled skills for runtime inspection', () => { + const snapshot = buildAIGuidanceSnapshot({ + userPromptSettings: { + global: '回答前先核对上下文。', + database: '默认只读优先。', + jvm: '', + jvmDiagnostic: '', + }, + skills: [ + { + id: 'skill-1', + name: '结构审查', + description: '优先核对字段和索引', + systemPrompt: '先看字段,再给结论。', + enabled: true, + scopes: ['database'], + requiredTools: ['get_columns'], + }, + { + id: 'skill-2', + name: 'JVM 诊断', + description: '诊断命令审查', + systemPrompt: '先列风险。', + enabled: false, + scopes: ['jvmDiagnostic'], + }, + ], + }); + + expect(snapshot.customPromptCount).toBe(2); + expect(snapshot.customPrompts.find((item) => item.scope === 'global')?.enabled).toBe(true); + expect(snapshot.enabledSkillCount).toBe(1); + expect(snapshot.disabledSkillCount).toBe(1); + expect(snapshot.enabledSkills[0].name).toBe('结构审查'); + expect(snapshot.enabledSkills[0].requiredTools).toEqual(['get_columns']); + }); +}); diff --git a/frontend/src/components/ai/aiPromptInsights.ts b/frontend/src/components/ai/aiPromptInsights.ts new file mode 100644 index 0000000..9f32547 --- /dev/null +++ b/frontend/src/components/ai/aiPromptInsights.ts @@ -0,0 +1,60 @@ +import type { AISkillConfig, AIUserPromptSettings } from '../../types'; + +const PROMPT_SCOPE_LABELS: Record = { + global: '全局', + database: '数据库会话', + jvm: 'JVM 资源分析', + jvmDiagnostic: 'JVM 诊断', +}; + +export const buildAIGuidanceSnapshot = (params: { + userPromptSettings?: AIUserPromptSettings; + skills?: AISkillConfig[]; +}) => { + const userPromptSettings = params.userPromptSettings || { + global: '', + database: '', + jvm: '', + jvmDiagnostic: '', + }; + const skills = Array.isArray(params.skills) ? params.skills : []; + + const customPrompts = (Object.keys(PROMPT_SCOPE_LABELS) as Array).map((scope) => { + const content = String(userPromptSettings[scope] || '').trim(); + return { + scope, + label: PROMPT_SCOPE_LABELS[scope], + enabled: content.length > 0, + charCount: content.length, + content, + }; + }); + + const enabledSkills = skills + .filter((skill) => skill?.enabled) + .map((skill) => ({ + id: skill.id, + name: skill.name, + description: skill.description || '', + scopes: Array.isArray(skill.scopes) ? skill.scopes : [], + requiredTools: Array.isArray(skill.requiredTools) ? skill.requiredTools : [], + systemPrompt: String(skill.systemPrompt || '').trim(), + systemPromptCharCount: String(skill.systemPrompt || '').trim().length, + })) + .sort((left, right) => left.name.localeCompare(right.name)); + + const disabledSkillCount = skills.filter((skill) => skill && !skill.enabled).length; + const enabledPromptCount = customPrompts.filter((item) => item.enabled).length; + + return { + customPromptCount: enabledPromptCount, + customPrompts, + skillCount: skills.length, + enabledSkillCount: enabledSkills.length, + disabledSkillCount, + enabledSkills, + message: enabledPromptCount > 0 || enabledSkills.length > 0 + ? `当前已启用 ${enabledPromptCount} 条自定义提示词、${enabledSkills.length} 个 Skills` + : '当前没有启用自定义提示词或 Skills', + }; +}; diff --git a/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts b/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts index 003540f..b87824e 100644 --- a/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts +++ b/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts @@ -6,6 +6,7 @@ import type { AIProviderConfig, AISafetyLevel, AISkillConfig, + AIUserPromptSettings, SavedConnection, SavedQuery, SqlSnippet, @@ -16,6 +17,7 @@ import { BUILTIN_AI_TOOL_INFO } from '../../utils/aiToolRegistry'; import { buildAIContextSnapshot } from './aiContextInsights'; import { buildCurrentConnectionSnapshot } from './aiConnectionInsights'; import { buildMCPSetupSnapshot } from './aiMCPInsights'; +import { buildAIGuidanceSnapshot } from './aiPromptInsights'; import { buildAIRuntimeSnapshot } from './aiRuntimeInsights'; import { buildSavedQueriesSnapshot, @@ -53,6 +55,7 @@ interface ExecuteSnapshotInspectionToolCallOptions { savedQueries?: SavedQuery[]; sqlSnippets?: SqlSnippet[]; skills?: AISkillConfig[]; + userPromptSettings?: AIUserPromptSettings; dynamicModels?: string[]; runtime?: AISnapshotInspectionRuntime; } @@ -80,6 +83,7 @@ export async function executeSnapshotInspectionToolCall( savedQueries = [], sqlSnippets = [], skills = [], + userPromptSettings, dynamicModels = [], runtime, } = options; @@ -118,6 +122,14 @@ export async function executeSnapshotInspectionToolCall( success: true, }; } + case 'inspect_ai_guidance': + return { + content: JSON.stringify(buildAIGuidanceSnapshot({ + userPromptSettings, + skills, + })), + success: true, + }; case 'inspect_current_connection': return { content: JSON.stringify(buildCurrentConnectionSnapshot({ @@ -199,6 +211,7 @@ export async function executeSnapshotInspectionToolCall( const label = { inspect_ai_runtime: '读取当前 AI 运行状态失败', inspect_mcp_setup: '读取 MCP 配置状态失败', + inspect_ai_guidance: '读取当前 AI 提示与技能配置失败', inspect_current_connection: '读取当前连接失败', inspect_active_tab: '读取当前活动页签失败', inspect_workspace_tabs: '读取当前工作区页签失败', diff --git a/frontend/src/components/ai/aiSystemContextMessages.test.ts b/frontend/src/components/ai/aiSystemContextMessages.test.ts index c165d70..bec02c2 100644 --- a/frontend/src/components/ai/aiSystemContextMessages.test.ts +++ b/frontend/src/components/ai/aiSystemContextMessages.test.ts @@ -68,7 +68,7 @@ describe('buildAISystemContextMessages', () => { connections: [connections[0]], tabs: [], activeTabId: null, - availableToolNames: ['inspect_workspace_tabs', 'inspect_ai_runtime', 'inspect_mcp_setup', 'inspect_ai_context', 'inspect_current_connection', 'inspect_saved_queries', 'inspect_sql_snippets', 'get_columns'], + availableToolNames: ['inspect_workspace_tabs', 'inspect_ai_runtime', 'inspect_mcp_setup', 'inspect_ai_guidance', 'inspect_ai_context', 'inspect_current_connection', 'inspect_saved_queries', 'inspect_sql_snippets', 'get_columns'], skills, userPromptSettings, }); @@ -77,6 +77,7 @@ describe('buildAISystemContextMessages', () => { expect(joined).toContain('inspect_workspace_tabs 盘点当前工作区'); expect(joined).toContain('inspect_ai_runtime 读取当前 AI 运行状态'); expect(joined).toContain('inspect_mcp_setup 读取真实 MCP 配置'); + expect(joined).toContain('inspect_ai_guidance 读取真实提示与技能配置'); expect(joined).toContain('inspect_ai_context 读取当前挂载的表结构上下文'); expect(joined).toContain('inspect_current_connection'); expect(joined).toContain('inspect_saved_queries'); diff --git a/frontend/src/components/ai/aiSystemContextMessages.ts b/frontend/src/components/ai/aiSystemContextMessages.ts index fb9c00f..b1784a0 100644 --- a/frontend/src/components/ai/aiSystemContextMessages.ts +++ b/frontend/src/components/ai/aiSystemContextMessages.ts @@ -121,6 +121,19 @@ const appendMCPSetupInspectionGuidance = ( }); }; +const appendAIGuidanceInspectionGuidance = ( + messages: AISystemContextMessage[], + availableToolNames: string[], +) => { + if (!availableToolNames.includes('inspect_ai_guidance')) { + return; + } + messages.push({ + role: 'system', + content: '如果用户提到“你现在带了哪些提示词”“当前生效的是哪些 Skills”“为什么你会这样回答”“当前数据库/JVM prompt 是什么”,优先调用 inspect_ai_guidance 读取真实提示与技能配置,不要凭记忆概括。', + }); +}; + const resolveDatabaseDisplayType = (config: ConnectionConfig | undefined): string => { const dbType = config?.type || 'unknown'; return dbType === 'diros' ? 'Doris' : dbType.charAt(0).toUpperCase() + dbType.slice(1); @@ -339,6 +352,7 @@ SELECT * FROM users WHERE status = 1; } appendAIRuntimeInspectionGuidance(systemMessages, availableToolNames); appendMCPSetupInspectionGuidance(systemMessages, availableToolNames); + appendAIGuidanceInspectionGuidance(systemMessages, availableToolNames); if (availableToolNames.includes('inspect_current_connection')) { systemMessages.push({ role: 'system', diff --git a/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx b/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx index 4907cb4..5138ab9 100644 --- a/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx +++ b/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx @@ -25,6 +25,7 @@ interface AIToolCallingBlockProps { const TOOL_ACTION_LABELS: Record = { inspect_ai_runtime: '读取当前 AI 运行状态', inspect_mcp_setup: '读取当前 MCP 配置状态', + inspect_ai_guidance: '读取当前 AI 提示与技能配置', get_connections: '获取可用连接信息', get_databases: '扫描数据库列表', get_tables: '分析表结构信息', diff --git a/frontend/src/utils/aiToolRegistry.test.ts b/frontend/src/utils/aiToolRegistry.test.ts index c287418..bf4d426 100644 --- a/frontend/src/utils/aiToolRegistry.test.ts +++ b/frontend/src/utils/aiToolRegistry.test.ts @@ -17,6 +17,13 @@ describe('aiToolRegistry', () => { expect(info?.tool.function.description).toContain('外部客户端'); }); + it('registers the ai-guidance inspector as a builtin tool', () => { + const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_guidance'); + expect(info).toBeTruthy(); + expect(info?.desc).toContain('提示词与 Skills'); + expect(info?.tool.function.description).toContain('自定义提示词'); + }); + it('registers the current-connection inspector as a builtin tool', () => { const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_current_connection'); expect(info).toBeTruthy(); @@ -52,6 +59,7 @@ describe('aiToolRegistry', () => { expect(tools.some((item) => item.function.name === 'inspect_ai_runtime')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_mcp_setup')).toBe(true); + expect(tools.some((item) => item.function.name === 'inspect_ai_guidance')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_current_connection')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_saved_queries')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_sql_snippets')).toBe(true); diff --git a/frontend/src/utils/aiToolRegistry.ts b/frontend/src/utils/aiToolRegistry.ts index 7f46b3e..ab237de 100644 --- a/frontend/src/utils/aiToolRegistry.ts +++ b/frontend/src/utils/aiToolRegistry.ts @@ -343,6 +343,23 @@ export const BUILTIN_AI_TOOL_INFO: AIBuiltinToolInfo[] = [ }, }, }, + { + name: "inspect_ai_guidance", + icon: "🧠", + desc: "查看当前 AI 提示词与 Skills 配置", + detail: + "返回当前用户自定义的全局/数据库/JVM 提示词,以及当前启用的 Skills、作用域、依赖工具和 skill prompt 内容。适合用户问“你现在到底带了哪些提示词”“为什么你会这样回答”“当前有哪些 Skills 在生效”时先读真实配置。", + params: "无参数", + tool: { + type: "function", + function: { + name: "inspect_ai_guidance", + description: + "读取当前 AI 的提示与技能配置快照,包括用户自定义提示词、当前启用的 Skills、作用域、依赖工具和各自的 system prompt。适用于用户提到当前提示词、当前 Skill、为什么 AI 当前会这样回答、当前有哪些规则在生效时,先读取真实配置再解释。", + parameters: { type: "object", properties: {} }, + }, + }, + }, { name: "inspect_ai_context", icon: "🧷",