给 tool use 设计负反馈机制
This commit is contained in:
parent
dd9c117df7
commit
f14687b1d8
@ -3,8 +3,9 @@
|
||||
<el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll" v-if="renderMessages.length > 0 || isLoading">
|
||||
<div class="message-list" :ref="el => messageListRef = el">
|
||||
<div v-for="(message, index) in renderMessages" :key="index"
|
||||
:class="['message-item', message.role.split('/')[0]]">
|
||||
<div class="message-avatar" v-if="message.role.split('/')[0] === 'assistant'">
|
||||
:class="['message-item', message.role.split('/')[0]]"
|
||||
>
|
||||
<div class="message-avatar" v-if="message.role.startsWith('assistant')">
|
||||
<span class="iconfont icon-robot"></span>
|
||||
</div>
|
||||
|
||||
@ -131,6 +132,7 @@ interface IRenderMessage {
|
||||
tool_calls?: ToolCall[];
|
||||
showJson?: Ref<boolean>;
|
||||
extraInfo: IExtraInfo;
|
||||
isLast: boolean;
|
||||
}
|
||||
|
||||
const renderMessages = computed(() => {
|
||||
@ -140,7 +142,8 @@ const renderMessages = computed(() => {
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: message.content,
|
||||
extraInfo: message.extraInfo
|
||||
extraInfo: message.extraInfo,
|
||||
isLast: false
|
||||
});
|
||||
} else if (message.role === 'assistant') {
|
||||
if (message.tool_calls) {
|
||||
@ -149,13 +152,15 @@ const renderMessages = computed(() => {
|
||||
content: message.content,
|
||||
tool_calls: message.tool_calls,
|
||||
showJson: ref(false),
|
||||
extraInfo: message.extraInfo
|
||||
extraInfo: message.extraInfo,
|
||||
isLast: false
|
||||
});
|
||||
} else {
|
||||
messages.push({
|
||||
role: 'assistant/content',
|
||||
content: message.content,
|
||||
extraInfo: message.extraInfo
|
||||
extraInfo: message.extraInfo,
|
||||
isLast: false
|
||||
});
|
||||
}
|
||||
|
||||
@ -169,6 +174,11 @@ const renderMessages = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.length > 0) {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
lastMessage.isLast = true;
|
||||
}
|
||||
|
||||
return messages;
|
||||
});
|
||||
|
||||
@ -375,11 +385,6 @@ onUnmounted(() => {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.message-text.tool_calls {
|
||||
border-left: 3px solid var(--main-color);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.user .message-text {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
@ -1,29 +1,34 @@
|
||||
<template>
|
||||
<div class="message-role">
|
||||
Agent
|
||||
<span class="message-reminder" v-if="!props.message.toolResult">
|
||||
<span class="message-reminder" v-if="props.message.isLast && !props.message.toolResult">
|
||||
正在使用工具
|
||||
<span class="tool-loading iconfont icon-double-loading">
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="message-text tool_calls">
|
||||
<div class="message-text tool_calls" :class="{ 'fail': !props.message.isLast && !props.message.toolResult }">
|
||||
<div v-if="props.message.content" v-html="markdownToHtml(props.message.content)"></div>
|
||||
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item name="tool">
|
||||
|
||||
<template #title>
|
||||
<div class="tool-calls">
|
||||
<div v-for="(call, index) in props.message.tool_calls" :key="index" class="tool-call-item">
|
||||
<div class="tool-call-header">
|
||||
<span class="tool-type">{{ 'tool' }}</span>
|
||||
<span class="tool-name">{{ call.function.name }}</span>
|
||||
<el-button size="small" @click="createTest(call)">
|
||||
<span class="tool-type">{{ 'tool use' }}</span>
|
||||
<span class="tool-name">{{ props.message.tool_calls[0].function.name }}</span>
|
||||
<el-button size="small" @click="createTest(props.message.tool_calls[0])">
|
||||
<span class="iconfont icon-send"></span>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<div class="tool-arguments">
|
||||
<div class="inner">
|
||||
<div v-html="jsonResultToHtml(call.function.arguments)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-html="jsonResultToHtml(props.message.tool_calls[0].function.arguments)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -32,8 +37,8 @@
|
||||
<div class="tool-call-header">
|
||||
<span class="tool-name">{{ "响应" }}</span>
|
||||
<span style="width: 200px;" class="tools-dialog-container">
|
||||
<el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON" inactive-text="Text"
|
||||
style="margin-left: 10px; width: 200px;"
|
||||
<el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON"
|
||||
inactive-text="Text" style="margin-left: 10px; width: 200px;"
|
||||
:inactive-action-style="'backgroundColor: var(--sidebar)'" />
|
||||
</span>
|
||||
</div>
|
||||
@ -54,11 +59,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
<MessageMeta :message="message" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
import { defineProps, ref, watch } from 'vue';
|
||||
|
||||
import MessageMeta from './message-meta.vue';
|
||||
import { markdownToHtml } from '../markdown';
|
||||
@ -71,6 +79,19 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const activeNames = ref<string[]>(props.message.toolResult ? [''] : ['tool']);
|
||||
|
||||
watch(
|
||||
() => props.message.toolResult,
|
||||
(value, oldValue) => {
|
||||
if (value) {
|
||||
setTimeout(() => {
|
||||
activeNames.value = [''];
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const jsonResultToHtml = (jsonString: string) => {
|
||||
const formattedJson = JSON.stringify(JSON.parse(jsonString), null, 2);
|
||||
const html = markdownToHtml('```json\n' + formattedJson + '\n```');
|
||||
@ -89,6 +110,14 @@ const isValidJSON = (str: string) => {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.message-text.tool_calls {
|
||||
border-left: 3px solid var(--main-color);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.message-text.tool_calls.fail {
|
||||
border-left: 3px solid var(--el-color-error);
|
||||
}
|
||||
|
||||
.tool-calls {
|
||||
margin-top: 10px;
|
||||
@ -123,6 +152,7 @@ const isValidJSON = (str: string) => {
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
margin-right: 10px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.tool-arguments {
|
||||
@ -151,6 +181,4 @@ const isValidJSON = (str: string) => {
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
@ -18,7 +18,7 @@ export function createTest(call: ToolCall) {
|
||||
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)
|
||||
|
129
service/tabs.example-servers/puppeteer.json
Normal file
129
service/tabs.example-servers/puppeteer.json
Normal file
@ -0,0 +1,129 @@
|
||||
{
|
||||
"currentIndex": 1,
|
||||
"tabs": [
|
||||
{
|
||||
"name": "空白测试 2",
|
||||
"icon": "icon-blank",
|
||||
"type": "blank",
|
||||
"componentIndex": -1,
|
||||
"storage": {}
|
||||
},
|
||||
{
|
||||
"name": "交互测试",
|
||||
"icon": "icon-robot",
|
||||
"type": "blank",
|
||||
"componentIndex": 3,
|
||||
"storage": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "今天天气",
|
||||
"extraInfo": {
|
||||
"created": 1745498953049,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_zw5axi9gftk294vifdlfv83y",
|
||||
"index": 0,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "puppeteer_navigate",
|
||||
"arguments": "{\"url\":\"https://www.weather.com\"}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1745498953891,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_zw5axi9gftk294vifdlfv83y",
|
||||
"content": "[{\"type\":\"text\",\"text\":\"Navigated to https://www.weather.com\"}]",
|
||||
"extraInfo": {
|
||||
"created": 1745498965901,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_enwp8eh9q0rklmz1cr0lfinx",
|
||||
"index": 0,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "puppeteer_evaluate",
|
||||
"arguments": "{\"script\":\"document.querySelector('.CurrentConditions--tempValue--3KcTQ').innerText\"}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1745498967051,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "错误: 工具调用失败: [object Object]",
|
||||
"extraInfo": {
|
||||
"created": 1745498967057,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"modelIndex": 8,
|
||||
"enableTools": [
|
||||
{
|
||||
"name": "puppeteer_navigate",
|
||||
"description": "Navigate to a URL",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "puppeteer_screenshot",
|
||||
"description": "Take a screenshot of the current page or a specific element",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "puppeteer_click",
|
||||
"description": "Click an element on the page",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "puppeteer_fill",
|
||||
"description": "Fill out an input field",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "puppeteer_select",
|
||||
"description": "Select an element on the page with Select tag",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "puppeteer_hover",
|
||||
"description": "Hover an element on the page",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "puppeteer_evaluate",
|
||||
"description": "Execute JavaScript in the browser console",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"enableWebSearch": false,
|
||||
"temperature": 0.7,
|
||||
"contextLength": 10,
|
||||
"systemPrompt": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -81,6 +81,50 @@
|
||||
"prompt_cache_miss_tokens": 84
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "再试一次",
|
||||
"extraInfo": {
|
||||
"created": 1745495349299,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_jpm4v1e92v4nq650wlqkhm56",
|
||||
"index": 0,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather_by_city_code",
|
||||
"arguments": "{\"city_code\":101210101}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1745495350097,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_jpm4v1e92v4nq650wlqkhm56",
|
||||
"content": "[{\"type\":\"text\",\"text\":\"CityWeather(city_name_en='hangzhou', city_name_cn='杭州', city_code='101210101', temp='18.3', wd='', ws='', sd='89%', aqi='57', weather='阴')\"}]",
|
||||
"extraInfo": {
|
||||
"created": 1745495350319,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "杭州的最新天气信息如下:\n\n- 城市:杭州\n- 温度:18.3°C\n- 天气状况:阴\n- 湿度:89%\n- 空气质量指数 (AQI):57(良好)\n\n天气较为凉爽,适合外出活动!",
|
||||
"extraInfo": {
|
||||
"created": 1745495352693,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user