feat(ai-tools): 新增 AI 提示与技能配置探针

This commit is contained in:
Syngnat
2026-06-08 20:41:45 +08:00
parent 472686e8ff
commit 4ac6a9e798
13 changed files with 204 additions and 1 deletions

View File

@@ -879,6 +879,7 @@ export const AIChatPanel: React.FC<AIChatPanelProps> = ({
savedQueries: useStore.getState().savedQueries,
sqlSnippets: useStore.getState().sqlSnippets,
skills,
userPromptSettings,
dynamicModels,
});
const toolResultMsg: AIChatMessage = buildToolResultMessage({

View File

@@ -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('查看当前连接');

View File

@@ -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',

View File

@@ -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', {}),

View File

@@ -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<AILocalToolRuntime>;
}
@@ -217,6 +219,7 @@ export async function executeLocalAIToolCall({
savedQueries = [],
sqlSnippets = [],
skills = [],
userPromptSettings,
dynamicModels = [],
runtime,
}: ExecuteLocalAIToolCallOptions): Promise<ExecuteLocalAIToolCallResult> {
@@ -240,6 +243,7 @@ export async function executeLocalAIToolCall({
savedQueries,
sqlSnippets,
skills,
userPromptSettings,
dynamicModels,
runtime: mergedRuntime,
});

View File

@@ -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']);
});
});

View File

@@ -0,0 +1,60 @@
import type { AISkillConfig, AIUserPromptSettings } from '../../types';
const PROMPT_SCOPE_LABELS: Record<keyof AIUserPromptSettings, string> = {
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<keyof AIUserPromptSettings>).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',
};
};

View File

@@ -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: '读取当前工作区页签失败',

View File

@@ -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');

View File

@@ -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',

View File

@@ -25,6 +25,7 @@ interface AIToolCallingBlockProps {
const TOOL_ACTION_LABELS: Record<string, string> = {
inspect_ai_runtime: '读取当前 AI 运行状态',
inspect_mcp_setup: '读取当前 MCP 配置状态',
inspect_ai_guidance: '读取当前 AI 提示与技能配置',
get_connections: '获取可用连接信息',
get_databases: '扫描数据库列表',
get_tables: '分析表结构信息',

View File

@@ -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);

View File

@@ -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: "🧷",