/** * 消息处理工具类 */ export class MessageProcessor { /** * 将工具结果与消息合并 * @param {Array} msgs - 消息数组 * @returns {Array} 合并后的消息数组 */ static convertToolResultToMessages(msgs) { const toolResponseMap = new Map(); // 构建工具响应映射 for (const item of msgs) { if (item.type === 'tool') { // 使用多种可能的ID字段来匹配工具调用 const toolCallId = item.tool_call_id || item.id; if (toolCallId) { toolResponseMap.set(toolCallId, item); } } } // 合并工具调用和响应 const convertedMsgs = msgs.map(item => { if (item.type === 'ai' && item.tool_calls && item.tool_calls.length > 0) { return { ...item, tool_calls: item.tool_calls.map(toolCall => { const toolResponse = toolResponseMap.get(toolCall.id); return { ...toolCall, tool_call_result: toolResponse || null }; }) }; } return item; }); return convertedMsgs; } /** * 将服务器历史记录转换为对话格式 * @param {Array} serverHistory - 服务器历史记录 * @returns {Array} 对话数组 */ static convertServerHistoryToMessages(serverHistory) { // Filter out standalone 'tool' messages since tool results are already in AI messages' tool_calls // Backend new storage: tool results are embedded in AI messages' tool_calls array with tool_call_result field const filteredHistory = serverHistory.filter(item => item.type !== 'tool'); // 按照对话分组 const conversations = []; let currentConv = null; for (const item of filteredHistory) { if (item.type === 'human') { // Start new conversation, finalize previous one if (currentConv) { // Find the last AI message and mark it as final for (let i = currentConv.messages.length - 1; i >= 0; i--) { if (currentConv.messages[i].type === 'ai') { currentConv.messages[i].isLast = true; currentConv.status = 'finished'; break; } } } currentConv = { messages: [item], status: 'loading' }; conversations.push(currentConv); } else if (item.type === 'ai' && currentConv) { currentConv.messages.push(item); } } // Mark the last conversation as finished if (currentConv && currentConv.messages.length > 0) { // Find the last AI message and mark it as final for (let i = currentConv.messages.length - 1; i >= 0; i--) { if (currentConv.messages[i].type === 'ai') { currentConv.messages[i].isLast = true; currentConv.status = 'finished'; break; } } } return conversations; } /** * 合并消息块 * @param {Array} chunks - 消息块数组 * @returns {Object|null} 合并后的消息 */ static mergeMessageChunk(chunks) { if (chunks.length === 0) return null; // 深拷贝第一个chunk作为结果 const result = JSON.parse(JSON.stringify(chunks[0])); // 处理用户消息的内容格式 - 确保显示纯文本 if (result.type === 'human' || result.role === 'user') { // 如果content是数组格式(LangChain多模态消息),提取文本部分 if (Array.isArray(result.content)) { const textPart = result.content.find(item => item.type === 'text'); result.content = textPart ? textPart.text : ''; } else { result.content = result.content || ''; } } else { result.content = result.content || ''; } // 合并后续chunks for (let i = 1; i < chunks.length; i++) { const chunk = chunks[i]; // 合并内容 if (chunk.content) { result.content += chunk.content; } // 合并reasoning_content if (chunk.reasoning_content) { if (!result.reasoning_content) { result.reasoning_content = ''; } result.reasoning_content += chunk.reasoning_content; } // 合并additional_kwargs中的reasoning_content if (chunk.additional_kwargs?.reasoning_content) { if (!result.additional_kwargs) result.additional_kwargs = {}; if (!result.additional_kwargs.reasoning_content) { result.additional_kwargs.reasoning_content = ''; } result.additional_kwargs.reasoning_content += chunk.additional_kwargs.reasoning_content; } // 合并tool_calls (处理新的数据结构) MessageProcessor._mergeToolCalls(result, chunk); } // 处理AIMessageChunk类型 if (result.type === 'AIMessageChunk') { result.type = 'ai'; } return result; } /** * 合并工具调用 * @private * @param {Object} result - 结果对象 * @param {Object} chunk - 当前块 */ static _mergeToolCalls(result, chunk) { if (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) { // 确保 result 有 tool_calls 数组 if (!result.tool_calls) result.tool_calls = []; for (const toolCallChunk of chunk.tool_call_chunks) { // 使用 index 来标识工具调用(因为可能有多个工具调用) const existingToolCallIndex = result.tool_calls.findIndex( t => t.index === toolCallChunk.index ); if (existingToolCallIndex !== -1) { // 合并相同index的tool call const existingToolCall = result.tool_calls[existingToolCallIndex]; // 更新名称和ID(如果存在) if (toolCallChunk.name && !existingToolCall.function?.name) { if (!existingToolCall.function) existingToolCall.function = {}; existingToolCall.function.name = toolCallChunk.name; } if (toolCallChunk.id && !existingToolCall.id) { existingToolCall.id = toolCallChunk.id; } // 合并参数 if (toolCallChunk.args) { if (!existingToolCall.function) existingToolCall.function = {}; if (!existingToolCall.function.arguments) existingToolCall.function.arguments = ''; existingToolCall.function.arguments += toolCallChunk.args; } } else { // 添加新的tool call const newToolCall = { index: toolCallChunk.index, id: toolCallChunk.id, function: { name: toolCallChunk.name || null, arguments: toolCallChunk.args || '' } }; result.tool_calls.push(newToolCall); } } } } /** * 处理流式响应数据块 * @param {Object} data - 响应数据 * @param {Object} onGoingConv - 进行中的对话对象 * @param {Object} state - 状态对象 * @param {Function} getAgentHistory - 获取历史记录函数 * @param {Function} handleError - 错误处理函数 */ static async processResponseChunk(data, onGoingConv, state, getAgentHistory, handleError) { try { switch (data.status) { case 'init': // 代表服务端收到请求并返回第一个响应 state.waitingServerResponse = false; onGoingConv.msgChunks[data.request_id] = [data.msg]; break; case 'loading': if (data.msg.id) { if (!onGoingConv.msgChunks[data.msg.id]) { onGoingConv.msgChunks[data.msg.id] = []; } onGoingConv.msgChunks[data.msg.id].push(data.msg); } break; case 'error': console.error("流式处理出错:", data.message); handleError(new Error(data.message), 'stream'); break; case 'finished': await getAgentHistory(); break; default: console.warn('未知的响应状态:', data.status); } } catch (error) { handleError(error, 'stream'); } } /** * 处理流式响应 * @param {Response} response - 响应对象 * @param {Function} processChunk - 处理块的函数 * @param {Function} scrollToBottom - 滚动到底部函数 * @param {Function} handleError - 错误处理函数 */ static async handleStreamResponse(response, processChunk, scrollToBottom, handleError) { try { const reader = response.body.getReader(); let buffer = ''; const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; // 保留最后一行可能不完整的内容 for (const line of lines) { if (line.trim()) { try { const data = JSON.parse(line.trim()); await processChunk(data); } catch (e) { console.debug('解析JSON出错:', e.message); } } } await scrollToBottom(); } // 处理缓冲区中可能剩余的内容 if (buffer.trim()) { try { const data = JSON.parse(buffer.trim()); await processChunk(data); } catch (e) { console.warn('最终缓冲区内容无法解析:', buffer); } } } catch (error) { handleError(error, 'stream'); } } } export default MessageProcessor;