mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-15 10:59:41 +08:00
✨ feat(ai-tools): 新增 AI 运行时探针
This commit is contained in:
@@ -878,6 +878,8 @@ export const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
||||
sqlLogs: useStore.getState().sqlLogs,
|
||||
savedQueries: useStore.getState().savedQueries,
|
||||
sqlSnippets: useStore.getState().sqlSnippets,
|
||||
skills,
|
||||
dynamicModels,
|
||||
});
|
||||
const toolResultMsg: AIChatMessage = buildToolResultMessage({
|
||||
id: genId(),
|
||||
@@ -996,7 +998,7 @@ export const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
||||
console.error('Failed to chain tool call', e);
|
||||
setSending(false);
|
||||
}
|
||||
}, [availableTools, buildSystemContextMessages, mcpTools, sid]);
|
||||
}, [availableTools, buildSystemContextMessages, dynamicModels, mcpTools, sid, skills]);
|
||||
|
||||
const handleSend = useCallback(async () => {
|
||||
const text = input.trim();
|
||||
|
||||
@@ -26,6 +26,8 @@ describe('AIBuiltinToolsCatalog', () => {
|
||||
expect(markup).toContain('inspect_table_bundle');
|
||||
expect(markup).toContain('全库快速摸底');
|
||||
expect(markup).toContain('inspect_database_bundle');
|
||||
expect(markup).toContain('查看 AI 当前能力');
|
||||
expect(markup).toContain('inspect_ai_runtime');
|
||||
expect(markup).toContain('查看当前 AI 上下文');
|
||||
expect(markup).toContain('inspect_ai_context');
|
||||
expect(markup).toContain('查看当前连接');
|
||||
|
||||
@@ -37,6 +37,11 @@ const BUILTIN_TOOL_FLOWS = [
|
||||
steps: 'inspect_database_bundle → inspect_table_bundle',
|
||||
description: '适合先看整库有哪些表、每张表大概有哪些字段,再对目标表继续做深挖快照。',
|
||||
},
|
||||
{
|
||||
title: '查看 AI 当前能力',
|
||||
steps: 'inspect_ai_runtime → inspect_ai_context / inspect_current_connection',
|
||||
description: '适合先确认当前模型、安全级别、上下文级别、Skills 和 MCP 工具,再决定让 AI 走哪条探针链路。',
|
||||
},
|
||||
{
|
||||
title: '查看当前 AI 上下文',
|
||||
steps: 'inspect_ai_context → inspect_table_bundle / get_columns',
|
||||
|
||||
@@ -169,6 +169,61 @@ describe('aiLocalToolExecutor', () => {
|
||||
expect(result.content).toContain('CREATE TABLE orders');
|
||||
});
|
||||
|
||||
it('returns the current ai runtime snapshot so the model can inspect provider, safety, skills, and tools', async () => {
|
||||
const result = await executeLocalAIToolCall({
|
||||
toolCall: buildToolCall('inspect_ai_runtime', {}),
|
||||
connections: [buildConnection()],
|
||||
mcpTools: [{
|
||||
alias: 'browser_open',
|
||||
originalName: 'browser_open',
|
||||
serverId: 'server-1',
|
||||
serverName: 'browser',
|
||||
title: '打开浏览器',
|
||||
description: '打开页面',
|
||||
}],
|
||||
skills: [{
|
||||
id: 'skill-1',
|
||||
name: '结构审查',
|
||||
systemPrompt: '先核对字段',
|
||||
enabled: true,
|
||||
scopes: ['database'],
|
||||
requiredTools: ['get_columns'],
|
||||
}],
|
||||
dynamicModels: ['gpt-5.4', 'gpt-4.1'],
|
||||
toolContextMap: new Map(),
|
||||
runtime: {
|
||||
getDatabases: vi.fn(),
|
||||
getTables: vi.fn(),
|
||||
getAIRuntimeState: vi.fn().mockResolvedValue({
|
||||
activeProviderId: 'provider-1',
|
||||
providers: [{
|
||||
id: 'provider-1',
|
||||
type: 'openai',
|
||||
name: 'OpenAI 主账号',
|
||||
apiKey: '',
|
||||
hasSecret: true,
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
model: 'gpt-5.4',
|
||||
models: ['gpt-5.4', 'gpt-4.1'],
|
||||
maxTokens: 32000,
|
||||
temperature: 0.2,
|
||||
}],
|
||||
safetyLevel: 'readonly',
|
||||
contextLevel: 'with_samples',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('"hasActiveProvider":true');
|
||||
expect(result.content).toContain('"name":"OpenAI 主账号"');
|
||||
expect(result.content).toContain('"safetyLevel":"readonly"');
|
||||
expect(result.content).toContain('"contextLevel":"with_samples"');
|
||||
expect(result.content).toContain('"enabledSkillCount":1');
|
||||
expect(result.content).toContain('"alias":"browser_open"');
|
||||
expect(result.content).toContain('"builtinToolCount":');
|
||||
});
|
||||
|
||||
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', {}),
|
||||
|
||||
@@ -2,21 +2,27 @@ import { DBGetAllColumns, DBGetDatabases, DBGetTables } from '../../../wailsjs/g
|
||||
|
||||
import type { SqlLog } from '../../store';
|
||||
import type {
|
||||
AIContextLevel,
|
||||
AIChatMessage,
|
||||
AIContextItem,
|
||||
AIMCPToolDescriptor,
|
||||
AIProviderConfig,
|
||||
AISafetyLevel,
|
||||
AISkillConfig,
|
||||
AIToolCall,
|
||||
SavedConnection,
|
||||
SavedQuery,
|
||||
SqlSnippet,
|
||||
TabData,
|
||||
} from '../../types';
|
||||
import { BUILTIN_AI_TOOL_INFO } from '../../utils/aiToolRegistry';
|
||||
import { buildRpcConnectionConfig } from '../../utils/connectionRpcConfig';
|
||||
import { buildAIReadonlyPreviewSQL } from '../../utils/aiSqlLimit';
|
||||
import { buildPaginatedSelectSQL, quoteQualifiedIdent } from '../../utils/sql';
|
||||
import { resolveAITableSchemaToolResult } from '../../utils/aiTableSchemaTool';
|
||||
import { buildAIContextSnapshot } from './aiContextInsights';
|
||||
import { buildCurrentConnectionSnapshot } from './aiConnectionInsights';
|
||||
import { buildAIRuntimeSnapshot } from './aiRuntimeInsights';
|
||||
import {
|
||||
buildSavedQueriesSnapshot,
|
||||
buildSqlSnippetsSnapshot,
|
||||
@@ -33,6 +39,13 @@ export interface AIToolContextEntry {
|
||||
tables: string[];
|
||||
}
|
||||
|
||||
interface AILocalRuntimeState {
|
||||
providers?: AIProviderConfig[];
|
||||
activeProviderId?: string;
|
||||
safetyLevel?: AISafetyLevel | string;
|
||||
contextLevel?: AIContextLevel | string;
|
||||
}
|
||||
|
||||
interface AILocalToolRuntime {
|
||||
getDatabases: (config: any) => Promise<any>;
|
||||
getTables: (config: any, dbName: string) => Promise<any>;
|
||||
@@ -45,6 +58,7 @@ interface AILocalToolRuntime {
|
||||
query: (config: any, dbName: string, sql: string) => Promise<any>;
|
||||
checkSQL?: (sql: string) => Promise<{ allowed?: boolean; operationType?: string } | undefined>;
|
||||
callMCPTool?: (name: string, args: string) => Promise<{ content?: string; isError?: boolean } | undefined>;
|
||||
getAIRuntimeState?: () => Promise<AILocalRuntimeState | undefined>;
|
||||
}
|
||||
|
||||
export interface ExecuteLocalAIToolCallOptions {
|
||||
@@ -59,6 +73,8 @@ export interface ExecuteLocalAIToolCallOptions {
|
||||
sqlLogs?: SqlLog[];
|
||||
savedQueries?: SavedQuery[];
|
||||
sqlSnippets?: SqlSnippet[];
|
||||
skills?: AISkillConfig[];
|
||||
dynamicModels?: string[];
|
||||
runtime?: Partial<AILocalToolRuntime>;
|
||||
}
|
||||
|
||||
@@ -110,8 +126,28 @@ const buildDefaultRuntime = (): AILocalToolRuntime => ({
|
||||
}
|
||||
return service.AICallMCPTool(name, args);
|
||||
},
|
||||
getAIRuntimeState: async () => {
|
||||
const service = (window as any).go?.aiservice?.Service;
|
||||
if (!service) {
|
||||
return undefined;
|
||||
}
|
||||
const [providers, activeProviderId, safetyLevel, contextLevel] = await Promise.all([
|
||||
typeof service.AIGetProviders === 'function' ? service.AIGetProviders() : Promise.resolve([]),
|
||||
typeof service.AIGetActiveProvider === 'function' ? service.AIGetActiveProvider() : Promise.resolve(''),
|
||||
typeof service.AIGetSafetyLevel === 'function' ? service.AIGetSafetyLevel() : Promise.resolve(''),
|
||||
typeof service.AIGetContextLevel === 'function' ? service.AIGetContextLevel() : Promise.resolve(''),
|
||||
]);
|
||||
return {
|
||||
providers: Array.isArray(providers) ? providers : [],
|
||||
activeProviderId: String(activeProviderId || '').trim(),
|
||||
safetyLevel: String(safetyLevel || '').trim(),
|
||||
contextLevel: String(contextLevel || '').trim(),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const BUILTIN_AI_TOOL_NAMES = BUILTIN_AI_TOOL_INFO.map((item) => item.name);
|
||||
|
||||
const normalizeTableList = (rows: any[]): string[] =>
|
||||
rows.map((row) => row.Table || row.table || (Object.values(row)[0] as string));
|
||||
|
||||
@@ -188,6 +224,8 @@ export async function executeLocalAIToolCall({
|
||||
sqlLogs = [],
|
||||
savedQueries = [],
|
||||
sqlSnippets = [],
|
||||
skills = [],
|
||||
dynamicModels = [],
|
||||
runtime,
|
||||
}: ExecuteLocalAIToolCallOptions): Promise<ExecuteLocalAIToolCallResult> {
|
||||
const mergedRuntime = { ...buildDefaultRuntime(), ...(runtime || {}) };
|
||||
@@ -198,6 +236,27 @@ export async function executeLocalAIToolCall({
|
||||
try {
|
||||
const args = JSON.parse(toolCall.function.arguments || '{}');
|
||||
switch (toolCall.function.name) {
|
||||
case 'inspect_ai_runtime': {
|
||||
try {
|
||||
const runtimeState = typeof mergedRuntime.getAIRuntimeState === 'function'
|
||||
? await mergedRuntime.getAIRuntimeState()
|
||||
: undefined;
|
||||
content = JSON.stringify(buildAIRuntimeSnapshot({
|
||||
providers: Array.isArray(runtimeState?.providers) ? runtimeState.providers : [],
|
||||
activeProviderId: runtimeState?.activeProviderId || '',
|
||||
safetyLevel: runtimeState?.safetyLevel,
|
||||
contextLevel: runtimeState?.contextLevel,
|
||||
skills,
|
||||
mcpTools,
|
||||
dynamicModels,
|
||||
builtinToolNames: BUILTIN_AI_TOOL_NAMES,
|
||||
}));
|
||||
success = true;
|
||||
} catch (error: any) {
|
||||
content = `读取当前 AI 运行状态失败: ${error?.message || error}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'inspect_current_connection': {
|
||||
try {
|
||||
content = JSON.stringify(buildCurrentConnectionSnapshot({
|
||||
|
||||
119
frontend/src/components/ai/aiRuntimeInsights.test.ts
Normal file
119
frontend/src/components/ai/aiRuntimeInsights.test.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { AIMCPToolDescriptor, AIProviderConfig, AISkillConfig } from '../../types';
|
||||
import { buildAIRuntimeSnapshot } from './aiRuntimeInsights';
|
||||
|
||||
const providers: AIProviderConfig[] = [{
|
||||
id: 'provider-1',
|
||||
type: 'openai',
|
||||
name: 'OpenAI 主账号',
|
||||
apiKey: '',
|
||||
hasSecret: true,
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
model: 'gpt-5.4',
|
||||
models: ['gpt-5.4', 'gpt-4.1'],
|
||||
maxTokens: 32000,
|
||||
temperature: 0.2,
|
||||
}];
|
||||
|
||||
const skills: AISkillConfig[] = [
|
||||
{
|
||||
id: 'skill-1',
|
||||
name: '结构审查',
|
||||
systemPrompt: '先核对字段。',
|
||||
enabled: true,
|
||||
scopes: ['database'],
|
||||
requiredTools: ['get_columns'],
|
||||
},
|
||||
{
|
||||
id: 'skill-2',
|
||||
name: '已禁用技能',
|
||||
systemPrompt: 'ignore',
|
||||
enabled: false,
|
||||
scopes: ['global'],
|
||||
},
|
||||
];
|
||||
|
||||
const mcpTools: AIMCPToolDescriptor[] = [{
|
||||
alias: 'browser_open',
|
||||
originalName: 'browser_open',
|
||||
serverId: 'server-1',
|
||||
serverName: 'browser',
|
||||
title: '打开浏览器',
|
||||
description: '打开目标页面',
|
||||
}];
|
||||
|
||||
describe('buildAIRuntimeSnapshot', () => {
|
||||
it('returns a sanitized runtime snapshot for the active provider, tools, and skills', () => {
|
||||
const snapshot = buildAIRuntimeSnapshot({
|
||||
providers,
|
||||
activeProviderId: 'provider-1',
|
||||
safetyLevel: 'readonly',
|
||||
contextLevel: 'with_samples',
|
||||
skills,
|
||||
mcpTools,
|
||||
dynamicModels: ['gpt-5.4', 'gpt-4.1-mini'],
|
||||
builtinToolNames: ['inspect_ai_runtime', 'get_columns', 'inspect_current_connection'],
|
||||
});
|
||||
|
||||
expect(snapshot).toMatchObject({
|
||||
hasActiveProvider: true,
|
||||
providerCount: 1,
|
||||
safetyLevel: 'readonly',
|
||||
safetyLabel: '只读',
|
||||
contextLevel: 'with_samples',
|
||||
contextLabel: '结构+样例',
|
||||
dynamicModelCount: 2,
|
||||
enabledSkillCount: 1,
|
||||
builtinToolCount: 3,
|
||||
mcpToolCount: 1,
|
||||
totalAvailableToolCount: 4,
|
||||
capabilities: {
|
||||
canWriteData: false,
|
||||
canUseSampleContext: true,
|
||||
hasExternalMCPTools: true,
|
||||
hasCustomSkills: true,
|
||||
},
|
||||
});
|
||||
expect(snapshot.activeProvider).toMatchObject({
|
||||
id: 'provider-1',
|
||||
name: 'OpenAI 主账号',
|
||||
model: 'gpt-5.4',
|
||||
hasSecret: true,
|
||||
});
|
||||
expect(JSON.stringify(snapshot)).not.toContain('apiKey');
|
||||
expect(snapshot.enabledSkills).toEqual([
|
||||
{
|
||||
id: 'skill-1',
|
||||
name: '结构审查',
|
||||
scopes: ['database'],
|
||||
requiredTools: ['get_columns'],
|
||||
},
|
||||
]);
|
||||
expect(snapshot.mcpTools).toEqual([
|
||||
{
|
||||
alias: 'browser_open',
|
||||
title: '打开浏览器',
|
||||
serverName: 'browser',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns a clear empty state when no provider is active', () => {
|
||||
const snapshot = buildAIRuntimeSnapshot({
|
||||
providers: [],
|
||||
activeProviderId: '',
|
||||
safetyLevel: 'readonly',
|
||||
contextLevel: 'schema_only',
|
||||
skills: [],
|
||||
mcpTools: [],
|
||||
builtinToolNames: [],
|
||||
});
|
||||
|
||||
expect(snapshot).toMatchObject({
|
||||
hasActiveProvider: false,
|
||||
activeProvider: null,
|
||||
message: '当前未启用 AI 供应商',
|
||||
});
|
||||
});
|
||||
});
|
||||
140
frontend/src/components/ai/aiRuntimeInsights.ts
Normal file
140
frontend/src/components/ai/aiRuntimeInsights.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import type {
|
||||
AIContextLevel,
|
||||
AIMCPToolDescriptor,
|
||||
AIProviderConfig,
|
||||
AISafetyLevel,
|
||||
AISkillConfig,
|
||||
} from '../../types';
|
||||
|
||||
const SAFETY_LEVEL_LABELS: Record<string, string> = {
|
||||
readonly: '只读',
|
||||
readwrite: '读写',
|
||||
full: '完全开放',
|
||||
};
|
||||
|
||||
const CONTEXT_LEVEL_LABELS: Record<string, string> = {
|
||||
schema_only: '仅结构',
|
||||
with_samples: '结构+样例',
|
||||
with_results: '结构+结果',
|
||||
};
|
||||
|
||||
const BUILTIN_TOOL_PREVIEW_LIMIT = 30;
|
||||
const MCP_TOOL_PREVIEW_LIMIT = 40;
|
||||
const DYNAMIC_MODEL_PREVIEW_LIMIT = 20;
|
||||
|
||||
const sliceList = <T,>(items: T[], limit: number) => {
|
||||
const list = Array.isArray(items) ? items : [];
|
||||
return {
|
||||
items: list.slice(0, limit),
|
||||
truncated: list.length > limit,
|
||||
total: list.length,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeSafetyLevel = (value: AISafetyLevel | string | undefined): string =>
|
||||
String(value || 'unknown').trim() || 'unknown';
|
||||
|
||||
const normalizeContextLevel = (value: AIContextLevel | string | undefined): string =>
|
||||
String(value || 'unknown').trim() || 'unknown';
|
||||
|
||||
export const buildAIRuntimeSnapshot = (params: {
|
||||
providers?: AIProviderConfig[];
|
||||
activeProviderId?: string | null;
|
||||
safetyLevel?: AISafetyLevel | string;
|
||||
contextLevel?: AIContextLevel | string;
|
||||
skills?: AISkillConfig[];
|
||||
mcpTools?: AIMCPToolDescriptor[];
|
||||
dynamicModels?: string[];
|
||||
builtinToolNames?: string[];
|
||||
}) => {
|
||||
const {
|
||||
providers = [],
|
||||
activeProviderId = '',
|
||||
safetyLevel,
|
||||
contextLevel,
|
||||
skills = [],
|
||||
mcpTools = [],
|
||||
dynamicModels = [],
|
||||
builtinToolNames = [],
|
||||
} = params;
|
||||
|
||||
const activeProvider = providers.find((provider) => provider.id === activeProviderId) || null;
|
||||
const enabledSkills = skills.filter((skill) => skill?.enabled);
|
||||
const builtinPreview = sliceList(
|
||||
builtinToolNames
|
||||
.map((name) => String(name || '').trim())
|
||||
.filter(Boolean)
|
||||
.sort((left, right) => left.localeCompare(right)),
|
||||
BUILTIN_TOOL_PREVIEW_LIMIT,
|
||||
);
|
||||
const mcpPreview = sliceList(
|
||||
mcpTools.map((tool) => ({
|
||||
alias: tool.alias,
|
||||
title: tool.title || tool.originalName || tool.alias,
|
||||
serverName: tool.serverName,
|
||||
})),
|
||||
MCP_TOOL_PREVIEW_LIMIT,
|
||||
);
|
||||
const dynamicModelPreview = sliceList(
|
||||
dynamicModels
|
||||
.map((model) => String(model || '').trim())
|
||||
.filter(Boolean),
|
||||
DYNAMIC_MODEL_PREVIEW_LIMIT,
|
||||
);
|
||||
const normalizedSafetyLevel = normalizeSafetyLevel(safetyLevel);
|
||||
const normalizedContextLevel = normalizeContextLevel(contextLevel);
|
||||
|
||||
return {
|
||||
hasActiveProvider: Boolean(activeProvider),
|
||||
activeProvider: activeProvider ? {
|
||||
id: activeProvider.id,
|
||||
name: activeProvider.name,
|
||||
type: activeProvider.type,
|
||||
apiFormat: activeProvider.apiFormat || 'openai',
|
||||
model: activeProvider.model || '',
|
||||
baseUrl: activeProvider.baseUrl || '',
|
||||
hasSecret: activeProvider.hasSecret ?? Boolean(activeProvider.secretRef || activeProvider.apiKey),
|
||||
declaredModelCount: Array.isArray(activeProvider.models) ? activeProvider.models.length : 0,
|
||||
} : null,
|
||||
providerCount: providers.length,
|
||||
providers: providers.map((provider) => ({
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
type: provider.type,
|
||||
active: provider.id === activeProviderId,
|
||||
model: provider.model || '',
|
||||
})),
|
||||
safetyLevel: normalizedSafetyLevel,
|
||||
safetyLabel: SAFETY_LEVEL_LABELS[normalizedSafetyLevel] || normalizedSafetyLevel,
|
||||
contextLevel: normalizedContextLevel,
|
||||
contextLabel: CONTEXT_LEVEL_LABELS[normalizedContextLevel] || normalizedContextLevel,
|
||||
dynamicModelCount: dynamicModelPreview.total,
|
||||
dynamicModels: dynamicModelPreview.items,
|
||||
dynamicModelsTruncated: dynamicModelPreview.truncated,
|
||||
enabledSkillCount: enabledSkills.length,
|
||||
enabledSkills: enabledSkills.map((skill) => ({
|
||||
id: skill.id,
|
||||
name: skill.name,
|
||||
scopes: Array.isArray(skill.scopes) ? skill.scopes : [],
|
||||
requiredTools: Array.isArray(skill.requiredTools) ? skill.requiredTools : [],
|
||||
})),
|
||||
builtinToolCount: builtinPreview.total,
|
||||
builtinTools: builtinPreview.items,
|
||||
builtinToolsTruncated: builtinPreview.truncated,
|
||||
mcpToolCount: mcpPreview.total,
|
||||
mcpTools: mcpPreview.items,
|
||||
mcpToolsTruncated: mcpPreview.truncated,
|
||||
totalAvailableToolCount: builtinPreview.total + mcpPreview.total,
|
||||
capabilities: {
|
||||
canWriteData: normalizedSafetyLevel !== 'readonly',
|
||||
canUseSampleContext: normalizedContextLevel === 'with_samples' || normalizedContextLevel === 'with_results',
|
||||
canUseResultContext: normalizedContextLevel === 'with_results',
|
||||
hasExternalMCPTools: mcpPreview.total > 0,
|
||||
hasCustomSkills: enabledSkills.length > 0,
|
||||
hasDynamicModelsLoaded: dynamicModelPreview.total > 0,
|
||||
},
|
||||
message: activeProvider
|
||||
? `当前 AI 正在使用 ${activeProvider.name || activeProvider.id},共暴露 ${builtinPreview.total + mcpPreview.total} 个工具`
|
||||
: '当前未启用 AI 供应商',
|
||||
};
|
||||
};
|
||||
@@ -68,13 +68,14 @@ describe('buildAISystemContextMessages', () => {
|
||||
connections: [connections[0]],
|
||||
tabs: [],
|
||||
activeTabId: null,
|
||||
availableToolNames: ['inspect_workspace_tabs', 'inspect_ai_context', 'inspect_current_connection', 'inspect_saved_queries', 'inspect_sql_snippets', 'get_columns'],
|
||||
availableToolNames: ['inspect_workspace_tabs', 'inspect_ai_runtime', 'inspect_ai_context', 'inspect_current_connection', 'inspect_saved_queries', 'inspect_sql_snippets', 'get_columns'],
|
||||
skills,
|
||||
userPromptSettings,
|
||||
});
|
||||
|
||||
const joined = messages.map((message) => message.content).join('\n');
|
||||
expect(joined).toContain('inspect_workspace_tabs 盘点当前工作区');
|
||||
expect(joined).toContain('inspect_ai_runtime 读取当前 AI 运行状态');
|
||||
expect(joined).toContain('inspect_ai_context 读取当前挂载的表结构上下文');
|
||||
expect(joined).toContain('inspect_current_connection');
|
||||
expect(joined).toContain('inspect_saved_queries');
|
||||
|
||||
@@ -95,6 +95,19 @@ const appendSkillPromptGroup = (
|
||||
});
|
||||
};
|
||||
|
||||
const appendAIRuntimeInspectionGuidance = (
|
||||
messages: AISystemContextMessage[],
|
||||
availableToolNames: string[],
|
||||
) => {
|
||||
if (!availableToolNames.includes('inspect_ai_runtime')) {
|
||||
return;
|
||||
}
|
||||
messages.push({
|
||||
role: 'system',
|
||||
content: '如果用户提到“你现在用的哪个模型”“当前安全级别”“你现在能调用什么工具”“当前启用了哪些 skills / MCP 工具”,优先调用 inspect_ai_runtime 读取当前 AI 运行状态,不要凭记忆或假设回答。',
|
||||
});
|
||||
};
|
||||
|
||||
const resolveDatabaseDisplayType = (config: ConnectionConfig | undefined): string => {
|
||||
const dbType = config?.type || 'unknown';
|
||||
return dbType === 'diros' ? 'Doris' : dbType.charAt(0).toUpperCase() + dbType.slice(1);
|
||||
@@ -205,6 +218,7 @@ export function buildAISystemContextMessages({
|
||||
6. expectedSignals 必须是字符串数组,描述执行后需要重点观察的信号。
|
||||
7. 如果命令权限不允许某类操作,就不要输出该类命令;无法满足时直接说明限制。`,
|
||||
});
|
||||
appendAIRuntimeInspectionGuidance(systemMessages, availableToolNames);
|
||||
appendCustomPromptGroup(systemMessages, ['jvmDiagnostic'], userPromptSettings);
|
||||
appendSkillPromptGroup(systemMessages, ['jvmDiagnostic'], skills, availableToolNames);
|
||||
return systemMessages;
|
||||
@@ -238,6 +252,7 @@ ${resourcePath ? `当前资源路径:${resourcePath}` : '当前未选中具体
|
||||
5. payload 只能使用 {"format":"json","value":{...}} 或 {"format":"text","value":"..."} 这两种包装形式,不要输出脚本、命令或裸值。
|
||||
6. 不要输出脚本、命令或“已经执行成功”之类的表述。`,
|
||||
});
|
||||
appendAIRuntimeInspectionGuidance(systemMessages, availableToolNames);
|
||||
appendCustomPromptGroup(systemMessages, ['jvm'], userPromptSettings);
|
||||
appendSkillPromptGroup(systemMessages, ['jvm'], skills, availableToolNames);
|
||||
return systemMessages;
|
||||
@@ -309,6 +324,7 @@ SELECT * FROM users WHERE status = 1;
|
||||
content: '如果用户提到“当前 AI 上下文”“当前关联了哪些表”“现在带了哪些表结构”,优先调用 inspect_ai_context 读取当前挂载的表结构上下文,不要凭记忆复述。',
|
||||
});
|
||||
}
|
||||
appendAIRuntimeInspectionGuidance(systemMessages, availableToolNames);
|
||||
if (availableToolNames.includes('inspect_current_connection')) {
|
||||
systemMessages.push({
|
||||
role: 'system',
|
||||
|
||||
@@ -23,6 +23,7 @@ interface AIToolCallingBlockProps {
|
||||
}
|
||||
|
||||
const TOOL_ACTION_LABELS: Record<string, string> = {
|
||||
inspect_ai_runtime: '读取当前 AI 运行状态',
|
||||
get_connections: '获取可用连接信息',
|
||||
get_databases: '扫描数据库列表',
|
||||
get_tables: '分析表结构信息',
|
||||
|
||||
@@ -3,6 +3,13 @@ import { describe, expect, it } from 'vitest';
|
||||
import { BUILTIN_AI_TOOL_INFO, buildAvailableAIChatTools } from './aiToolRegistry';
|
||||
|
||||
describe('aiToolRegistry', () => {
|
||||
it('registers the ai-runtime inspector as a builtin tool', () => {
|
||||
const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_runtime');
|
||||
expect(info).toBeTruthy();
|
||||
expect(info?.desc).toContain('AI 自身运行状态');
|
||||
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();
|
||||
@@ -36,6 +43,7 @@ describe('aiToolRegistry', () => {
|
||||
},
|
||||
}]);
|
||||
|
||||
expect(tools.some((item) => item.function.name === 'inspect_ai_runtime')).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);
|
||||
|
||||
@@ -309,6 +309,23 @@ export const BUILTIN_AI_TOOL_INFO: AIBuiltinToolInfo[] = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inspect_ai_runtime",
|
||||
icon: "🎛️",
|
||||
desc: "查看当前 AI 自身运行状态",
|
||||
detail:
|
||||
"返回当前启用的模型供应商、模型名、安全级别、上下文级别、启用的 Skills,以及当前已暴露的内置工具和 MCP 工具。适合用户问“你现在能调用什么”“当前用的哪个模型”“为什么不能执行写操作”时,先读真实运行状态再回答。",
|
||||
params: "无参数",
|
||||
tool: {
|
||||
type: "function",
|
||||
function: {
|
||||
name: "inspect_ai_runtime",
|
||||
description:
|
||||
"读取当前 AI 运行时快照,包括当前供应商、模型、安全级别、上下文级别、启用的 Skills、当前可用的内置工具与 MCP 工具。适用于用户询问当前 AI 能力边界、当前使用哪个模型、为什么不能执行某些操作时,先读取真实运行状态,避免模型猜测。",
|
||||
parameters: { type: "object", properties: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inspect_ai_context",
|
||||
icon: "🧷",
|
||||
|
||||
Reference in New Issue
Block a user