feat: 添加工具自检并行测试功能

- 添加并行测试开关UI,允许用户选择串行或并行测试模式
- 实现makeParallelTest函数,支持在单个对话请求中并行测试多个工具
- 优化测试逻辑,提升测试效率和用户体验
- 添加中英文国际化支持
- 保持向后兼容,默认使用串行测试模式
This commit is contained in:
Meghan Morrow 2025-07-10 17:18:31 +08:00
parent 43a19b1d11
commit 5fbff1e85a
4 changed files with 169 additions and 24 deletions

View File

@ -161,6 +161,125 @@ export function topoSortParallel(state: DiagramState): string[][] {
return result;
}
export async function makeParallelTest(
dataViews: Reactive<NodeDataView>[],
enableXmlWrapper: boolean,
prompt: string | null = null,
context: DiagramContext
) {
if (dataViews.length === 0) {
return;
}
// 设置所有节点状态为运行中
const createAt = Date.now();
dataViews.forEach(dataView => {
dataView.status = 'running';
dataView.createAt = createAt;
});
context.render();
try {
const loop = new TaskLoop({ maxEpochs: 1 });
// 构建所有工具的信息
const allTools = dataViews.map(dataView => ({
name: dataView.tool.name,
description: dataView.tool.description,
inputSchema: dataView.tool.inputSchema,
enabled: true
}));
// 构建测试提示词,包含所有工具
const toolNames = dataViews.map(dv => dv.tool.name).join(', ');
const usePrompt = (prompt || 'please call the tools {tool} to make some test').replace('{tool}', toolNames);
const chatStorage = {
messages: [],
settings: {
temperature: 0.6,
systemPrompt: '',
enableTools: allTools,
enableWebSearch: false,
contextLength: 5,
enableXmlWrapper,
parallelToolCalls: true // 开启并行工具调用
}
} as ChatStorage;
loop.setMaxEpochs(1);
// 记录工具调用信息,用于匹配工具调用结果
const toolCallMap: Map<string, Reactive<NodeDataView>> = new Map(); // toolCallId -> dataView
let completedToolsCount = 0;
loop.registerOnToolCall(toolCall => {
// 找到对应的dataView
const dataView = dataViews.find(dv => dv.tool.name === toolCall.function?.name);
if (dataView) {
dataView.function = toolCall.function;
dataView.llmTimecost = Date.now() - createAt;
context.render();
// 记录工具调用ID与dataView的映射
if (toolCall.id) {
toolCallMap.set(toolCall.id, dataView);
}
}
return toolCall;
});
loop.registerOnToolCalled(toolCalled => {
// 这里我们需要改变策略因为没有工具调用ID信息
// 对于并行调用,我们可以根据工具调用的顺序来匹配结果
// 简单的策略:找到第一个仍在运行状态的工具
const runningView = dataViews.find(dv => dv.status === 'running');
if (runningView) {
runningView.toolcallTimecost = Date.now() - createAt - (runningView.llmTimecost || 0);
if (toolCalled.state === MessageState.Success) {
runningView.status = 'success';
runningView.result = toolCalled.content;
} else {
runningView.status = 'error';
runningView.result = toolCalled.content;
}
completedToolsCount++;
context.render();
// 如果所有工具都完成了,终止循环
if (completedToolsCount >= dataViews.length) {
loop.abort();
}
}
return toolCalled;
});
loop.registerOnError(error => {
dataViews.forEach(dataView => {
if (dataView.status === 'running') {
dataView.status = 'error';
dataView.result = error;
}
});
context.render();
});
await loop.start(chatStorage, usePrompt);
} finally {
const finishAt = Date.now();
dataViews.forEach(dataView => {
dataView.finishAt = finishAt;
if (dataView.status === 'running') {
dataView.status = 'success';
}
});
context.render();
}
}
export async function makeNodeTest(
dataView: Reactive<NodeDataView>,
enableXmlWrapper: boolean,

View File

@ -37,6 +37,13 @@
color: enableXmlWrapper ? 'var(--main-color)' : undefined
}">XML</span>
</div>
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<el-switch v-model="enableParallelTest" style="margin-right: 8px;" />
<span :style="{
opacity: enableParallelTest ? 1 : 0.7,
color: enableParallelTest ? 'var(--main-color)' : undefined
}">{{ t('parallel-test') }}</span>
</div>
<div style="text-align: right;">
<el-button size="small" @click="testFormVisible = false">{{ t("cancel") }}</el-button>
<el-button size="small" type="primary" @click="onTestConfirm">
@ -70,7 +77,7 @@
<script setup lang="ts">
import { computed, nextTick, provide, ref } from 'vue';
import Diagram from './diagram.vue';
import { makeNodeTest, topoSortParallel, type DiagramContext, type DiagramState } from './diagram';
import { makeNodeTest, makeParallelTest, topoSortParallel, type DiagramContext, type DiagramState } from './diagram';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
@ -137,23 +144,42 @@ if (autoDetectDiagram) {
//
const testFormVisible = ref(false);
const enableXmlWrapper = ref(false);
const enableParallelTest = ref(false);
const testPrompt = ref('please call the tool {tool} to make some test');
async function onTestConfirm() {
testFormVisible.value = false;
// enableXmlWrapper.value testPrompt.value
const state = context.state;
tabStorage.autoDetectDiagram!.views = [];
if (state) {
const dispatches = topoSortParallel(state);
for (const nodeIds of dispatches) {
for (const id of nodeIds) {
const view = state.dataView.get(id);
if (view) {
await makeNodeTest(view, enableXmlWrapper.value, testPrompt.value, context)
if (enableParallelTest.value) {
//
const allViews = Array.from(state.dataView.values());
await makeParallelTest(allViews, enableXmlWrapper.value, testPrompt.value, context);
//
allViews.forEach(view => {
tabStorage.autoDetectDiagram!.views!.push({
tool: view.tool,
status: view.status,
function: view.function,
result: view.result,
createAt: view.createAt,
finishAt: view.finishAt,
llmTimecost: view.llmTimecost,
toolcallTimecost: view.toolcallTimecost,
});
});
} else {
//
const dispatches = topoSortParallel(state);
for (const nodeIds of dispatches) {
for (const id of nodeIds) {
const view = state.dataView.get(id);
if (view) {
await makeNodeTest(view, enableXmlWrapper.value, testPrompt.value, context);
tabStorage.autoDetectDiagram!.views!.push({
tool: view.tool,
status: view.status,
@ -164,16 +190,14 @@ async function onTestConfirm() {
llmTimecost: view.llmTimecost,
toolcallTimecost: view.toolcallTimecost,
});
context.render();
}
}
}
}
} else {
ElMessage.error('error');
}
}
const resetPopoverVisible = ref(false);

View File

@ -157,6 +157,7 @@
"feedback": "Feedback",
"waiting-mcp-server": "Waiting for MCP server response",
"parallel-tool-calls": "Allow model to call multiple tools in single response",
"parallel-test": "Parallel Test",
"proxy-server": "Proxy Server",
"update-model-list": "Update Model List",
"preset-env-sync.success": "Preset environment variables synced",

View File

@ -157,6 +157,7 @@
"feedback": "反馈",
"waiting-mcp-server": "等待 MCP 服务器响应",
"parallel-tool-calls": "允许模型在单轮回复中调用多个工具",
"parallel-test": "并行测试",
"proxy-server": "代理服务器",
"update-model-list": "更新模型列表",
"preset-env-sync.success": "预设环境变量同步完成",