给 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">
|
<el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll" v-if="renderMessages.length > 0 || isLoading">
|
||||||
<div class="message-list" :ref="el => messageListRef = el">
|
<div class="message-list" :ref="el => messageListRef = el">
|
||||||
<div v-for="(message, index) in renderMessages" :key="index"
|
<div v-for="(message, index) in renderMessages" :key="index"
|
||||||
:class="['message-item', message.role.split('/')[0]]">
|
:class="['message-item', message.role.split('/')[0]]"
|
||||||
<div class="message-avatar" v-if="message.role.split('/')[0] === 'assistant'">
|
>
|
||||||
|
<div class="message-avatar" v-if="message.role.startsWith('assistant')">
|
||||||
<span class="iconfont icon-robot"></span>
|
<span class="iconfont icon-robot"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -131,6 +132,7 @@ interface IRenderMessage {
|
|||||||
tool_calls?: ToolCall[];
|
tool_calls?: ToolCall[];
|
||||||
showJson?: Ref<boolean>;
|
showJson?: Ref<boolean>;
|
||||||
extraInfo: IExtraInfo;
|
extraInfo: IExtraInfo;
|
||||||
|
isLast: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderMessages = computed(() => {
|
const renderMessages = computed(() => {
|
||||||
@ -140,7 +142,8 @@ const renderMessages = computed(() => {
|
|||||||
messages.push({
|
messages.push({
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: message.content,
|
content: message.content,
|
||||||
extraInfo: message.extraInfo
|
extraInfo: message.extraInfo,
|
||||||
|
isLast: false
|
||||||
});
|
});
|
||||||
} else if (message.role === 'assistant') {
|
} else if (message.role === 'assistant') {
|
||||||
if (message.tool_calls) {
|
if (message.tool_calls) {
|
||||||
@ -149,13 +152,15 @@ const renderMessages = computed(() => {
|
|||||||
content: message.content,
|
content: message.content,
|
||||||
tool_calls: message.tool_calls,
|
tool_calls: message.tool_calls,
|
||||||
showJson: ref(false),
|
showJson: ref(false),
|
||||||
extraInfo: message.extraInfo
|
extraInfo: message.extraInfo,
|
||||||
|
isLast: false
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
messages.push({
|
messages.push({
|
||||||
role: 'assistant/content',
|
role: 'assistant/content',
|
||||||
content: message.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;
|
return messages;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -375,11 +385,6 @@ onUnmounted(() => {
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text.tool_calls {
|
|
||||||
border-left: 3px solid var(--main-color);
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user .message-text {
|
.user .message-text {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -1,64 +1,72 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="message-role">
|
<div class="message-role">
|
||||||
Agent
|
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 class="tool-loading iconfont icon-double-loading">
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
<div v-if="props.message.content" v-html="markdownToHtml(props.message.content)"></div>
|
||||||
|
|
||||||
<div class="tool-calls">
|
<el-collapse v-model="activeNames">
|
||||||
<div v-for="(call, index) in props.message.tool_calls" :key="index" class="tool-call-item">
|
<el-collapse-item name="tool">
|
||||||
<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="iconfont icon-send"></span>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<div class="tool-arguments">
|
|
||||||
<div class="inner">
|
|
||||||
<div v-html="jsonResultToHtml(call.function.arguments)"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 工具调用结果 -->
|
<template #title>
|
||||||
<div v-if="props.message.toolResult">
|
<div class="tool-calls">
|
||||||
<div class="tool-call-header">
|
<div class="tool-call-header">
|
||||||
<span class="tool-name">{{ "响应" }}</span>
|
<span class="tool-type">{{ 'tool use' }}</span>
|
||||||
<span style="width: 200px;" class="tools-dialog-container">
|
<span class="tool-name">{{ props.message.tool_calls[0].function.name }}</span>
|
||||||
<el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON" inactive-text="Text"
|
<el-button size="small" @click="createTest(props.message.tool_calls[0])">
|
||||||
style="margin-left: 10px; width: 200px;"
|
<span class="iconfont icon-send"></span>
|
||||||
:inactive-action-style="'backgroundColor: var(--sidebar)'" />
|
</el-button>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tool-result" v-if="isValidJSON(props.message.toolResult)">
|
</template>
|
||||||
<div v-if="props.message.showJson!.value" class="tool-result-content">
|
|
||||||
<div class="inner">
|
<div>
|
||||||
<div v-html="jsonResultToHtml(props.message.toolResult)"></div>
|
<div class="tool-arguments">
|
||||||
|
<div class="inner">
|
||||||
|
<div v-html="jsonResultToHtml(props.message.tool_calls[0].function.arguments)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 工具调用结果 -->
|
||||||
|
<div v-if="props.message.toolResult">
|
||||||
|
<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;"
|
||||||
|
:inactive-action-style="'backgroundColor: var(--sidebar)'" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="tool-result" v-if="isValidJSON(props.message.toolResult)">
|
||||||
|
<div v-if="props.message.showJson!.value" class="tool-result-content">
|
||||||
|
<div class="inner">
|
||||||
|
<div v-html="jsonResultToHtml(props.message.toolResult)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span v-else>
|
||||||
|
<div v-for="(item, index) in JSON.parse(props.message.toolResult)" :key="index">
|
||||||
|
<el-scrollbar width="100%">
|
||||||
|
<div v-if="item.type === 'text'" class="tool-text">{{ item.text }}</div>
|
||||||
|
<div v-else class="tool-other">{{ JSON.stringify(item) }}</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span v-else>
|
</el-collapse-item>
|
||||||
<div v-for="(item, index) in JSON.parse(props.message.toolResult)" :key="index">
|
</el-collapse>
|
||||||
<el-scrollbar width="100%">
|
|
||||||
<div v-if="item.type === 'text'" class="tool-text">{{ item.text }}</div>
|
|
||||||
<div v-else class="tool-other">{{ JSON.stringify(item) }}</div>
|
|
||||||
</el-scrollbar>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<MessageMeta :message="message" />
|
<MessageMeta :message="message" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps } from 'vue';
|
import { defineProps, ref, watch } from 'vue';
|
||||||
|
|
||||||
import MessageMeta from './message-meta.vue';
|
import MessageMeta from './message-meta.vue';
|
||||||
import { markdownToHtml } from '../markdown';
|
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 jsonResultToHtml = (jsonString: string) => {
|
||||||
const formattedJson = JSON.stringify(JSON.parse(jsonString), null, 2);
|
const formattedJson = JSON.stringify(JSON.parse(jsonString), null, 2);
|
||||||
const html = markdownToHtml('```json\n' + formattedJson + '\n```');
|
const html = markdownToHtml('```json\n' + formattedJson + '\n```');
|
||||||
@ -89,6 +110,14 @@ const isValidJSON = (str: string) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<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 {
|
.tool-calls {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
@ -123,6 +152,7 @@ const isValidJSON = (str: string) => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-arguments {
|
.tool-arguments {
|
||||||
@ -151,6 +181,4 @@ const isValidJSON = (str: string) => {
|
|||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
@ -18,7 +18,7 @@ export function createTest(call: ToolCall) {
|
|||||||
tab.component = markRaw(debugModes[2]);
|
tab.component = markRaw(debugModes[2]);
|
||||||
tab.icon = 'icon-tool';
|
tab.icon = 'icon-tool';
|
||||||
tab.name = t("tools");
|
tab.name = t("tools");
|
||||||
1
|
|
||||||
const storage: ToolStorage = {
|
const storage: ToolStorage = {
|
||||||
currentToolName: call.function.name,
|
currentToolName: call.function.name,
|
||||||
formData: JSON.parse(call.function.arguments)
|
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
|
"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": {
|
"settings": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user