mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-25 16:04:02 +08:00
✨ feat(ai): 优化内置工具目录检索与参数提示
- 为内置工具目录增加关键词搜索和结果计数 - 参数提示补充类型、默认值、枚举和示例信息 - 补充目录渲染和参数摘要提取测试
This commit is contained in:
93
frontend/src/utils/aiBuiltinToolCatalog.test.ts
Normal file
93
frontend/src/utils/aiBuiltinToolCatalog.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
BUILTIN_TOOL_FLOWS,
|
||||
describeBuiltinToolParameters,
|
||||
filterBuiltinToolFlows,
|
||||
filterBuiltinTools,
|
||||
} from './aiBuiltinToolCatalog';
|
||||
import type { AIBuiltinToolInfo } from './aiBuiltinToolInfo.types';
|
||||
import { BUILTIN_AI_TOOL_INFO } from './aiToolRegistry';
|
||||
|
||||
describe('describeBuiltinToolParameters', () => {
|
||||
it('extracts type, required, enum, default, and example hints from builtin tool schemas', () => {
|
||||
const tool: AIBuiltinToolInfo = {
|
||||
name: 'inspect_demo',
|
||||
icon: '🧪',
|
||||
desc: '测试工具',
|
||||
detail: '用于测试参数提示提取。',
|
||||
params: 'lineLimit?, mode?, serverName?',
|
||||
tool: {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'inspect_demo',
|
||||
description: '测试工具',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
required: ['mode'],
|
||||
properties: {
|
||||
lineLimit: { type: 'number', description: '可选,最多读取多少行,默认 160,最大 200' },
|
||||
mode: { type: 'string', enum: ['fast', 'safe'], default: 'safe', description: '运行模式' },
|
||||
serverName: { type: 'string', description: '可选,例如 GitHub、Browser、DockerFetch' },
|
||||
includeDisabled: { type: ['boolean', 'null'], description: '是否包含禁用项,默认 false' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(describeBuiltinToolParameters(tool)).toEqual([
|
||||
{
|
||||
name: 'lineLimit',
|
||||
required: false,
|
||||
typeLabel: 'number',
|
||||
description: '可选,最多读取多少行,默认 160,最大 200',
|
||||
enumValues: [],
|
||||
defaultValue: '160',
|
||||
exampleValue: '',
|
||||
},
|
||||
{
|
||||
name: 'mode',
|
||||
required: true,
|
||||
typeLabel: 'string',
|
||||
description: '运行模式',
|
||||
enumValues: ['fast', 'safe'],
|
||||
defaultValue: 'safe',
|
||||
exampleValue: '',
|
||||
},
|
||||
{
|
||||
name: 'serverName',
|
||||
required: false,
|
||||
typeLabel: 'string',
|
||||
description: '可选,例如 GitHub、Browser、DockerFetch',
|
||||
enumValues: [],
|
||||
defaultValue: '',
|
||||
exampleValue: 'GitHub、Browser、DockerFetch',
|
||||
},
|
||||
{
|
||||
name: 'includeDisabled',
|
||||
required: false,
|
||||
typeLabel: 'boolean | null',
|
||||
description: '是否包含禁用项,默认 false',
|
||||
enumValues: [],
|
||||
defaultValue: 'false',
|
||||
exampleValue: '',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('filters flows and tools by parameter names and descriptions', () => {
|
||||
const allowMutatingTools = filterBuiltinTools(BUILTIN_AI_TOOL_INFO, 'allowMutating')
|
||||
.map((tool) => tool.name);
|
||||
expect(allowMutatingTools).toContain('inspect_ai_safety');
|
||||
expect(allowMutatingTools).not.toContain('inspect_mcp_runtime_failures');
|
||||
|
||||
const executeSqlTools = filterBuiltinTools(BUILTIN_AI_TOOL_INFO, '要执行的 SQL 语句')
|
||||
.map((tool) => tool.name);
|
||||
expect(executeSqlTools).toContain('execute_sql');
|
||||
|
||||
const mcpFlows = filterBuiltinToolFlows(BUILTIN_TOOL_FLOWS, '运行期失败日志')
|
||||
.map((flow) => flow.title);
|
||||
expect(mcpFlows).toContain('排查 MCP 接入状态');
|
||||
});
|
||||
});
|
||||
@@ -9,8 +9,11 @@ export interface AIBuiltinToolFlow {
|
||||
export interface AIBuiltinToolParameterHint {
|
||||
name: string;
|
||||
required: boolean;
|
||||
typeLabel: string;
|
||||
description: string;
|
||||
enumValues: string[];
|
||||
defaultValue: string;
|
||||
exampleValue: string;
|
||||
}
|
||||
|
||||
export const BUILTIN_TOOL_FLOWS: AIBuiltinToolFlow[] = [
|
||||
@@ -231,6 +234,44 @@ export const BUILTIN_TOOL_FLOWS: AIBuiltinToolFlow[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const stringifyHintValue = (value: unknown): string => {
|
||||
if (value === undefined) return '';
|
||||
if (value === null) return 'null';
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
};
|
||||
|
||||
const readTypeLabel = (schema: Record<string, any>): string => {
|
||||
if (Array.isArray(schema.type)) {
|
||||
return schema.type.map((item) => String(item)).filter(Boolean).join(' | ') || 'any';
|
||||
}
|
||||
if (typeof schema.type === 'string' && schema.type.trim()) {
|
||||
return schema.type.trim();
|
||||
}
|
||||
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
||||
return 'enum';
|
||||
}
|
||||
return 'any';
|
||||
};
|
||||
|
||||
const readDefaultValue = (schema: Record<string, any>, description: string): string => {
|
||||
if (Object.prototype.hasOwnProperty.call(schema, 'default')) {
|
||||
return stringifyHintValue(schema.default);
|
||||
}
|
||||
const match = description.match(/默认\s*([^\s,,;;。))]+)/u);
|
||||
return match?.[1]?.trim() || '';
|
||||
};
|
||||
|
||||
const readExampleValue = (description: string): string => {
|
||||
const match = description.match(/(?:例如|示例值?[::])\s*([^。;;\n]+)/u);
|
||||
return match?.[1]?.trim() || '';
|
||||
};
|
||||
|
||||
export const describeBuiltinToolParameters = (tool: AIBuiltinToolInfo): AIBuiltinToolParameterHint[] => {
|
||||
const schema = tool.tool.function.parameters;
|
||||
const properties = schema && typeof schema === 'object' && typeof schema.properties === 'object'
|
||||
@@ -242,11 +283,57 @@ export const describeBuiltinToolParameters = (tool: AIBuiltinToolInfo): AIBuilti
|
||||
|
||||
return Object.entries(properties).map(([name, config]) => {
|
||||
const normalized = config && typeof config === 'object' ? config as Record<string, any> : {};
|
||||
const description = typeof normalized.description === 'string' ? normalized.description : '';
|
||||
return {
|
||||
name,
|
||||
required: required.has(name),
|
||||
description: typeof normalized.description === 'string' ? normalized.description : '',
|
||||
typeLabel: readTypeLabel(normalized),
|
||||
description,
|
||||
enumValues: Array.isArray(normalized.enum) ? normalized.enum.map((item) => String(item)) : [],
|
||||
defaultValue: readDefaultValue(normalized, description),
|
||||
exampleValue: readExampleValue(description),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const normalizeBuiltinToolCatalogSearch = (value: string): string =>
|
||||
value.trim().toLowerCase();
|
||||
|
||||
const matchesCatalogSearch = (keyword: string, values: unknown[]): boolean =>
|
||||
!keyword || values.some((value) => String(value || '').toLowerCase().includes(keyword));
|
||||
|
||||
export const filterBuiltinToolFlows = (
|
||||
flows: AIBuiltinToolFlow[],
|
||||
searchText: string,
|
||||
): AIBuiltinToolFlow[] => {
|
||||
const keyword = normalizeBuiltinToolCatalogSearch(searchText);
|
||||
return flows.filter((flow) => matchesCatalogSearch(keyword, [
|
||||
flow.title,
|
||||
flow.steps,
|
||||
flow.description,
|
||||
]));
|
||||
};
|
||||
|
||||
export const filterBuiltinTools = (
|
||||
tools: AIBuiltinToolInfo[],
|
||||
searchText: string,
|
||||
): AIBuiltinToolInfo[] => {
|
||||
const keyword = normalizeBuiltinToolCatalogSearch(searchText);
|
||||
return tools.filter((tool) => {
|
||||
const parameterDetails = describeBuiltinToolParameters(tool);
|
||||
return matchesCatalogSearch(keyword, [
|
||||
tool.name,
|
||||
tool.desc,
|
||||
tool.detail,
|
||||
tool.params,
|
||||
...parameterDetails.flatMap((item) => [
|
||||
item.name,
|
||||
item.typeLabel,
|
||||
item.description,
|
||||
item.defaultValue,
|
||||
item.exampleValue,
|
||||
item.enumValues.join(' '),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user