ForcePilot/web/src/utils/messageProcessor.js

248 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 消息处理工具类
*/
export class MessageProcessor {
/**
* 将工具结果与消息合并
* @param {Array} msgs - 消息数组
* @returns {Array} 合并后的消息数组
*/
static convertToolResultToMessages(msgs) {
const toolResponseMap = new Map();
// 构建工具响应映射
for (const item of msgs) {
if (item.type === 'tool' && item.tool_call_id) {
toolResponseMap.set(item.tool_call_id, 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) {
// 第一步将所有tool消息与对应的tool call合并
const mergedHistory = this.convertToolResultToMessages(serverHistory);
// 第二步:按照对话分组
const conversations = [];
let currentConv = null;
for (const item of mergedHistory) {
if (item.type === 'human') {
currentConv = {
messages: [item],
status: 'loading'
};
conversations.push(currentConv);
} else if (item.type === 'ai' && currentConv) {
currentConv.messages.push(item);
if (item.response_metadata?.finish_reason === 'stop') {
item.isLast = true;
currentConv.status = 'finished';
currentConv = null;
}
}
}
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]));
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';
if (result.additional_kwargs?.tool_calls) {
result.tool_calls = result.additional_kwargs.tool_calls;
}
}
return result;
}
/**
* 合并工具调用
* @private
* @param {Object} result - 结果对象
* @param {Object} chunk - 当前块
*/
static _mergeToolCalls(result, chunk) {
if (chunk.additional_kwargs?.tool_calls) {
if (!result.additional_kwargs) result.additional_kwargs = {};
if (!result.additional_kwargs.tool_calls) result.additional_kwargs.tool_calls = [];
for (const toolCall of chunk.additional_kwargs.tool_calls) {
const existingToolCall = result.additional_kwargs.tool_calls.find(
t => (t.id === toolCall.id || t.index === toolCall.index)
);
if (existingToolCall) {
// 合并相同ID的tool call
if (existingToolCall.function && toolCall.function) {
existingToolCall.function.arguments += toolCall.function.arguments;
}
} else {
// 添加新的tool call
result.additional_kwargs.tool_calls.push(JSON.parse(JSON.stringify(toolCall)));
}
}
}
}
/**
* 处理流式响应数据块
* @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;