From da482e26a9a2a9f5a1d479aa207c335881b185cb Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Thu, 8 May 2025 19:33:38 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E8=BD=AEbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 9 ++ package.json | 2 +- renderer/public/iconfont.css | 10 +- renderer/public/iconfont.woff2 | Bin 6512 -> 6700 bytes .../main-panel/chat/chat-box/index.vue | 11 +- .../chat/chat-box/rich-textarea.vue | 27 +++++ .../main-panel/chat/core/task-loop.ts | 99 +++++++++++++----- .../components/main-panel/chat/core/usage.ts | 12 +++ .../src/components/main-panel/chat/index.vue | 5 + .../main-panel/chat/message/assistant.vue | 15 ++- .../main-panel/chat/message/streaming-box.vue | 4 +- .../main-panel/chat/message/toolcall.vue | 45 +++++++- .../main-panel/chat/message/user.vue | 10 +- renderer/src/i18n/ar.json | 5 +- renderer/src/i18n/de.json | 5 +- renderer/src/i18n/en.json | 5 +- renderer/src/i18n/fr.json | 5 +- renderer/src/i18n/ja.json | 5 +- renderer/src/i18n/ko.json | 5 +- renderer/src/i18n/ru.json | 5 +- renderer/src/i18n/zh-cn.json | 5 +- renderer/src/i18n/zh-tw.json | 5 +- renderer/src/views/about/index.vue | 2 +- renderer/src/views/setting/api.ts | 37 +++++-- renderer/src/views/setting/api.vue | 15 ++- renderer/src/views/setting/connect-test.vue | 23 ++-- service/src/llm/llm.controller.ts | 15 ++- service/src/llm/llm.service.ts | 3 - 28 files changed, 311 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d016fb..183abd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log + +## [main] 0.0.8 +- 大模型 API 测试时更加完整的报错 +- 修复 0.0.7 引入的bug:修改对话无法发出 +- 修复 bug:富文本编辑器粘贴文本会带样式 +- 修复 bug:富文本编辑器发送前缀为空的字符会全部为空 +- 修复 bug:流式传输进行 function calling 时,多工具的索引串流导致的 JSON Schema 反序列化失败 +- 修复 bug:大模型返回大量重复错误信息 + ## [main] 0.0.7 - 优化页面布局,使得调试窗口可以显示更多内容 - 扩大默认的上下文长度 10 -> 20 diff --git a/package.json b/package.json index a869533..0b1f844 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "openmcp", "displayName": "OpenMCP", "description": "An all in one MCP Client/TestTool", - "version": "0.0.7", + "version": "0.0.8", "publisher": "kirigaya", "author": { "name": "kirigaya", diff --git a/renderer/public/iconfont.css b/renderer/public/iconfont.css index 5c04cb3..feacc0c 100644 --- a/renderer/public/iconfont.css +++ b/renderer/public/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4870215 */ - src: url('iconfont.woff2?t=1746529081655') format('woff2'), - url('iconfont.woff?t=1746529081655') format('woff'), - url('iconfont.ttf?t=1746529081655') format('truetype'); + src: url('iconfont.woff2?t=1746703816245') format('woff2'), + url('iconfont.woff?t=1746703816245') format('woff'), + url('iconfont.ttf?t=1746703816245') format('truetype'); } .iconfont { @@ -13,6 +13,10 @@ -moz-osx-font-smoothing: grayscale; } +.icon-waiting:before { + content: "\e6d0"; +} + .icon-timeout:before { content: "\edf5"; } diff --git a/renderer/public/iconfont.woff2 b/renderer/public/iconfont.woff2 index 1ba99482f0956b7d67f5725942b8a7c4a0e19e4d..45332aee8e083e4cd5867629084226a59147baa3 100644 GIT binary patch literal 6700 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 Cn%-#u literal 6512 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 W`XE)y?oi?3MEz5S66SmY0001-K7>2~ diff --git a/renderer/src/components/main-panel/chat/chat-box/index.vue b/renderer/src/components/main-panel/chat/chat-box/index.vue index 64c7313..ce4c1af 100644 --- a/renderer/src/components/main-panel/chat/chat-box/index.vue +++ b/renderer/src/components/main-panel/chat/chat-box/index.vue @@ -61,6 +61,9 @@ const streamingContent = inject('streamingContent') as Ref; const streamingToolCalls = inject('streamingToolCalls') as Ref; const scrollToBottom = inject('scrollToBottom') as () => Promise; const updateScrollHeight = inject('updateScrollHeight') as () => void; +const chatContext = inject('chatContext') as any; + +chatContext.handleSend = handleSend; function handleSend(newMessage?: string) { // 将富文本信息转换成纯文本信息 @@ -77,11 +80,7 @@ function handleSend(newMessage?: string) { loop.registerOnError((error) => { - ElMessage({ - message: error.msg, - type: 'error', - duration: 3000 - }); + ElMessage.error(error.msg); if (error.state === MessageState.ReceiveChunkError) { tabStorage.messages.push({ @@ -125,8 +124,6 @@ function handleAbort() { } } -provide('handleSend', handleSend); - onMounted(() => { updateScrollHeight(); diff --git a/renderer/src/components/main-panel/chat/chat-box/rich-textarea.vue b/renderer/src/components/main-panel/chat/chat-box/rich-textarea.vue index b0913cb..c689b58 100644 --- a/renderer/src/components/main-panel/chat/chat-box/rich-textarea.vue +++ b/renderer/src/components/main-panel/chat/chat-box/rich-textarea.vue @@ -10,6 +10,7 @@ class="rich-editor" :placeholder="placeholder" @input="handleInput" + @paste="handlePaste" @keydown.backspace="handleBackspace" @keydown.enter="handleKeydown" @compositionstart="handleCompositionStart" @@ -171,6 +172,32 @@ function handleKeydown(event: KeyboardEvent) { } } +function handlePaste(event: ClipboardEvent) { + event.preventDefault(); // 阻止默认粘贴行为 + const clipboardData = event.clipboardData; + if (clipboardData) { + const pastedText = clipboardData.getData('text/plain'); + const editorElement = editor.value; + if (editorElement instanceof HTMLDivElement) { + const selection = window.getSelection(); + if (selection && selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + range.deleteContents(); + const textNode = document.createTextNode(pastedText); + range.insertNode(textNode); + range.setStartAfter(textNode); + range.collapse(true); + selection.removeAllRanges(); + selection.addRange(range); + } + } + } + + if (editor.value) { + editor.value.dispatchEvent(new Event('input')); + } +} + function handleCompositionStart() { isComposing.value = true; } diff --git a/renderer/src/components/main-panel/chat/core/task-loop.ts b/renderer/src/components/main-panel/chat/core/task-loop.ts index 87bb65b..0d4138c 100644 --- a/renderer/src/components/main-panel/chat/core/task-loop.ts +++ b/renderer/src/components/main-panel/chat/core/task-loop.ts @@ -12,6 +12,7 @@ export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk; export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string }; interface TaskLoopOptions { maxEpochs: number; + maxJsonParseRetry: number; } interface IErrorMssage { @@ -19,6 +20,10 @@ interface IErrorMssage { msg: string } +interface IDoConversationResult { + stop: boolean; +} + /** * @description 对任务循环进行的抽象封装 */ @@ -34,15 +39,19 @@ export class TaskLoop { private onChunk: (chunk: ChatCompletionChunk) => void = (chunk) => {}, private onDone: () => void = () => {}, private onEpoch: () => void = () => {}, - private readonly taskOptions: TaskLoopOptions = { maxEpochs: 20 }, + private readonly taskOptions: TaskLoopOptions = { maxEpochs: 20, maxJsonParseRetry: 3 }, ) { } private async handleToolCalls(toolCalls: ToolCall[]) { // TODO: 调用多个工具并返回调用结果? + const toolCall = toolCalls[0]; + console.log('debug toolcall'); + console.log(toolCalls); + let toolName: string; let toolArgs: Record; @@ -131,15 +140,15 @@ export class TaskLoop { if (currentCall === undefined) { // 新的工具调用开始 - this.streamingToolCalls.value = [{ + this.streamingToolCalls.value[toolCall.index] = { id: toolCall.id, - index: 0, + index: toolCall.index, type: 'function', function: { name: toolCall.function?.name || '', arguments: toolCall.function?.arguments || '' } - }]; + }; } else { // 累积现有工具调用的信息 if (currentCall) { @@ -150,7 +159,7 @@ export class TaskLoop { currentCall.function.name = toolCall.function.name; } if (toolCall.function?.arguments) { - currentCall.function.arguments += toolCall.function.arguments; + currentCall.function.arguments += toolCall.function.arguments; } } } @@ -167,16 +176,9 @@ export class TaskLoop { private doConversation(chatData: ChatCompletionCreateParamsBase) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const chunkHandler = this.bridge.addCommandListener('llm/chat/completions/chunk', data => { - if (data.code !== 200) { - this.onError({ - state: MessageState.ReceiveChunkError, - msg: data.msg || '请求模型服务时发生错误' - }); - resolve(); - return; - } + // data.code 一定为 200,否则不会走这个 route const { chunk } = data.msg as { chunk: ChatCompletionChunk }; // 处理增量的 content 和 tool_calls @@ -187,11 +189,34 @@ export class TaskLoop { this.onChunk(chunk); }, { once: false }); - this.bridge.addCommandListener('llm/chat/completions/done', data => { + const doneHandler = this.bridge.addCommandListener('llm/chat/completions/done', data => { this.onDone(); chunkHandler(); + errorHandler(); + + resolve({ + stop: false + }); + }, { once: true }); + + console.log('register error handler'); + + const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => { + + console.log('enter error report'); + + this.onError({ + state: MessageState.ReceiveChunkError, + msg: data.msg || '请求模型服务时发生错误' + }); + + chunkHandler(); + doneHandler(); + + resolve({ + stop: true + }); - resolve(); }, { once: true }); this.bridge.postMessage({ @@ -273,6 +298,10 @@ export class TaskLoop { this.onEpoch = handler; } + public setMaxEpochs(maxEpochs: number) { + this.taskOptions.maxEpochs = maxEpochs; + } + /** * @description 开启循环,异步更新 DOM */ @@ -288,6 +317,8 @@ export class TaskLoop { } }); + let jsonParseErrorRetryCount = 0; + for (let i = 0; i < this.taskOptions.maxEpochs; ++ i) { this.onEpoch(); @@ -308,7 +339,10 @@ export class TaskLoop { this.currentChatId = chatData.id!; // 发送请求 - await this.doConversation(chatData); + const doConverationResult = await this.doConversation(chatData); + + console.log(doConverationResult); + // 如果存在需要调度的工具 if (this.streamingToolCalls.value.length > 0) { @@ -333,11 +367,25 @@ export class TaskLoop { if (toolCallResult.state === MessageState.ParseJsonError) { // 如果是因为解析 JSON 错误,则重新开始 tabStorage.messages.pop(); - redLog('解析 JSON 错误 ' + this.streamingToolCalls.value[0]?.function?.arguments); - continue; - } + jsonParseErrorRetryCount ++; - if (toolCallResult.state === MessageState.Success) { + redLog('解析 JSON 错误 ' + this.streamingToolCalls.value[0]?.function?.arguments); + + // 如果因为 JSON 错误而失败太多,就只能中断了 + if (jsonParseErrorRetryCount >= this.taskOptions.maxJsonParseRetry) { + tabStorage.messages.push({ + 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) { const toolCall = this.streamingToolCalls.value[0]; tabStorage.messages.push({ @@ -351,10 +399,7 @@ export class TaskLoop { usage: this.completionUsage } }); - } - - - if (toolCallResult.state === MessageState.ToolCall) { + } else if (toolCallResult.state === MessageState.ToolCall) { const toolCall = this.streamingToolCalls.value[0]; tabStorage.messages.push({ @@ -385,7 +430,11 @@ export class TaskLoop { } else { // 一些提示 + break; + } + // 回答聚合完成后根据 stop 来决定是否提前中断 + if (doConverationResult.stop) { break; } } diff --git a/renderer/src/components/main-panel/chat/core/usage.ts b/renderer/src/components/main-panel/chat/core/usage.ts index a6b51d2..5b0237f 100644 --- a/renderer/src/components/main-panel/chat/core/usage.ts +++ b/renderer/src/components/main-panel/chat/core/usage.ts @@ -31,6 +31,18 @@ export function makeUsageStatistic(extraInfo: IExtraInfo): UsageStatistic | unde total: usage.prompt_tokens + usage.completion_tokens, cacheHitRatio: Math.ceil(usage.prompt_tokens_details?.cached_tokens || 0 / usage.prompt_tokens * 1000) / 10, } + + + default: + if (usage.prompt_tokens && usage.completion_tokens) { + return { + input: usage.prompt_tokens, + output: usage.completion_tokens, + total: usage.prompt_tokens + usage.completion_tokens, + cacheHitRatio: Math.ceil((usage.prompt_tokens_details?.cached_tokens || 0) / usage.prompt_tokens * 1000) / 10, + } + } + return undefined; } return undefined; diff --git a/renderer/src/components/main-panel/chat/index.vue b/renderer/src/components/main-panel/chat/index.vue index 72f7dff..88bdd82 100644 --- a/renderer/src/components/main-panel/chat/index.vue +++ b/renderer/src/components/main-panel/chat/index.vue @@ -165,6 +165,11 @@ provide('streamingToolCalls', streamingToolCalls); provide('isLoading', isLoading); provide('autoScroll', autoScroll); +const chatContext = { + handleSend: undefined +}; +provide('chatContext', chatContext); + // 修改 scrollToBottom 方法 async function scrollToBottom() { if (!scrollbarRef.value || !messageListRef.value) return; diff --git a/renderer/src/components/main-panel/chat/message/assistant.vue b/renderer/src/components/main-panel/chat/message/assistant.vue index 792bd7e..5ffe68c 100644 --- a/renderer/src/components/main-panel/chat/message/assistant.vue +++ b/renderer/src/components/main-panel/chat/message/assistant.vue @@ -1,13 +1,13 @@