对话中可以直接进行工具测试
This commit is contained in:
parent
493580ba3b
commit
fcf3b8cb9f
@ -29,7 +29,7 @@ bridge.addCommandListener('hello', data => {
|
||||
|
||||
|
||||
function initDebug() {
|
||||
connectionArgs.commandString = 'uv run mcp run ../servers/bing-picture.py';
|
||||
connectionArgs.commandString = 'uv run mcp run ../servers/main.py';
|
||||
connectionMethods.current = 'STDIO';
|
||||
|
||||
setTimeout(async () => {
|
||||
|
@ -42,7 +42,10 @@
|
||||
<div v-for="(call, index) in message.tool_calls" :key="index" class="tool-call-item">
|
||||
<div class="tool-call-header">
|
||||
<span class="tool-name">{{ call.function.name }}</span>
|
||||
<span class="tool-type">{{ call.type }}</span>
|
||||
<span class="tool-type">{{ 'tool' }}</span>
|
||||
<el-button @click="createTest(call)">
|
||||
<span class="iconfont icon-send"></span>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="tool-arguments">
|
||||
<div class="inner">
|
||||
@ -154,7 +157,7 @@ import MessageMeta from './message-meta.vue';
|
||||
// 引入 markdown.ts 中的函数
|
||||
import { markdownToHtml, copyToClipboard } from './markdown';
|
||||
import { ChatCompletionChunk, TaskLoop } from './task-loop';
|
||||
import { llmManager, llms } from '@/views/setting/llm';
|
||||
import { createTest, llmManager, llms } from '@/views/setting/llm';
|
||||
|
||||
defineComponent({ name: 'chat' });
|
||||
|
||||
@ -390,14 +393,6 @@ const jsonResultToHtml = (jsonString: string) => {
|
||||
return html;
|
||||
};
|
||||
|
||||
const formatToolArguments = (args: string) => {
|
||||
try {
|
||||
const parsed = JSON.parse(args);
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
} catch {
|
||||
return args;
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -50,7 +50,7 @@ watch(
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
function createTab(type: string, index: number): Tab {
|
||||
export function createTab(type: string, index: number): Tab {
|
||||
let customName: string | null = null;
|
||||
|
||||
return {
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h3>{{ currentTool?.name }}</h3>
|
||||
</div>
|
||||
<div class="tool-executor-container">
|
||||
<el-form :model="formData" :rules="formRules" ref="formRef" label-position="top">
|
||||
<el-form :model="tabStorage.formData" :rules="formRules" ref="formRef" label-position="top">
|
||||
<template v-if="currentTool?.inputSchema?.properties">
|
||||
<el-form-item
|
||||
v-for="[name, property] in Object.entries(currentTool.inputSchema.properties)"
|
||||
@ -14,7 +14,7 @@
|
||||
>
|
||||
<el-input
|
||||
v-if="property.type === 'string'"
|
||||
v-model="formData[name]"
|
||||
v-model="tabStorage.formData[name]"
|
||||
type="text"
|
||||
:placeholder="t('enter') + ' ' + (property.title || name)"
|
||||
@keydown.enter.prevent="handleExecute"
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
<el-input-number
|
||||
v-else-if="property.type === 'number' || property.type === 'integer'"
|
||||
v-model="formData[name]"
|
||||
v-model="tabStorage.formData[name]"
|
||||
controls-position="right"
|
||||
:placeholder="t('enter') + ' ' + (property.title || name)"
|
||||
@keydown.enter.prevent="handleExecute"
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
<el-switch
|
||||
v-else-if="property.type === 'boolean'"
|
||||
v-model="formData[name]"
|
||||
v-model="tabStorage.formData[name]"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
@ -53,8 +53,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { tabs } from '../panel';
|
||||
import { callTool, toolsManager, ToolStorage } from './tools';
|
||||
import { CasualRestAPI, ToolCallResponse } from '@/hook/type';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { pinkLog } from '@/views/setting/util';
|
||||
|
||||
defineComponent({ name: 'tool-executor' });
|
||||
|
||||
@ -70,8 +69,11 @@ const props = defineProps({
|
||||
const tab = tabs.content[props.tabId];
|
||||
const tabStorage = tab.storage as ToolStorage;
|
||||
|
||||
if (!tabStorage.formData) {
|
||||
tabStorage.formData = {};
|
||||
}
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const formData = ref<Record<string, any>>({});
|
||||
const loading = ref(false);
|
||||
|
||||
const currentTool = computed(() => {
|
||||
@ -97,14 +99,38 @@ const formRules = computed<FormRules>(() => {
|
||||
return rules;
|
||||
});
|
||||
|
||||
const getDefaultValue = (property: any) => {
|
||||
if (property.type === 'number' || property.type === 'integer') {
|
||||
return 0;
|
||||
} else if (property.type === 'boolean') {
|
||||
return false;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const initFormData = () => {
|
||||
formData.value = {};
|
||||
// 初始化,根据输入的 inputSchema 校验
|
||||
// 1. 当前是否存在缺失的 key value,如果有,则根据 schema 给与默认值
|
||||
// 2. 如果有多余的 key value,则删除
|
||||
|
||||
if (!currentTool.value?.inputSchema?.properties) return;
|
||||
|
||||
const newSchemaDataForm: Record<string, number | boolean | string> = {};
|
||||
|
||||
Object.entries(currentTool.value.inputSchema.properties).forEach(([name, property]) => {
|
||||
formData.value[name] = (property.type === 'number' || property.type === 'integer') ? 0 :
|
||||
property.type === 'boolean' ? false : '';
|
||||
newSchemaDataForm[name] = getDefaultValue(property);
|
||||
let originType: string = typeof tabStorage.formData[name];
|
||||
if (originType === 'number') {
|
||||
originType = 'integer';
|
||||
}
|
||||
|
||||
if (tabStorage.formData[name] !== undefined && originType === property.type) {
|
||||
newSchemaDataForm[name] = tabStorage.formData[name];
|
||||
}
|
||||
});
|
||||
|
||||
tabStorage.formData = newSchemaDataForm;
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
@ -114,7 +140,7 @@ const resetForm = () => {
|
||||
|
||||
async function handleExecute() {
|
||||
if (!currentTool.value) return;
|
||||
const toolResponse = await callTool(tabStorage.currentToolName, formData.value);
|
||||
const toolResponse = await callTool(tabStorage.currentToolName, tabStorage.formData);
|
||||
tabStorage.lastToolCallResponse = toolResponse;
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,11 @@ onMounted(() => {
|
||||
commandCancel = bridge.addCommandListener('tools/list', (data: CasualRestAPI<ToolsListResponse>) => {
|
||||
toolsManager.tools = data.msg.tools || [];
|
||||
|
||||
if (toolsManager.tools.length > 0) {
|
||||
const targetTool = toolsManager.tools.find((tool) => {
|
||||
return tool.name === tabStorage.currentToolName;
|
||||
});
|
||||
|
||||
if (targetTool === undefined) {
|
||||
tabStorage.currentToolName = toolsManager.tools[0].name;
|
||||
tabStorage.lastToolCallResponse = undefined;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import { defineComponent, defineProps, computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { tabs } from '../panel';
|
||||
import { ToolStorage } from './tools';
|
||||
import { markdownToHtml } from '../chat/markdown';
|
||||
|
||||
defineComponent({ name: 'tool-logger' });
|
||||
const { t } = useI18n();
|
||||
@ -64,6 +65,12 @@ const formattedJson = computed(() => {
|
||||
return 'Invalid JSON';
|
||||
}
|
||||
});
|
||||
|
||||
const jsonResultToHtml = (jsonString: string) => {
|
||||
const formattedJson = JSON.stringify(JSON.parse(jsonString), null, 2);
|
||||
const html = markdownToHtml('```json\n' + formattedJson + '\n```');
|
||||
return html;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -12,6 +12,7 @@ export const toolsManager = reactive<{
|
||||
export interface ToolStorage {
|
||||
currentToolName: string;
|
||||
lastToolCallResponse?: ToolCallResponse;
|
||||
formData: Record<string, number | string | boolean>;
|
||||
}
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
|
@ -45,8 +45,8 @@ export function loadPanels() {
|
||||
|
||||
for (const tab of persistTab.tabs || []) {
|
||||
|
||||
const component = tab.componentIndex >= 0? debugModes[tab.componentIndex] : undefined;
|
||||
|
||||
const component = tab.componentIndex >= 0? markRaw(debugModes[tab.componentIndex]) : undefined;
|
||||
|
||||
tabs.content.push({
|
||||
name: tab.name,
|
||||
icon: tab.icon,
|
||||
|
@ -1,9 +1,30 @@
|
||||
import { reactive } from 'vue';
|
||||
import { pinkLog } from './util';
|
||||
import { saveSetting } from '@/hook/setting';
|
||||
import { markRaw, reactive } from 'vue';
|
||||
import { createTab, debugModes, tabs } from '@/components/main-panel/panel';
|
||||
import { ToolStorage } from '@/components/main-panel/tool/tools';
|
||||
import { ToolCall } from '@/components/main-panel/chat/chat';
|
||||
|
||||
import I18n from '@/i18n';
|
||||
const { t } = I18n.global;
|
||||
|
||||
export const llms = reactive<any[]>([]);
|
||||
|
||||
export const llmManager = reactive({
|
||||
currentModelIndex: 0,
|
||||
});
|
||||
|
||||
export function createTest(call: ToolCall) {
|
||||
const tab = createTab('tool', 0);
|
||||
tab.componentIndex = 2;
|
||||
tab.component = markRaw(debugModes[2]);
|
||||
tab.icon = 'icon-tool';
|
||||
tab.name = t("tools");
|
||||
1
|
||||
const storage: ToolStorage = {
|
||||
currentToolName: call.function.name,
|
||||
formData: JSON.parse(call.function.arguments)
|
||||
};
|
||||
|
||||
tab.storage = storage;
|
||||
tabs.content.push(tab);
|
||||
tabs.activeIndex = tabs.content.length - 1;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"currentIndex": 0,
|
||||
"currentIndex": 1,
|
||||
"tabs": [
|
||||
{
|
||||
"name": "交互测试",
|
||||
@ -7,7 +7,82 @@
|
||||
"type": "blank",
|
||||
"componentIndex": 3,
|
||||
"storage": {
|
||||
"messages": [],
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请问杭州的天气",
|
||||
"extraInfo": {
|
||||
"created": 1745258229256,
|
||||
"serverName": "deepseek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_0_57ad3eab-cd9d-4403-bdf0-31d00b83b98c",
|
||||
"index": 0,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather_by_city_code",
|
||||
"arguments": "{\"city_code\":101210101}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1745258234470,
|
||||
"serverName": "deepseek",
|
||||
"usage": {
|
||||
"prompt_tokens": 570,
|
||||
"completion_tokens": 26,
|
||||
"total_tokens": 596,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 512
|
||||
},
|
||||
"prompt_cache_hit_tokens": 512,
|
||||
"prompt_cache_miss_tokens": 58
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_0_57ad3eab-cd9d-4403-bdf0-31d00b83b98c",
|
||||
"content": "[{\"type\":\"text\",\"text\":\"CityWeather(city_name_en='hangzhou', city_name_cn='杭州', city_code='101210101', temp='21', wd='', ws='', sd='92%', aqi='13', weather='阴')\"}]",
|
||||
"extraInfo": {
|
||||
"created": 1745258234522,
|
||||
"serverName": "deepseek",
|
||||
"usage": {
|
||||
"prompt_tokens": 570,
|
||||
"completion_tokens": 26,
|
||||
"total_tokens": 596,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 512
|
||||
},
|
||||
"prompt_cache_hit_tokens": 512,
|
||||
"prompt_cache_miss_tokens": 58
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "杭州的天气信息如下:\n\n- 城市:杭州\n- 温度:21°C\n- 天气状况:阴\n- 湿度:92%\n- 空气质量指数 (AQI):13(优秀)\n\n天气较为舒适,适合出行!",
|
||||
"extraInfo": {
|
||||
"created": 1745258240571,
|
||||
"serverName": "deepseek",
|
||||
"usage": {
|
||||
"prompt_tokens": 660,
|
||||
"completion_tokens": 52,
|
||||
"total_tokens": 712,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 576
|
||||
},
|
||||
"prompt_cache_hit_tokens": 576,
|
||||
"prompt_cache_miss_tokens": 84
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"modelIndex": 0,
|
||||
"enableTools": [
|
||||
@ -43,6 +118,27 @@
|
||||
"systemPrompt": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "工具",
|
||||
"icon": "icon-tool",
|
||||
"type": "tool",
|
||||
"componentIndex": 2,
|
||||
"storage": {
|
||||
"currentToolName": "get_weather_by_city_code",
|
||||
"formData": {
|
||||
"city_code": 101210101
|
||||
},
|
||||
"lastToolCallResponse": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "CityWeather(city_name_en='hangzhou', city_name_cn='杭州', city_code='101210101', temp='20.7', wd='', ws='', sd='93%', aqi='13', weather='阴')"
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user