From 8ddd8a726dbf0a02f02d96f130efaefa586ac327 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Wed, 10 Jun 2026 12:59:09 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(ai):=20=E6=96=B0=E5=A2=9E=20AI?= =?UTF-8?q?=20=E6=B6=88=E6=81=AF=E6=B5=81=E8=AF=8A=E6=96=AD=E6=8E=A2?= =?UTF-8?q?=E9=92=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 inspect_ai_message_flow 内置工具 - 识别连续 assistant 气泡、空消息和未闭环工具调用 - 同步工具目录、系统引导、执行状态文案和回归测试 --- .../ai/AIBuiltinToolsCatalog.test.tsx | 2 + .../components/ai/AIBuiltinToolsCatalog.tsx | 9 +- .../ai/aiChatSessionInsights.test.ts | 43 +++++- .../components/ai/aiChatSessionInsights.ts | 136 ++++++++++++++++++ ...ToolExecutor.localAssetsInspection.test.ts | 38 +++++ .../ai/aiSnapshotInspectionToolExecutor.ts | 19 ++- .../ai/aiSystemContextMessages.test.ts | 3 +- .../ai/aiSystemInspectionGuidance.ts | 6 + .../messageBubble/AIMessageStatusBlocks.tsx | 1 + .../src/utils/aiBuiltinInspectionToolInfo.ts | 25 ++++ frontend/src/utils/aiToolRegistry.test.ts | 11 ++ 11 files changed, 288 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx b/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx index 8ef364d..0a340b1 100644 --- a/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx +++ b/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx @@ -75,6 +75,8 @@ describe('AIBuiltinToolsCatalog', () => { expect(markup).toContain('inspect_recent_connection_failures'); expect(markup).toContain('排查 AI 气泡渲染异常'); expect(markup).toContain('inspect_ai_last_render_error'); + expect(markup).toContain('诊断 AI 消息流'); + expect(markup).toContain('inspect_ai_message_flow'); expect(markup).toContain('复用历史 SQL'); expect(markup).toContain('inspect_saved_queries'); expect(markup).toContain('回看 AI 历史对话'); diff --git a/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx b/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx index 201189e..b144723 100644 --- a/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx +++ b/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx @@ -42,8 +42,8 @@ const BUILTIN_TOOL_FLOWS = [ }, { title: 'AI 应用健康总览', - steps: 'inspect_app_health → inspect_ai_setup_health / inspect_app_logs / inspect_recent_connection_failures / inspect_ai_last_render_error', - description: '适合用户反馈 AI 不稳定、连接和 MCP 问题交织、回复气泡显示异常,或需要先看整体健康状态时,一次汇总配置、日志、连接失败、渲染异常和工作区现场。', + steps: 'inspect_app_health → inspect_ai_setup_health / inspect_app_logs / inspect_recent_connection_failures / inspect_ai_last_render_error / inspect_ai_message_flow', + description: '适合用户反馈 AI 不稳定、连接和 MCP 问题交织、回复气泡显示异常,或需要先看整体健康状态时,一次汇总配置、日志、连接失败、渲染异常、消息流和工作区现场。', }, { title: '一键体检 AI 配置', @@ -160,6 +160,11 @@ const BUILTIN_TOOL_FLOWS = [ steps: 'inspect_ai_last_render_error → inspect_active_tab / inspect_ai_runtime', description: '适合用户反馈 AI 某条消息空白、气泡局部报错但整个面板没挂时,先拿到最近一次被隔离的渲染异常快照,再回到具体会话和运行时上下文继续缩小范围。', }, + { + title: '诊断 AI 消息流', + steps: 'inspect_ai_message_flow → inspect_ai_last_render_error / inspect_app_logs', + description: '适合用户反馈回复被拆成多个气泡、工具调用后没继续回答、消息流状态不对时,先读取当前会话的真实消息结构和异常信号。', + }, { title: '复用历史 SQL', steps: 'inspect_saved_queries → get_columns / execute_sql', diff --git a/frontend/src/components/ai/aiChatSessionInsights.test.ts b/frontend/src/components/ai/aiChatSessionInsights.test.ts index bd8899b..ee71723 100644 --- a/frontend/src/components/ai/aiChatSessionInsights.test.ts +++ b/frontend/src/components/ai/aiChatSessionInsights.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { buildAIChatSessionsSnapshot } from './aiChatSessionInsights'; +import { + buildAIChatSessionsSnapshot, + buildAIMessageFlowSnapshot, +} from './aiChatSessionInsights'; describe('aiChatSessionInsights', () => { it('filters and summarizes ai sessions with previews from local history', () => { @@ -34,4 +37,42 @@ describe('aiChatSessionInsights', () => { latestMessagePreview: '先检查支付回调日志', }); }); + + it('diagnoses active ai message flow anomalies', () => { + const snapshot = buildAIMessageFlowSnapshot({ + aiChatSessions: [ + { id: 'session-1', title: '气泡异常排查', updatedAt: 300 }, + ], + aiChatHistory: { + 'session-1': [ + { id: 'msg-1', role: 'user', content: '为什么回复变成多个气泡', timestamp: 101 }, + { + id: 'msg-2', + role: 'assistant', + content: '我先检查消息流', + timestamp: 102, + tool_calls: [{ id: 'tool-1', type: 'function', function: { name: 'inspect_ai_runtime', arguments: '{}' } }], + }, + { id: 'msg-3', role: 'assistant', content: '这里被拆成了第二个气泡', timestamp: 103 }, + { id: 'msg-4', role: 'assistant', content: '', timestamp: 104 }, + ], + }, + activeSessionId: 'session-1', + limit: 10, + }); + + expect(snapshot.found).toBe(true); + expect(snapshot.title).toBe('气泡异常排查'); + expect(snapshot.totalMessages).toBe(4); + expect(snapshot.unresolvedToolCallCount).toBe(1); + expect(snapshot.consecutiveAssistantPairCount).toBe(2); + expect(snapshot.emptyAssistantMessageCount).toBe(1); + expect(snapshot.warnings).toContain('有 1 个工具调用没有匹配到 tool 结果消息'); + expect(snapshot.nextActions).toContain('检查流式追加逻辑是否复用了同一个 assistantMsgId,而不是为同一轮回复新建 assistant 消息'); + expect(snapshot.messages[1]).toMatchObject({ + id: 'msg-2', + toolCallNames: ['inspect_ai_runtime'], + toolCallIds: ['tool-1'], + }); + }); }); diff --git a/frontend/src/components/ai/aiChatSessionInsights.ts b/frontend/src/components/ai/aiChatSessionInsights.ts index 3375197..e186234 100644 --- a/frontend/src/components/ai/aiChatSessionInsights.ts +++ b/frontend/src/components/ai/aiChatSessionInsights.ts @@ -7,6 +7,7 @@ interface AIChatSessionMeta { } const AI_CHAT_SESSION_PREVIEW_LIMIT = 240; +const AI_MESSAGE_FLOW_PREVIEW_LIMIT = 180; const normalizeLimit = (input: unknown, fallback: number, max: number): number => { const value = Math.floor(Number(input) || fallback); @@ -134,3 +135,138 @@ export const buildAIChatSessionsSnapshot = (params: { sessions: visibleSessions, }; }; + +const buildMessagePreview = (message: AIChatMessage, previewLimit: number): string => { + const raw = String(message.content || message.reasoning_content || '').trim(); + return raw.slice(0, previewLimit); +}; + +const getToolCallNames = (message: AIChatMessage): string[] => ( + (message.tool_calls || []) + .map((toolCall) => String(toolCall?.function?.name || '').trim()) + .filter(Boolean) +); + +export const buildAIMessageFlowSnapshot = (params: { + aiChatSessions?: AIChatSessionMeta[]; + aiChatHistory?: Record; + activeSessionId?: string | null; + sessionId?: unknown; + limit?: unknown; + includeContent?: unknown; + previewLimit?: unknown; +}) => { + const { + aiChatSessions = [], + aiChatHistory = {}, + activeSessionId = null, + sessionId, + limit, + includeContent = true, + previewLimit, + } = params; + + const requestedSessionId = String(sessionId || activeSessionId || '').trim(); + const safeLimit = normalizeLimit(limit, 24, 80); + const safePreviewLimit = normalizeLimit(previewLimit, AI_MESSAGE_FLOW_PREVIEW_LIMIT, 1000); + const shouldIncludeContent = includeContent !== false; + const sessionMetaMap = new Map(aiChatSessions.map((session) => [session.id, session])); + const messages = requestedSessionId + ? [...(aiChatHistory[requestedSessionId] || [])].sort((left, right) => left.timestamp - right.timestamp) + : []; + const toolResultsByCallId = new Map( + messages + .filter((message) => message.role === 'tool' && message.tool_call_id) + .map((message) => [String(message.tool_call_id), message]), + ); + + const assistantMessages = messages.filter((message) => message.role === 'assistant'); + const toolCallMessages = assistantMessages.filter((message) => (message.tool_calls || []).length > 0); + const unresolvedToolCalls = toolCallMessages.flatMap((message) => + (message.tool_calls || []) + .filter((toolCall) => !toolResultsByCallId.has(toolCall.id)) + .map((toolCall) => ({ + assistantMessageId: message.id, + toolCallId: toolCall.id, + toolName: toolCall.function?.name || '', + })), + ); + const emptyAssistantMessages = assistantMessages.filter((message) => + !String(message.content || '').trim() + && !String(message.reasoning_content || '').trim() + && !(message.tool_calls || []).length + && !message.loading, + ); + + const consecutiveAssistantPairs: Array<{ previousMessageId: string; nextMessageId: string }> = []; + for (let index = 1; index < messages.length; index += 1) { + if (messages[index - 1]?.role === 'assistant' && messages[index]?.role === 'assistant') { + consecutiveAssistantPairs.push({ + previousMessageId: messages[index - 1].id, + nextMessageId: messages[index].id, + }); + } + } + + const warnings = [ + unresolvedToolCalls.length > 0 ? `有 ${unresolvedToolCalls.length} 个工具调用没有匹配到 tool 结果消息` : '', + consecutiveAssistantPairs.length > 0 ? `发现 ${consecutiveAssistantPairs.length} 组连续 assistant 消息,可能存在回复被拆成多个气泡` : '', + emptyAssistantMessages.length > 0 ? `发现 ${emptyAssistantMessages.length} 条空 assistant 消息` : '', + messages.some((message) => message.loading) ? '会话中仍有 loading 消息,可能还在流式生成或上次中断未清理' : '', + ].filter(Boolean); + + const nextActions = [ + unresolvedToolCalls.length > 0 ? '优先核对 useAIChatLocalTools 是否为每个 tool_call_id 写入 tool 消息' : '', + consecutiveAssistantPairs.length > 0 ? '检查流式追加逻辑是否复用了同一个 assistantMsgId,而不是为同一轮回复新建 assistant 消息' : '', + emptyAssistantMessages.length > 0 ? '检查异常或取消路径是否留下了空 assistant 占位消息' : '', + warnings.length === 0 ? '消息流未发现明显结构异常,可继续结合 inspect_ai_last_render_error 或 inspect_app_logs 排查渲染/运行时问题' : '', + ].filter(Boolean); + + const recentMessages = messages.slice(-safeLimit).map((message) => { + const preview = shouldIncludeContent ? buildMessagePreview(message, safePreviewLimit) : ''; + const toolCallNames = getToolCallNames(message); + return { + id: message.id, + role: message.role, + phase: message.phase || '', + timestamp: message.timestamp, + loading: Boolean(message.loading), + contentLength: String(message.content || '').length, + reasoningLength: String(message.reasoning_content || '').length, + preview, + previewTruncated: shouldIncludeContent + && String(message.content || message.reasoning_content || '').trim().length > preview.length, + toolCallCount: (message.tool_calls || []).length, + toolCallNames, + toolCallIds: (message.tool_calls || []).map((toolCall) => toolCall.id), + toolCallId: message.tool_call_id || '', + toolName: message.tool_name || '', + success: message.success, + }; + }); + + const meta = requestedSessionId ? sessionMetaMap.get(requestedSessionId) : undefined; + return { + activeSessionId: activeSessionId || '', + requestedSessionId, + found: Boolean(requestedSessionId && (messages.length > 0 || meta)), + title: String(meta?.title || '').trim(), + updatedAt: Number(meta?.updatedAt || messages[messages.length - 1]?.timestamp || 0), + totalMessages: messages.length, + returnedMessages: recentMessages.length, + truncated: messages.length > recentMessages.length, + userMessageCount: messages.filter((message) => message.role === 'user').length, + assistantMessageCount: assistantMessages.length, + toolMessageCount: messages.filter((message) => message.role === 'tool').length, + systemMessageCount: messages.filter((message) => message.role === 'system').length, + assistantToolCallMessageCount: toolCallMessages.length, + unresolvedToolCallCount: unresolvedToolCalls.length, + emptyAssistantMessageCount: emptyAssistantMessages.length, + consecutiveAssistantPairCount: consecutiveAssistantPairs.length, + unresolvedToolCalls, + consecutiveAssistantPairs, + warnings, + nextActions, + messages: recentMessages, + }; +}; diff --git a/frontend/src/components/ai/aiLocalToolExecutor.localAssetsInspection.test.ts b/frontend/src/components/ai/aiLocalToolExecutor.localAssetsInspection.test.ts index 59a73f1..a874a28 100644 --- a/frontend/src/components/ai/aiLocalToolExecutor.localAssetsInspection.test.ts +++ b/frontend/src/components/ai/aiLocalToolExecutor.localAssetsInspection.test.ts @@ -101,6 +101,44 @@ describe('aiLocalToolExecutor local asset inspection tools', () => { expect(result.content).not.toContain('列出最近注册用户'); }); + it('returns ai message flow diagnostics for the active session', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_ai_message_flow', { + limit: 8, + }), + connections: [buildConnection()], + mcpTools: [], + toolContextMap: new Map(), + aiChatSessions: [ + { id: 'session-1', title: '消息流异常', updatedAt: 200 }, + ], + aiChatHistory: { + 'session-1': [ + { id: 'msg-1', role: 'user', content: 'AI 回复拆成多个气泡', timestamp: 101 }, + { + id: 'msg-2', + role: 'assistant', + content: '先调用探针', + timestamp: 102, + tool_calls: [{ id: 'tool-1', type: 'function', function: { name: 'inspect_ai_runtime', arguments: '{}' } }], + }, + { id: 'msg-3', role: 'assistant', content: '继续回答', timestamp: 103 }, + ], + }, + activeSessionId: 'session-1', + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"requestedSessionId":"session-1"'); + expect(result.content).toContain('"unresolvedToolCallCount":1'); + expect(result.content).toContain('"consecutiveAssistantPairCount":1'); + expect(result.content).toContain('回复拆成多个气泡'); + }); + it('returns sql snippets so the model can inspect local query templates', async () => { const result = await executeLocalAIToolCall({ toolCall: buildToolCall('inspect_sql_snippets', { diff --git a/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts b/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts index 66f2fa1..827ccf5 100644 --- a/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts +++ b/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts @@ -12,7 +12,10 @@ import type { } from '../../types'; import type { SqlLog } from '../../store'; import { buildAIContextSnapshot } from './aiContextInsights'; -import { buildAIChatSessionsSnapshot } from './aiChatSessionInsights'; +import { + buildAIChatSessionsSnapshot, + buildAIMessageFlowSnapshot, +} from './aiChatSessionInsights'; import { buildConnectionCapabilitiesSnapshot } from './aiConnectionCapabilitiesInsights'; import { buildCurrentConnectionSnapshot } from './aiConnectionInsights'; import { @@ -262,6 +265,19 @@ export async function executeSnapshotInspectionToolCall( })), success: true, }; + case 'inspect_ai_message_flow': + return { + content: JSON.stringify(buildAIMessageFlowSnapshot({ + aiChatSessions, + aiChatHistory, + activeSessionId, + sessionId: args.sessionId, + limit: args.limit, + includeContent: args.includeContent !== false, + previewLimit: args.previewLimit, + })), + success: true, + }; case 'inspect_recent_sql_logs': return { content: JSON.stringify(buildRecentSqlLogsSnapshot({ @@ -390,6 +406,7 @@ export async function executeSnapshotInspectionToolCall( inspect_app_logs: '读取 GoNavi 应用日志失败', inspect_recent_connection_failures: '汇总最近连接失败记录失败', inspect_ai_last_render_error: '读取最近一次 AI 渲染异常失败', + inspect_ai_message_flow: '读取 AI 消息流诊断失败', inspect_saved_queries: '读取已保存查询失败', inspect_sql_snippets: '读取 SQL 片段失败', inspect_shortcuts: '读取快捷键配置失败', diff --git a/frontend/src/components/ai/aiSystemContextMessages.test.ts b/frontend/src/components/ai/aiSystemContextMessages.test.ts index d77e5ec..a894ecf 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_app_health', 'inspect_ai_setup_health', 'inspect_ai_runtime', 'inspect_ai_safety', 'inspect_ai_providers', 'inspect_ai_chat_readiness', 'inspect_mcp_setup', 'inspect_mcp_authoring_guide', 'inspect_ai_guidance', 'inspect_ai_context', 'inspect_current_connection', 'inspect_connection_capabilities', 'inspect_saved_connections', 'inspect_external_sql_directories', 'inspect_external_sql_file', 'inspect_recent_sql_activity', 'inspect_sql_risk', 'inspect_recent_connection_failures', 'inspect_app_logs', 'inspect_ai_last_render_error', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'inspect_shortcuts', 'get_columns'], + availableToolNames: ['inspect_workspace_tabs', 'inspect_app_health', 'inspect_ai_setup_health', 'inspect_ai_runtime', 'inspect_ai_safety', 'inspect_ai_providers', 'inspect_ai_chat_readiness', 'inspect_mcp_setup', 'inspect_mcp_authoring_guide', 'inspect_ai_guidance', 'inspect_ai_context', 'inspect_current_connection', 'inspect_connection_capabilities', 'inspect_saved_connections', 'inspect_external_sql_directories', 'inspect_external_sql_file', 'inspect_recent_sql_activity', 'inspect_sql_risk', 'inspect_recent_connection_failures', 'inspect_app_logs', 'inspect_ai_last_render_error', 'inspect_ai_message_flow', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'inspect_shortcuts', 'get_columns'], skills, userPromptSettings, }); @@ -95,6 +95,7 @@ describe('buildAISystemContextMessages', () => { expect(joined).toContain('inspect_recent_connection_failures 读取真实连接失败总结'); expect(joined).toContain('inspect_app_logs 读取真实应用日志尾部'); expect(joined).toContain('inspect_ai_last_render_error 读取最近一次被隔离的前端渲染异常记录'); + expect(joined).toContain('inspect_ai_message_flow 读取当前会话的真实消息结构'); expect(joined).toContain('inspect_saved_queries'); expect(joined).toContain('inspect_ai_sessions'); expect(joined).toContain('inspect_sql_snippets'); diff --git a/frontend/src/components/ai/aiSystemInspectionGuidance.ts b/frontend/src/components/ai/aiSystemInspectionGuidance.ts index 6352c45..8fce3a4 100644 --- a/frontend/src/components/ai/aiSystemInspectionGuidance.ts +++ b/frontend/src/components/ai/aiSystemInspectionGuidance.ts @@ -122,6 +122,12 @@ export const appendDatabaseInspectionGuidanceMessages = ( 'inspect_ai_last_render_error', '如果用户提到“AI 某条消息空白了”“某个气泡渲染失败”“消息块局部报错但面板没全挂”,优先调用 inspect_ai_last_render_error 读取最近一次被隔离的前端渲染异常记录,不要只凭截图现象猜测。', ); + appendGuidanceIfToolAvailable( + messages, + availableToolNames, + 'inspect_ai_message_flow', + '如果用户提到“AI 回复被拆成多个气泡”“工具调用后没继续回答”“消息流状态不对”“同一轮回答没有追加到同一个气泡”,优先调用 inspect_ai_message_flow 读取当前会话的真实消息结构、连续 assistant 消息和未闭环工具调用,不要只凭界面现象猜测。', + ); appendGuidanceIfToolAvailable( messages, availableToolNames, diff --git a/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx b/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx index 5999111..e94e88b 100644 --- a/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx +++ b/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx @@ -54,6 +54,7 @@ const TOOL_ACTION_LABELS: Record = { inspect_app_logs: '回看 GoNavi 应用日志', inspect_recent_connection_failures: '总结最近连接失败记录', inspect_ai_last_render_error: '读取最近一次 AI 渲染异常', + inspect_ai_message_flow: '诊断当前 AI 消息流', inspect_saved_queries: '检索本地已保存查询', inspect_sql_snippets: '读取 SQL 片段模板', inspect_shortcuts: '读取当前快捷键配置', diff --git a/frontend/src/utils/aiBuiltinInspectionToolInfo.ts b/frontend/src/utils/aiBuiltinInspectionToolInfo.ts index 6855475..ca35cad 100644 --- a/frontend/src/utils/aiBuiltinInspectionToolInfo.ts +++ b/frontend/src/utils/aiBuiltinInspectionToolInfo.ts @@ -539,6 +539,31 @@ export const BUILTIN_AI_INSPECTION_TOOL_INFO: AIBuiltinToolInfo[] = [ }, }, }, + { + name: "inspect_ai_message_flow", + icon: "🧬", + desc: "诊断当前 AI 会话消息流", + detail: + "读取当前或指定 AI 会话的最近消息流,统计用户/助手/tool 消息、工具调用是否都有结果、是否出现连续 assistant 气泡、空 assistant 占位或未清理 loading。适合用户反馈“AI 回复被拆成多个气泡”“工具调用后没继续回答”“消息流看着不对”时先看真实消息结构。", + params: "sessionId?(默认当前会话), limit?(默认 24), includeContent?(默认 true), previewLimit?(默认 180)", + tool: { + type: "function", + function: { + name: "inspect_ai_message_flow", + description: + "读取当前或指定 AI 会话的最近消息流诊断,包括消息角色序列、assistant/tool 消息数量、工具调用与 tool 结果匹配情况、连续 assistant 消息、空 assistant 消息和 loading 残留。适用于用户提到 AI 回复被拆成多个气泡、流式追加异常、工具调用没有闭环、某轮回答没有继续生成时,先读取真实消息结构再定位。", + parameters: { + type: "object", + properties: { + sessionId: { type: "string", description: "可选,指定要诊断的 AI 会话 ID;不传时读取当前活动会话" }, + limit: { type: "number", description: "可选,最多返回最近多少条消息,默认 24,最大 80" }, + includeContent: { type: "boolean", description: "可选,是否附带消息内容预览,默认 true" }, + previewLimit: { type: "number", description: "可选,每条消息预览字符数,默认 180,最大 1000" }, + }, + }, + }, + }, + }, { name: "inspect_sql_snippets", icon: "🧩", diff --git a/frontend/src/utils/aiToolRegistry.test.ts b/frontend/src/utils/aiToolRegistry.test.ts index 8456346..b98c5cf 100644 --- a/frontend/src/utils/aiToolRegistry.test.ts +++ b/frontend/src/utils/aiToolRegistry.test.ts @@ -122,12 +122,20 @@ describe('aiToolRegistry', () => { expect(info?.tool.function.description).toContain('消息渲染异常'); }); + it('registers the ai-message-flow inspector as a builtin tool', () => { + const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_message_flow'); + expect(info).toBeTruthy(); + expect(info?.desc).toContain('消息流'); + expect(info?.tool.function.description).toContain('连续 assistant 消息'); + }); + it('registers the recent-sql-activity, saved-query, and sql-snippet inspectors as builtin tools', () => { const recentActivityTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_recent_sql_activity'); const sqlRiskTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_sql_risk'); const appLogTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_app_logs'); const connectionFailureTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_recent_connection_failures'); const renderErrorTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_last_render_error'); + const messageFlowTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_message_flow'); const savedQueryTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_saved_queries'); const aiSessionsTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_sessions'); const snippetTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_sql_snippets'); @@ -142,6 +150,8 @@ describe('aiToolRegistry', () => { expect(connectionFailureTool?.tool.function.description).toContain('连接冷却'); expect(renderErrorTool?.desc).toContain('渲染异常记录'); expect(renderErrorTool?.tool.function.description).toContain('气泡局部报错'); + expect(messageFlowTool?.desc).toContain('消息流'); + expect(messageFlowTool?.tool.function.description).toContain('工具调用没有闭环'); expect(savedQueryTool?.desc).toContain('已保存的 SQL 查询'); expect(savedQueryTool?.tool.function.description).toContain('历史查询'); expect(aiSessionsTool?.desc).toContain('AI 历史会话'); @@ -184,6 +194,7 @@ describe('aiToolRegistry', () => { expect(tools.some((item) => item.function.name === 'inspect_app_logs')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_recent_connection_failures')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_ai_last_render_error')).toBe(true); + expect(tools.some((item) => item.function.name === 'inspect_ai_message_flow')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_saved_queries')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_ai_sessions')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_sql_snippets')).toBe(true);