feat(ai): 新增 AI 消息流诊断探针

- 新增 inspect_ai_message_flow 内置工具

- 识别连续 assistant 气泡、空消息和未闭环工具调用

- 同步工具目录、系统引导、执行状态文案和回归测试
This commit is contained in:
Syngnat
2026-06-10 12:59:09 +08:00
parent e16082af9a
commit 8ddd8a726d
11 changed files with 288 additions and 5 deletions

View File

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

View File

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