支持一次对话同时调用多个工具
This commit is contained in:
parent
da482e26a9
commit
4c0566b470
@ -1,6 +1,5 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
|
||||||
## [main] 0.0.8
|
## [main] 0.0.8
|
||||||
- 大模型 API 测试时更加完整的报错
|
- 大模型 API 测试时更加完整的报错
|
||||||
- 修复 0.0.7 引入的bug:修改对话无法发出
|
- 修复 0.0.7 引入的bug:修改对话无法发出
|
||||||
@ -8,6 +7,7 @@
|
|||||||
- 修复 bug:富文本编辑器发送前缀为空的字符会全部为空
|
- 修复 bug:富文本编辑器发送前缀为空的字符会全部为空
|
||||||
- 修复 bug:流式传输进行 function calling 时,多工具的索引串流导致的 JSON Schema 反序列化失败
|
- 修复 bug:流式传输进行 function calling 时,多工具的索引串流导致的 JSON Schema 反序列化失败
|
||||||
- 修复 bug:大模型返回大量重复错误信息
|
- 修复 bug:大模型返回大量重复错误信息
|
||||||
|
- 新特性:支持一次对话同时调用多个工具
|
||||||
|
|
||||||
## [main] 0.0.7
|
## [main] 0.0.7
|
||||||
- 优化页面布局,使得调试窗口可以显示更多内容
|
- 优化页面布局,使得调试窗口可以显示更多内容
|
||||||
|
@ -131,6 +131,10 @@ a {
|
|||||||
height: 5px;
|
height: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-arguments .openmcp-code-block pre code::-webkit-scrollbar {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
.tool-arguments .openmcp-code-block pre code {
|
.tool-arguments .openmcp-code-block pre code {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ export interface IExtraInfo {
|
|||||||
|
|
||||||
export interface ToolMessage {
|
export interface ToolMessage {
|
||||||
role: 'tool';
|
role: 'tool';
|
||||||
|
index: number;
|
||||||
content: ToolCallContent[];
|
content: ToolCallContent[];
|
||||||
tool_call_id?: string
|
tool_call_id?: string
|
||||||
name?: string // 工具名称,当 role 为 tool
|
name?: string // 工具名称,当 role 为 tool
|
||||||
@ -95,15 +96,24 @@ export type RichTextItem = PromptTextItem | ResourceTextItem | TextItem;
|
|||||||
|
|
||||||
export const allTools = ref<ToolItem[]>([]);
|
export const allTools = ref<ToolItem[]>([]);
|
||||||
|
|
||||||
export interface IRenderMessage {
|
export interface ICommonRenderMessage {
|
||||||
role: 'user' | 'assistant/content' | 'assistant/tool_calls' | 'tool';
|
role: 'user' | 'assistant/content';
|
||||||
content: string;
|
content: string;
|
||||||
toolResult?: ToolCallContent[];
|
|
||||||
tool_calls?: ToolCall[];
|
|
||||||
showJson?: Ref<boolean>;
|
showJson?: Ref<boolean>;
|
||||||
extraInfo: IExtraInfo;
|
extraInfo: IExtraInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IToolRenderMessage {
|
||||||
|
role: 'assistant/tool_calls';
|
||||||
|
content: string;
|
||||||
|
toolResults: ToolCallContent[][];
|
||||||
|
tool_calls: ToolCall[];
|
||||||
|
showJson?: Ref<boolean>;
|
||||||
|
extraInfo: IExtraInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage;
|
||||||
|
|
||||||
export function getToolSchema(enableTools: EnableToolItem[]) {
|
export function getToolSchema(enableTools: EnableToolItem[]) {
|
||||||
const toolsSchema = [];
|
const toolsSchema = [];
|
||||||
for (let i = 0; i < enableTools.length; i++) {
|
for (let i = 0; i < enableTools.length; i++) {
|
||||||
|
@ -65,6 +65,23 @@ const chatContext = inject('chatContext') as any;
|
|||||||
|
|
||||||
chatContext.handleSend = handleSend;
|
chatContext.handleSend = handleSend;
|
||||||
|
|
||||||
|
function clearErrorMessage(errorMessage: string) {
|
||||||
|
try {
|
||||||
|
const errorObject = JSON.parse(errorMessage);
|
||||||
|
if (errorObject.error) {
|
||||||
|
return errorObject.error;
|
||||||
|
}
|
||||||
|
if (errorObject.message) {
|
||||||
|
return errorObject.message;
|
||||||
|
}
|
||||||
|
if (errorObject.msg) {
|
||||||
|
return errorObject.msg;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleSend(newMessage?: string) {
|
function handleSend(newMessage?: string) {
|
||||||
// 将富文本信息转换成纯文本信息
|
// 将富文本信息转换成纯文本信息
|
||||||
const userMessage = newMessage || userInput.value;
|
const userMessage = newMessage || userInput.value;
|
||||||
@ -79,13 +96,16 @@ function handleSend(newMessage?: string) {
|
|||||||
loop = new TaskLoop(streamingContent, streamingToolCalls);
|
loop = new TaskLoop(streamingContent, streamingToolCalls);
|
||||||
|
|
||||||
loop.registerOnError((error) => {
|
loop.registerOnError((error) => {
|
||||||
|
console.log('error.msg');
|
||||||
|
console.log(error.msg);
|
||||||
|
|
||||||
ElMessage.error(error.msg);
|
const errorMessage = clearErrorMessage(error.msg);
|
||||||
|
ElMessage.error(errorMessage);
|
||||||
|
|
||||||
if (error.state === MessageState.ReceiveChunkError) {
|
if (error.state === MessageState.ReceiveChunkError) {
|
||||||
tabStorage.messages.push({
|
tabStorage.messages.push({
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: error.msg,
|
content: errorMessage,
|
||||||
extraInfo: {
|
extraInfo: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
state: error.state,
|
state: error.state,
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
import { ToolCallResponse } from "@/hook/type";
|
||||||
|
import { callTool } from "../../tool/tools";
|
||||||
|
import { MessageState, ToolCall } from "../chat-box/chat";
|
||||||
|
|
||||||
|
export async function handleToolCalls(toolCall: ToolCall) {
|
||||||
|
// 反序列化 streaming 来的参数字符串
|
||||||
|
const toolName = toolCall.function.name;
|
||||||
|
const argsResult = deserializeToolCallResponse(toolCall.function.arguments);
|
||||||
|
|
||||||
|
if (argsResult.error) {
|
||||||
|
return {
|
||||||
|
content: [{
|
||||||
|
type: 'error',
|
||||||
|
text: parseErrorObject(argsResult.error)
|
||||||
|
}],
|
||||||
|
state: MessageState.ParseJsonError
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolArgs = argsResult.value;
|
||||||
|
|
||||||
|
// 进行调用,根据结果返回不同的值
|
||||||
|
const toolResponse = await callTool(toolName, toolArgs);
|
||||||
|
return handleToolResponse(toolResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserializeToolCallResponse(toolArgs: string) {
|
||||||
|
try {
|
||||||
|
const args = JSON.parse(toolArgs);
|
||||||
|
return {
|
||||||
|
value: args,
|
||||||
|
error: undefined
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
value: undefined,
|
||||||
|
error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToolResponse(toolResponse: ToolCallResponse) {
|
||||||
|
if (typeof toolResponse === 'string') {
|
||||||
|
// 如果是 string,说明是错误信息
|
||||||
|
console.log(toolResponse);
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{
|
||||||
|
type: 'error',
|
||||||
|
text: toolResponse
|
||||||
|
}],
|
||||||
|
state: MessageState.ToolCall
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (!toolResponse.isError) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: toolResponse.content,
|
||||||
|
state: MessageState.Success
|
||||||
|
};
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: toolResponse.content,
|
||||||
|
state: MessageState.ToolCall
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseErrorObject(error: any): string {
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return error;
|
||||||
|
} else if (typeof error === 'object') {
|
||||||
|
return JSON.stringify(error, null, 2);
|
||||||
|
} else {
|
||||||
|
return error.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import { callTool } from "../../tool/tools";
|
|||||||
import { llmManager, llms } from "@/views/setting/llm";
|
import { llmManager, llms } from "@/views/setting/llm";
|
||||||
import { pinkLog, redLog } from "@/views/setting/util";
|
import { pinkLog, redLog } from "@/views/setting/util";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
|
import { handleToolCalls } from "./handle-tool-calls";
|
||||||
|
|
||||||
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
||||||
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
|
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
|
||||||
@ -44,87 +45,6 @@ export class TaskLoop {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleToolCalls(toolCalls: ToolCall[]) {
|
|
||||||
// TODO: 调用多个工具并返回调用结果?
|
|
||||||
|
|
||||||
const toolCall = toolCalls[0];
|
|
||||||
|
|
||||||
console.log('debug toolcall');
|
|
||||||
console.log(toolCalls);
|
|
||||||
|
|
||||||
let toolName: string;
|
|
||||||
let toolArgs: Record<string, any>;
|
|
||||||
|
|
||||||
try {
|
|
||||||
toolName = toolCall.function.name;
|
|
||||||
toolArgs = JSON.parse(toolCall.function.arguments);
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
content: [{
|
|
||||||
type: 'error',
|
|
||||||
text: this.parseErrorObject(error)
|
|
||||||
}],
|
|
||||||
state: MessageState.ParseJsonError
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
const toolResponse = await callTool(toolName, toolArgs);
|
|
||||||
|
|
||||||
console.log(toolResponse);
|
|
||||||
|
|
||||||
if (typeof toolResponse === 'string') {
|
|
||||||
console.log(toolResponse);
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [{
|
|
||||||
type: 'error',
|
|
||||||
text: toolResponse
|
|
||||||
}],
|
|
||||||
state: MessageState.ToolCall
|
|
||||||
}
|
|
||||||
} else if (!toolResponse.isError) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: toolResponse.content,
|
|
||||||
state: MessageState.Success
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: toolResponse.content,
|
|
||||||
state: MessageState.ToolCall
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.onError({
|
|
||||||
state: MessageState.ToolCall,
|
|
||||||
msg: `工具调用失败: ${(error as Error).message}`
|
|
||||||
});
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [{
|
|
||||||
type: 'error',
|
|
||||||
text: this.parseErrorObject(error)
|
|
||||||
}],
|
|
||||||
state: MessageState.ToolCall
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseErrorObject(error: any): string {
|
|
||||||
if (typeof error === 'string') {
|
|
||||||
return error;
|
|
||||||
} else if (typeof error === 'object') {
|
|
||||||
return JSON.stringify(error, null, 2);
|
|
||||||
} else {
|
|
||||||
return error.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleChunkDeltaContent(chunk: ChatCompletionChunk) {
|
private handleChunkDeltaContent(chunk: ChatCompletionChunk) {
|
||||||
const content = chunk.choices[0]?.delta?.content || '';
|
const content = chunk.choices[0]?.delta?.content || '';
|
||||||
if (content) {
|
if (content) {
|
||||||
@ -199,12 +119,7 @@ export class TaskLoop {
|
|||||||
});
|
});
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
|
|
||||||
console.log('register error handler');
|
|
||||||
|
|
||||||
const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => {
|
const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => {
|
||||||
|
|
||||||
console.log('enter error report');
|
|
||||||
|
|
||||||
this.onError({
|
this.onError({
|
||||||
state: MessageState.ReceiveChunkError,
|
state: MessageState.ReceiveChunkError,
|
||||||
msg: data.msg || '请求模型服务时发生错误'
|
msg: data.msg || '请求模型服务时发生错误'
|
||||||
@ -219,6 +134,8 @@ export class TaskLoop {
|
|||||||
|
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
|
|
||||||
|
console.log(chatData);
|
||||||
|
|
||||||
this.bridge.postMessage({
|
this.bridge.postMessage({
|
||||||
command: 'llm/chat/completions',
|
command: 'llm/chat/completions',
|
||||||
data: JSON.parse(JSON.stringify(chatData)),
|
data: JSON.parse(JSON.stringify(chatData)),
|
||||||
@ -360,59 +277,58 @@ export class TaskLoop {
|
|||||||
|
|
||||||
pinkLog('调用工具数量:' + this.streamingToolCalls.value.length);
|
pinkLog('调用工具数量:' + this.streamingToolCalls.value.length);
|
||||||
|
|
||||||
const toolCallResult = await this.handleToolCalls(this.streamingToolCalls.value);
|
for (const toolCall of this.streamingToolCalls.value || []) {
|
||||||
|
const toolCallResult = await handleToolCalls(toolCall);
|
||||||
|
|
||||||
console.log('toolCallResult', toolCallResult);
|
if (toolCallResult.state === MessageState.ParseJsonError) {
|
||||||
|
// 如果是因为解析 JSON 错误,则重新开始
|
||||||
|
tabStorage.messages.pop();
|
||||||
|
jsonParseErrorRetryCount ++;
|
||||||
|
|
||||||
if (toolCallResult.state === MessageState.ParseJsonError) {
|
redLog('解析 JSON 错误 ' + toolCall?.function?.arguments);
|
||||||
// 如果是因为解析 JSON 错误,则重新开始
|
|
||||||
tabStorage.messages.pop();
|
|
||||||
jsonParseErrorRetryCount ++;
|
|
||||||
|
|
||||||
redLog('解析 JSON 错误 ' + this.streamingToolCalls.value[0]?.function?.arguments);
|
// 如果因为 JSON 错误而失败太多,就只能中断了
|
||||||
|
if (jsonParseErrorRetryCount >= this.taskOptions.maxJsonParseRetry) {
|
||||||
// 如果因为 JSON 错误而失败太多,就只能中断了
|
tabStorage.messages.push({
|
||||||
if (jsonParseErrorRetryCount >= this.taskOptions.maxJsonParseRetry) {
|
role: 'assistant',
|
||||||
|
content: `解析 JSON 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`,
|
||||||
|
extraInfo: {
|
||||||
|
created: Date.now(),
|
||||||
|
state: toolCallResult.state,
|
||||||
|
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
|
||||||
|
usage: undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (toolCallResult.state === MessageState.Success) {
|
||||||
tabStorage.messages.push({
|
tabStorage.messages.push({
|
||||||
role: 'assistant',
|
role: 'tool',
|
||||||
content: `解析 JSON 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`,
|
index: toolCall.index || 0,
|
||||||
|
tool_call_id: toolCall.id || toolCall.function.name,
|
||||||
|
content: toolCallResult.content,
|
||||||
extraInfo: {
|
extraInfo: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
state: toolCallResult.state,
|
state: toolCallResult.state,
|
||||||
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
|
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
|
||||||
usage: undefined
|
usage: this.completionUsage
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (toolCallResult.state === MessageState.ToolCall) {
|
||||||
|
|
||||||
|
tabStorage.messages.push({
|
||||||
|
role: 'tool',
|
||||||
|
index: toolCall.index || 0,
|
||||||
|
tool_call_id: toolCall.id || toolCall.function.name,
|
||||||
|
content: toolCallResult.content,
|
||||||
|
extraInfo: {
|
||||||
|
created: Date.now(),
|
||||||
|
state: toolCallResult.state,
|
||||||
|
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
|
||||||
|
usage: this.completionUsage
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else if (toolCallResult.state === MessageState.Success) {
|
|
||||||
const toolCall = this.streamingToolCalls.value[0];
|
|
||||||
|
|
||||||
tabStorage.messages.push({
|
|
||||||
role: 'tool',
|
|
||||||
tool_call_id: toolCall.id || toolCall.function.name,
|
|
||||||
content: toolCallResult.content,
|
|
||||||
extraInfo: {
|
|
||||||
created: Date.now(),
|
|
||||||
state: toolCallResult.state,
|
|
||||||
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
|
|
||||||
usage: this.completionUsage
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (toolCallResult.state === MessageState.ToolCall) {
|
|
||||||
const toolCall = this.streamingToolCalls.value[0];
|
|
||||||
|
|
||||||
tabStorage.messages.push({
|
|
||||||
role: 'tool',
|
|
||||||
tool_call_id: toolCall.id || toolCall.function.name,
|
|
||||||
content: toolCallResult.content,
|
|
||||||
extraInfo: {
|
|
||||||
created: Date.now(),
|
|
||||||
state: toolCallResult.state,
|
|
||||||
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
|
|
||||||
usage: this.completionUsage
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (this.streamingContent.value) {
|
} else if (this.streamingContent.value) {
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<div class="message-content" v-else-if="message.role === 'assistant/tool_calls'">
|
<div class="message-content" v-else-if="message.role === 'assistant/tool_calls'">
|
||||||
<Message.Toolcall
|
<Message.Toolcall
|
||||||
:message="message" :tab-id="props.tabId"
|
:message="message" :tab-id="props.tabId"
|
||||||
@update:tool-result="(value, index) => (message.toolResult || [])[index] = value"
|
@update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -97,6 +97,7 @@ const renderMessages = computed(() => {
|
|||||||
messages.push({
|
messages.push({
|
||||||
role: 'assistant/tool_calls',
|
role: 'assistant/tool_calls',
|
||||||
content: message.content,
|
content: message.content,
|
||||||
|
toolResults: Array(message.tool_calls.length).fill([]),
|
||||||
tool_calls: message.tool_calls,
|
tool_calls: message.tool_calls,
|
||||||
showJson: ref(false),
|
showJson: ref(false),
|
||||||
extraInfo: {
|
extraInfo: {
|
||||||
@ -116,8 +117,16 @@ const renderMessages = computed(() => {
|
|||||||
// 如果是工具,则合并进入 之前 assistant 一起渲染
|
// 如果是工具,则合并进入 之前 assistant 一起渲染
|
||||||
const lastAssistantMessage = messages[messages.length - 1];
|
const lastAssistantMessage = messages[messages.length - 1];
|
||||||
if (lastAssistantMessage.role === 'assistant/tool_calls') {
|
if (lastAssistantMessage.role === 'assistant/tool_calls') {
|
||||||
lastAssistantMessage.toolResult = message.content;
|
lastAssistantMessage.toolResults[message.index] = message.content;
|
||||||
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
|
|
||||||
|
if (lastAssistantMessage.extraInfo.state === MessageState.Unknown) {
|
||||||
|
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
|
||||||
|
} else if (lastAssistantMessage.extraInfo.state === MessageState.Success
|
||||||
|
|| message.extraInfo.state !== MessageState.Success
|
||||||
|
) {
|
||||||
|
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
|
||||||
|
}
|
||||||
|
|
||||||
lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage;
|
lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="message-role">
|
<div class="message-role">
|
||||||
<span class="message-reminder" v-if="!props.message.toolResult">
|
<span class="message-reminder" v-if="callingTools">
|
||||||
Agent 正在使用工具
|
Agent 正在使用工具
|
||||||
<span class="tool-loading iconfont icon-double-loading">
|
<span class="tool-loading iconfont icon-double-loading">
|
||||||
</span>
|
</span>
|
||||||
@ -26,58 +26,77 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div>
|
<div v-for="(toolResult, toolIndex) in props.message.toolResults" :key="toolIndex"
|
||||||
<div class="tool-arguments">
|
class="toolcall-item">
|
||||||
<div class="inner">
|
|
||||||
<div v-html="jsonResultToHtml(props.message.tool_calls[0].function.arguments)"></div>
|
<div class="tool-calls" v-if="toolIndex > 0">
|
||||||
|
<div class="tool-call-header">
|
||||||
|
<span class="tool-name">
|
||||||
|
<span class="iconfont icon-tool"></span>
|
||||||
|
|
||||||
|
{{ props.message.tool_calls[toolIndex].function.name }}
|
||||||
|
</span>
|
||||||
|
<el-button size="small" @click="createTest(props.message.tool_calls[toolIndex])">
|
||||||
|
<span class="iconfont icon-send"></span>
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="tool-arguments">
|
||||||
|
<el-scrollbar width="100%">
|
||||||
|
<div class="inner">
|
||||||
|
<div v-html="jsonResultToHtml(props.message.tool_calls[toolIndex].function.arguments)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 工具调用结果 -->
|
<!-- 工具调用结果 -->
|
||||||
<div v-if="props.message.toolResult">
|
<div v-if="toolResult.length > 0">
|
||||||
<div class="tool-call-header result">
|
<div class="tool-call-header result">
|
||||||
<span class="tool-name">
|
|
||||||
|
<span class="tool-name" v-if="isValid(toolResult)">
|
||||||
|
<span :class="`iconfont icon-info`"></span>
|
||||||
|
{{ t("response") }}
|
||||||
|
</span>
|
||||||
|
<span class="tool-name" v-else>
|
||||||
<span :class="`iconfont icon-${currentMessageLevel}`"></span>
|
<span :class="`iconfont icon-${currentMessageLevel}`"></span>
|
||||||
{{ isValid ? t("response") : t('error') }}
|
{{ isValid(toolResult) ? t("response") : t('error') }}
|
||||||
<el-button v-if="!isValid" size="small"
|
<el-button size="small" @click="gotoIssue()">
|
||||||
@click="gotoIssue()"
|
|
||||||
>
|
|
||||||
{{ t('feedback') }}
|
{{ t('feedback') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
<span style="width: 200px;" class="tools-dialog-container" v-if="currentMessageLevel === 'info'">
|
|
||||||
|
|
||||||
|
<span style="width: 200px;" class="tools-dialog-container"
|
||||||
|
v-if="currentMessageLevel === 'info'">
|
||||||
<el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON"
|
<el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON"
|
||||||
inactive-text="Text" style="margin-left: 10px; width: 200px;"
|
inactive-text="Text" style="margin-left: 10px; width: 200px;"
|
||||||
:inactive-action-style="'backgroundColor: var(--sidebar)'" />
|
:inactive-action-style="'backgroundColor: var(--sidebar)'" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tool-result" v-if="isValid">
|
<div class="tool-result" v-if="isValid(toolResult)">
|
||||||
<!-- 展示 JSON -->
|
<!-- 展示 JSON -->
|
||||||
<div v-if="props.message.showJson!.value" class="tool-result-content">
|
<div v-if="props.message.showJson!.value" class="tool-result-content">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<div v-html="toHtml(props.message.toolResult)"></div>
|
<div v-html="toHtml(props.message.toolResults[toolIndex])"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 展示富文本 -->
|
<!-- 展示富文本 -->
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<div v-for="(item, index) in props.message.toolResult" :key="index"
|
<div v-for="(item, index) in props.message.toolResults[toolIndex]" :key="index"
|
||||||
class="response-item"
|
class="response-item">
|
||||||
>
|
<ToolcallResultItem :item="item"
|
||||||
<ToolcallResultItem
|
@update:item="value => updateToolCallResultItem(value, toolIndex, index)"
|
||||||
:item="item"
|
@update:ocr-done="value => collposePanel()" />
|
||||||
@update:item="value => updateToolCallResultItem(value, index)"
|
|
||||||
@update:ocr-done="value => collposePanel()"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="tool-result">
|
<div v-else class="tool-result">
|
||||||
<div class="tool-result-content"
|
<div class="tool-result-content" v-for="(error, index) of collectErrors(toolResult)"
|
||||||
v-for="(error, index) of collectErrors"
|
:key="index">
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -91,17 +110,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="tool-result-content">
|
<div class="tool-result-content">
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<el-progress
|
<el-progress :percentage="100" :format="() => ''" :indeterminate="true" text-inside />
|
||||||
:percentage="100"
|
|
||||||
:format="() => ''"
|
|
||||||
:indeterminate="true"
|
|
||||||
text-inside
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MessageMeta :message="message" />
|
<MessageMeta v-if="toolIndex === props.message.toolResults.length - 1" :message="message" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</el-collapse-item>
|
</el-collapse-item>
|
||||||
@ -110,13 +124,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, ref, watch, PropType, computed, defineEmits, inject, Ref } from 'vue';
|
import { defineProps, ref, watch, PropType, computed, defineEmits } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import MessageMeta from './message-meta.vue';
|
import MessageMeta from './message-meta.vue';
|
||||||
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
|
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
|
||||||
import { createTest } from '@/views/setting/llm';
|
import { createTest } from '@/views/setting/llm';
|
||||||
import { IRenderMessage, MessageState } from '../chat-box/chat';
|
import { IToolRenderMessage, MessageState } from '../chat-box/chat';
|
||||||
import { ToolCallContent } from '@/hook/type';
|
import { ToolCallContent } from '@/hook/type';
|
||||||
|
|
||||||
import ToolcallResultItem from './toolcall-result-item.vue';
|
import ToolcallResultItem from './toolcall-result-item.vue';
|
||||||
@ -125,7 +139,7 @@ const { t } = useI18n();
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
message: {
|
message: {
|
||||||
type: Object as PropType<IRenderMessage>,
|
type: Object as PropType<IToolRenderMessage>,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
tabId: {
|
tabId: {
|
||||||
@ -135,20 +149,38 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const hasOcr = computed(() => {
|
const hasOcr = computed(() => {
|
||||||
for (const item of props.message.toolResult || []) {
|
|
||||||
const metaInfo = item._meta || {};
|
if (props.message.role === 'assistant/tool_calls') {
|
||||||
const { ocr = false } = metaInfo;
|
for (const toolResult of props.message.toolResults) {
|
||||||
if (ocr) {
|
for (const item of toolResult) {
|
||||||
return true;
|
const metaInfo = item._meta || {};
|
||||||
|
const { ocr = false } = metaInfo;
|
||||||
|
if (ocr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeNames = ref<string[]>(props.message.toolResult ? [''] : ['tool']);
|
|
||||||
|
const callingTools = computed(() => {
|
||||||
|
const emptyToolResult = props.message.toolResults.find(item => item.length === 0);
|
||||||
|
|
||||||
|
if (emptyToolResult) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const activeNames = ref<string[]>(callingTools.value ? ['tool']: []);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.message.toolResult,
|
() => props.message,
|
||||||
(value, _) => {
|
(value, _) => {
|
||||||
if (hasOcr.value) {
|
if (hasOcr.value) {
|
||||||
return;
|
return;
|
||||||
@ -191,9 +223,10 @@ function gotoIssue() {
|
|||||||
window.open('https://github.com/LSTM-Kirigaya/openmcp-client/issues', '_blank');
|
window.open('https://github.com/LSTM-Kirigaya/openmcp-client/issues', '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid = computed(() => {
|
|
||||||
|
function isValid(toolResult: ToolCallContent[]) {
|
||||||
try {
|
try {
|
||||||
const item = props.message.toolResult![0];
|
const item = toolResult[0];
|
||||||
if (item.type === 'error') {
|
if (item.type === 'error') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -201,29 +234,34 @@ const isValid = computed(() => {
|
|||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
|
||||||
const currentMessageLevel = computed(() => {
|
const currentMessageLevel = computed(() => {
|
||||||
|
|
||||||
// 此时正在等待 mcp server 给出回应
|
// 此时正在等待 mcp server 给出回应
|
||||||
if (!props.message.toolResult) {
|
for (const toolResult of props.message.toolResults) {
|
||||||
return 'info';
|
if (toolResult.length === 0) {
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid(toolResult)) {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValid.value) {
|
if (props.message.extraInfo.state !== MessageState.Success) {
|
||||||
return 'error';
|
|
||||||
}
|
|
||||||
if (props.message.extraInfo.state != MessageState.Success) {
|
|
||||||
return 'warning';
|
return 'warning';
|
||||||
}
|
}
|
||||||
return 'info';
|
|
||||||
})
|
|
||||||
|
|
||||||
const collectErrors = computed(() => {
|
return 'info';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function collectErrors(toolResult: ToolCallContent[]) {
|
||||||
const errorMessages = [];
|
const errorMessages = [];
|
||||||
try {
|
try {
|
||||||
const errorResults = props.message.toolResult!.filter(item => item.type === 'error');
|
const errorResults = toolResult.filter(item => item.type === 'error');
|
||||||
console.log(errorResults);
|
console.log(errorResults);
|
||||||
|
|
||||||
for (const errorResult of errorResults) {
|
for (const errorResult of errorResults) {
|
||||||
@ -233,12 +271,12 @@ const collectErrors = computed(() => {
|
|||||||
} catch {
|
} catch {
|
||||||
return errorMessages;
|
return errorMessages;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
const emit = defineEmits(['update:tool-result']);
|
const emits = defineEmits(['update:tool-result']);
|
||||||
|
|
||||||
function updateToolCallResultItem(value: any, index: number) {
|
function updateToolCallResultItem(value: any, toolIndex: number, index: number) {
|
||||||
emit('update:tool-result', value, index);
|
emits('update:tool-result', value, toolIndex, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@ -250,9 +288,6 @@ function updateToolCallResultItem(value: any, index: number) {
|
|||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-result-content .el-progress-bar__outer {
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-result-content .progress {
|
.tool-result-content .progress {
|
||||||
border-radius: .5em;
|
border-radius: .5em;
|
||||||
background-color: var(--el-fill-color-light) !important;
|
background-color: var(--el-fill-color-light) !important;
|
||||||
@ -269,7 +304,7 @@ function updateToolCallResultItem(value: any, index: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-text.tool_calls.warning .tool-result {
|
.message-text.tool_calls.warning .tool-result {
|
||||||
background-color: rgba(230, 162, 60, 0.5);
|
background-color: rgba(230, 162, 60, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text.tool_calls.error {
|
.message-text.tool_calls.error {
|
||||||
@ -281,7 +316,7 @@ function updateToolCallResultItem(value: any, index: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-text.tool_calls.error .tool-result {
|
.message-text.tool_calls.error .tool-result {
|
||||||
background-color: rgba(245, 108, 108, 0.5);
|
background-color: rgba(245, 108, 108, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -295,6 +330,9 @@ function updateToolCallResultItem(value: any, index: number) {
|
|||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolcall-item .tool-calls {
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
.tool-call-item {
|
.tool-call-item {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -30,9 +30,6 @@ export function callTool(toolName: string, toolArgs: Record<string, any>) {
|
|||||||
}
|
}
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
|
|
||||||
pinkLog('callTool');
|
|
||||||
console.log(toolArgs);
|
|
||||||
|
|
||||||
bridge.postMessage({
|
bridge.postMessage({
|
||||||
command: 'tools/call',
|
command: 'tools/call',
|
||||||
data: {
|
data: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user