feat(ai): 新增上游请求日志自查工具

This commit is contained in:
Syngnat
2026-06-11 12:00:17 +08:00
parent 2d562ccfd6
commit 4265d7cfa9
11 changed files with 472 additions and 1 deletions

View File

@@ -148,6 +148,35 @@ export const BUILTIN_AI_INSPECTION_TOOL_INFO: AIBuiltinToolInfo[] = [
},
},
},
{
name: "inspect_ai_upstream_logs",
icon: "📡",
desc: "查看 AI 上游请求入参与状态",
detail:
"从 gonavi.log 读取最近的 AI 上游请求开始/完成/失败记录,按 provider、requestId 或关键词过滤,返回请求体 body 预览、endpoint、状态码、耗时和错误摘要。适合用户想核对发给上游模型的真实入参、排查请求参数兼容、确认脱敏日志是否写入时先调用。",
params: "provider?, requestId?, keyword?, lineLimit?(默认 160), requestLimit?(默认 12), includeBody?(默认 true), includeLines?(默认 false)",
tool: {
type: "function",
function: {
name: "inspect_ai_upstream_logs",
description:
"读取 GoNavi 应用日志中的 AI 上游请求记录,返回 requestId、provider、method、endpoint、请求 body 预览、状态码、耗时和错误摘要。适用于用户提到 AI 请求入参、上游请求体、requestId、provider 请求参数、模型接口报错、或需要核对刚才发给上游模型的真实 payload 时,先读取该工具,不要只凭界面响应推断。",
parameters: {
type: "object",
properties: {
provider: { type: "string", description: "可选,只看某个供应商,例如 openai、anthropic、gemini大小写不敏感" },
requestId: { type: "string", description: "可选,按日志里的 requestId 精确过滤,适合从错误日志继续追踪同一次请求" },
keyword: { type: "string", description: "可选,在 requestId、provider、endpoint、bodyPreview 或 error 中继续过滤,例如模型名、接口路径、参数名" },
lineLimit: { type: "number", description: "可选,最多读取多少行日志尾部,默认 160最大 300" },
requestLimit: { type: "number", description: "可选,最多返回多少个请求摘要,默认 12最大 40" },
includeBody: { type: "boolean", description: "可选,是否返回已脱敏的请求 body 预览,默认 true只看状态时可设为 false" },
includeLines: { type: "boolean", description: "可选,是否附带脱敏后的原始日志行,默认 false需要引用原文时再开启" },
bodyPreviewLimit: { type: "number", description: "可选,单个 body 预览最大字符数,默认 6000最大 12000" },
},
},
},
},
},
{
name: "inspect_ai_tool_catalog",
icon: "🧭",

View File

@@ -79,6 +79,11 @@ export const BUILTIN_TOOL_FLOWS: AIBuiltinToolFlow[] = [
steps: 'inspect_ai_chat_readiness -> inspect_ai_providers',
description: '适合先确认当前聊天输入区到底缺什么前置条件,例如没选活动供应商、缺密钥、缺接口地址、没选模型,避免只凭界面现象猜测。',
},
{
title: '追踪 AI 上游请求',
steps: 'inspect_ai_upstream_logs -> inspect_ai_providers / inspect_ai_message_flow',
description: '适合用户想看发给上游模型的真实入参、requestId、状态码、耗时或请求体预览时先读脱敏后的 gonavi.log 请求记录,再结合供应商配置和当前消息流继续排查。',
},
{
title: '排查 MCP 接入状态',
steps: 'inspect_mcp_setup -> inspect_ai_runtime',

View File

@@ -12,6 +12,10 @@ describe('ai composer notice helpers', () => {
tone: 'warning',
title: '还没有可用供应商',
description: '先在 AI 设置里添加并启用一个模型供应商。',
action: {
key: 'open-settings',
label: '打开 AI 设置',
},
});
});
@@ -20,6 +24,10 @@ describe('ai composer notice helpers', () => {
tone: 'warning',
title: '先选择一个模型',
description: '打开下方模型下拉并选择模型;如果列表为空,请检查供应商入口和 API Key。',
action: {
key: 'reload-models',
label: '重新加载模型',
},
});
});
@@ -28,6 +36,10 @@ describe('ai composer notice helpers', () => {
tone: 'error',
title: '模型列表加载失败',
description: '当前接口未返回可用模型',
action: {
key: 'reload-models',
label: '重新加载模型',
},
});
});
});

View File

@@ -84,6 +84,14 @@ describe('aiToolRegistry', () => {
expect(info?.tool.function.description).toContain('当前 AI 聊天输入区');
});
it('registers the ai-upstream-log inspector as a builtin tool', () => {
const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_upstream_logs');
expect(info).toBeTruthy();
expect(info?.desc).toContain('上游请求入参');
expect(info?.tool.function.description).toContain('请求 body 预览');
expect(info?.tool.function.parameters?.properties?.requestId?.description).toContain('requestId');
});
it('registers the ai-tool-catalog inspector as a builtin tool', () => {
const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_tool_catalog');
expect(info).toBeTruthy();
@@ -234,6 +242,7 @@ describe('aiToolRegistry', () => {
expect(tools.some((item) => item.function.name === 'inspect_ai_safety')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_ai_providers')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_ai_chat_readiness')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_ai_upstream_logs')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_ai_tool_catalog')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_mcp_setup')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_mcp_remote_access')).toBe(true);