2025-08-25 13:57:47 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 消息处理工具类
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class MessageProcessor {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将工具结果与消息合并
|
|
|
|
|
|
* @param {Array} msgs - 消息数组
|
|
|
|
|
|
* @returns {Array} 合并后的消息数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
static convertToolResultToMessages(msgs) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const toolResponseMap = new Map()
|
2025-10-04 22:21:30 +08:00
|
|
|
|
|
2025-08-25 13:57:47 +08:00
|
|
|
|
// 构建工具响应映射
|
|
|
|
|
|
for (const item of msgs) {
|
2025-10-27 18:41:35 +08:00
|
|
|
|
if (item.type === 'tool') {
|
|
|
|
|
|
// 使用多种可能的ID字段来匹配工具调用
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const toolCallId = item.tool_call_id || item.id
|
2025-10-27 18:41:35 +08:00
|
|
|
|
if (toolCallId) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
toolResponseMap.set(toolCallId, item)
|
2025-10-27 18:41:35 +08:00
|
|
|
|
}
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 合并工具调用和响应
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const convertedMsgs = msgs.map((item) => {
|
2025-08-25 13:57:47 +08:00
|
|
|
|
if (item.type === 'ai' && item.tool_calls && item.tool_calls.length > 0) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
2026-01-15 06:01:34 +08:00
|
|
|
|
tool_calls: item.tool_calls.map((toolCall) => {
|
|
|
|
|
|
const toolResponse = toolResponseMap.get(toolCall.id)
|
2025-08-25 13:57:47 +08:00
|
|
|
|
return {
|
|
|
|
|
|
...toolCall,
|
|
|
|
|
|
tool_call_result: toolResponse || null
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-08-25 13:57:47 +08:00
|
|
|
|
})
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return item
|
|
|
|
|
|
})
|
2025-08-25 13:57:47 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return convertedMsgs
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将服务器历史记录转换为对话格式
|
|
|
|
|
|
* @param {Array} serverHistory - 服务器历史记录
|
|
|
|
|
|
* @returns {Array} 对话数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
static convertServerHistoryToMessages(serverHistory) {
|
2025-10-04 22:21:30 +08:00
|
|
|
|
// 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
|
2026-05-28 12:53:29 +08:00
|
|
|
|
const filteredHistory = serverHistory.filter(
|
|
|
|
|
|
(item) =>
|
|
|
|
|
|
item.type !== 'tool' &&
|
|
|
|
|
|
!(item.type === 'human' && item.extra_metadata?.source === 'ask_user_question_resume')
|
|
|
|
|
|
)
|
2025-08-25 13:57:47 +08:00
|
|
|
|
|
2025-10-04 22:21:30 +08:00
|
|
|
|
// 按照对话分组
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const conversations = []
|
|
|
|
|
|
let currentConv = null
|
2025-08-25 13:57:47 +08:00
|
|
|
|
|
2025-10-04 22:21:30 +08:00
|
|
|
|
for (const item of filteredHistory) {
|
2025-08-25 13:57:47 +08:00
|
|
|
|
if (item.type === 'human') {
|
2025-10-04 22:21:30 +08:00
|
|
|
|
// 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') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
currentConv.messages[i].isLast = true
|
|
|
|
|
|
currentConv.status = 'finished'
|
|
|
|
|
|
break
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-25 13:57:47 +08:00
|
|
|
|
currentConv = {
|
|
|
|
|
|
messages: [item],
|
|
|
|
|
|
status: 'loading'
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
conversations.push(currentConv)
|
2025-08-25 13:57:47 +08:00
|
|
|
|
} else if (item.type === 'ai' && currentConv) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
currentConv.messages.push(item)
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-25 13:57:47 +08:00
|
|
|
|
|
2025-10-04 22:21:30 +08:00
|
|
|
|
// 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') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
currentConv.messages[i].isLast = true
|
|
|
|
|
|
currentConv.status = 'finished'
|
|
|
|
|
|
break
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return conversations
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 14:22:11 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 提取一轮对话中所有知识库检索块
|
|
|
|
|
|
* @param {Object} conv - 单轮对话
|
|
|
|
|
|
* @param {Array} databases - 知识库列表
|
|
|
|
|
|
* @returns {Array} 归一化后的检索块
|
|
|
|
|
|
*/
|
|
|
|
|
|
static extractKnowledgeChunksFromConversation(conv, databases = []) {
|
|
|
|
|
|
if (!conv || !Array.isArray(conv.messages) || conv.messages.length === 0) return []
|
|
|
|
|
|
|
|
|
|
|
|
const databaseNames = new Set(
|
2026-03-04 04:13:05 +08:00
|
|
|
|
(databases || [])
|
|
|
|
|
|
.map((db) => db?.name)
|
|
|
|
|
|
.filter((name) => typeof name === 'string' && name.trim())
|
2026-02-24 14:22:11 +08:00
|
|
|
|
)
|
|
|
|
|
|
if (databaseNames.size === 0) return []
|
|
|
|
|
|
|
|
|
|
|
|
const normalizedChunks = []
|
|
|
|
|
|
const dedupSet = new Set()
|
|
|
|
|
|
|
|
|
|
|
|
const appendChunk = (chunk, kbName) => {
|
|
|
|
|
|
if (!chunk || typeof chunk !== 'object') return
|
|
|
|
|
|
const content = typeof chunk.content === 'string' ? chunk.content.trim() : ''
|
|
|
|
|
|
if (!content) return
|
|
|
|
|
|
|
|
|
|
|
|
const metadata = chunk.metadata && typeof chunk.metadata === 'object' ? chunk.metadata : {}
|
|
|
|
|
|
const dedupKey =
|
|
|
|
|
|
metadata.chunk_id && typeof metadata.chunk_id === 'string'
|
|
|
|
|
|
? `${kbName}::${metadata.chunk_id}`
|
|
|
|
|
|
: `${kbName}::${content}`
|
|
|
|
|
|
if (dedupSet.has(dedupKey)) return
|
|
|
|
|
|
dedupSet.add(dedupKey)
|
|
|
|
|
|
|
|
|
|
|
|
const score = typeof chunk.score === 'number' ? chunk.score : null
|
|
|
|
|
|
normalizedChunks.push({
|
|
|
|
|
|
kb_name: kbName,
|
|
|
|
|
|
content,
|
|
|
|
|
|
score,
|
|
|
|
|
|
metadata: {
|
|
|
|
|
|
source: metadata.source || '',
|
|
|
|
|
|
file_id: metadata.file_id || '',
|
|
|
|
|
|
chunk_id: metadata.chunk_id || '',
|
2026-03-04 04:13:05 +08:00
|
|
|
|
chunk_index: metadata.chunk_index
|
|
|
|
|
|
}
|
2026-02-24 14:22:11 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const parseToolResultContent = (content) => {
|
|
|
|
|
|
if (Array.isArray(content)) return content
|
|
|
|
|
|
if (content && typeof content === 'object') return content
|
|
|
|
|
|
if (typeof content === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.parse(content)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const msg of conv.messages) {
|
|
|
|
|
|
if (!msg || msg.type !== 'ai' || !Array.isArray(msg.tool_calls)) continue
|
|
|
|
|
|
|
|
|
|
|
|
for (const toolCall of msg.tool_calls) {
|
|
|
|
|
|
const kbName = toolCall?.name || toolCall?.function?.name
|
|
|
|
|
|
if (!databaseNames.has(kbName)) continue
|
|
|
|
|
|
|
|
|
|
|
|
const content = toolCall?.tool_call_result?.content
|
|
|
|
|
|
const parsed = parseToolResultContent(content)
|
|
|
|
|
|
if (!parsed) continue
|
|
|
|
|
|
|
|
|
|
|
|
// Milvus / Dify: 直接是 chunks 数组
|
|
|
|
|
|
if (Array.isArray(parsed)) {
|
|
|
|
|
|
for (const chunk of parsed) appendChunk(chunk, kbName)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 13:06:25 +08:00
|
|
|
|
const wrappedChunks = parsed?.data?.chunks
|
|
|
|
|
|
if (Array.isArray(wrappedChunks)) {
|
|
|
|
|
|
for (const chunk of wrappedChunks) appendChunk(chunk, kbName)
|
2026-02-24 14:22:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
normalizedChunks.sort((a, b) => {
|
|
|
|
|
|
const scoreA = typeof a.score === 'number' ? a.score : Number.NEGATIVE_INFINITY
|
|
|
|
|
|
const scoreB = typeof b.score === 'number' ? b.score : Number.NEGATIVE_INFINITY
|
|
|
|
|
|
return scoreB - scoreA
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return normalizedChunks
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 提取一轮对话中的网络搜索来源
|
|
|
|
|
|
* @param {Object} conv - 单轮对话
|
|
|
|
|
|
* @returns {Array} 归一化后的网络来源
|
|
|
|
|
|
*/
|
|
|
|
|
|
static extractWebSourcesFromConversation(conv) {
|
|
|
|
|
|
if (!conv || !Array.isArray(conv.messages) || conv.messages.length === 0) return []
|
|
|
|
|
|
|
|
|
|
|
|
const webSources = []
|
|
|
|
|
|
const dedupSet = new Set()
|
|
|
|
|
|
|
|
|
|
|
|
const parseToolResultContent = (content) => {
|
|
|
|
|
|
if (Array.isArray(content)) return content
|
|
|
|
|
|
if (content && typeof content === 'object') return content
|
|
|
|
|
|
if (typeof content === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.parse(content)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const msg of conv.messages) {
|
|
|
|
|
|
if (!msg || msg.type !== 'ai' || !Array.isArray(msg.tool_calls)) continue
|
|
|
|
|
|
|
|
|
|
|
|
for (const toolCall of msg.tool_calls) {
|
|
|
|
|
|
const toolName = (toolCall?.name || toolCall?.function?.name || '').toLowerCase()
|
|
|
|
|
|
if (!toolName.includes('tavily_search')) continue
|
|
|
|
|
|
|
|
|
|
|
|
const content = toolCall?.tool_call_result?.content
|
|
|
|
|
|
const parsed = parseToolResultContent(content)
|
|
|
|
|
|
const results = Array.isArray(parsed?.results) ? parsed.results : []
|
|
|
|
|
|
if (results.length === 0) continue
|
|
|
|
|
|
|
|
|
|
|
|
for (const item of results) {
|
|
|
|
|
|
const title = typeof item?.title === 'string' ? item.title.trim() : ''
|
|
|
|
|
|
const url = typeof item?.url === 'string' ? item.url.trim() : ''
|
|
|
|
|
|
if (!title || !url) continue
|
|
|
|
|
|
if (dedupSet.has(url)) continue
|
|
|
|
|
|
dedupSet.add(url)
|
|
|
|
|
|
|
|
|
|
|
|
webSources.push({
|
|
|
|
|
|
tool_name: toolCall?.name || toolCall?.function?.name || '网络搜索',
|
|
|
|
|
|
title,
|
|
|
|
|
|
url,
|
|
|
|
|
|
score: typeof item?.score === 'number' ? item.score : null,
|
|
|
|
|
|
content: typeof item?.content === 'string' ? item.content : '',
|
2026-03-04 04:13:05 +08:00
|
|
|
|
published_date: typeof item?.published_date === 'string' ? item.published_date : ''
|
2026-02-24 14:22:11 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
webSources.sort((a, b) => {
|
|
|
|
|
|
const scoreA = typeof a.score === 'number' ? a.score : Number.NEGATIVE_INFINITY
|
|
|
|
|
|
const scoreB = typeof b.score === 'number' ? b.score : Number.NEGATIVE_INFINITY
|
|
|
|
|
|
return scoreB - scoreA
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return webSources
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 21:01:37 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 提取单个消息中的来源
|
|
|
|
|
|
* @param {Object} message - 消息对象
|
|
|
|
|
|
* @param {Array} databases - 知识库列表
|
|
|
|
|
|
* @returns {{knowledgeChunks: Array, webSources: Array}}
|
|
|
|
|
|
*/
|
|
|
|
|
|
static extractSourcesFromMessage(message, databases = []) {
|
|
|
|
|
|
if (!message || message.type !== 'ai') return { knowledgeChunks: [], webSources: [] }
|
|
|
|
|
|
|
|
|
|
|
|
// 复用提取逻辑,通过构建临时对话对象
|
|
|
|
|
|
const mockConv = { messages: [message] }
|
|
|
|
|
|
return {
|
|
|
|
|
|
knowledgeChunks: MessageProcessor.extractKnowledgeChunksFromConversation(mockConv, databases),
|
2026-03-04 04:13:05 +08:00
|
|
|
|
webSources: MessageProcessor.extractWebSourcesFromConversation(mockConv)
|
2026-03-02 21:01:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 14:22:11 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 提取一轮对话中的全部来源(知识库+网络搜索)
|
|
|
|
|
|
* @param {Object} conv - 单轮对话
|
|
|
|
|
|
* @param {Array} databases - 知识库列表
|
|
|
|
|
|
* @returns {{knowledgeChunks: Array, webSources: Array}}
|
|
|
|
|
|
*/
|
|
|
|
|
|
static extractSourcesFromConversation(conv, databases = []) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
knowledgeChunks: MessageProcessor.extractKnowledgeChunksFromConversation(conv, databases),
|
2026-03-04 04:13:05 +08:00
|
|
|
|
webSources: MessageProcessor.extractWebSourcesFromConversation(conv)
|
2026-02-24 14:22:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-02 00:08:38 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 解析助手消息正文与推理内容,保持渲染和列表拆分使用同一套规则。
|
|
|
|
|
|
* @param {Object} message - AI 消息对象
|
|
|
|
|
|
* @returns {{content: string, reasoningContent: string}}
|
|
|
|
|
|
*/
|
|
|
|
|
|
static parseAssistantMessageBody(message) {
|
|
|
|
|
|
let content = typeof message?.content === 'string' ? message.content.trim() : ''
|
|
|
|
|
|
let reasoningContent = message?.additional_kwargs?.reasoning_content || ''
|
|
|
|
|
|
|
|
|
|
|
|
if (!reasoningContent && content) {
|
|
|
|
|
|
const thinkRegex = /<think>(.*?)<\/think>|<think>(.*?)$/s
|
|
|
|
|
|
const thinkMatch = content.match(thinkRegex)
|
|
|
|
|
|
|
|
|
|
|
|
if (thinkMatch) {
|
|
|
|
|
|
reasoningContent = (thinkMatch[1] || thinkMatch[2] || '').trim()
|
|
|
|
|
|
content = content.replace(thinkMatch[0], '').trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { content, reasoningContent }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-25 13:57:47 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 合并消息块
|
|
|
|
|
|
* @param {Array} chunks - 消息块数组
|
|
|
|
|
|
* @returns {Object|null} 合并后的消息
|
|
|
|
|
|
*/
|
|
|
|
|
|
static mergeMessageChunk(chunks) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (chunks.length === 0) return null
|
2025-08-25 13:57:47 +08:00
|
|
|
|
|
|
|
|
|
|
// 深拷贝第一个chunk作为结果
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const result = JSON.parse(JSON.stringify(chunks[0]))
|
2025-11-12 14:04:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理用户消息的内容格式 - 确保显示纯文本
|
|
|
|
|
|
if (result.type === 'human' || result.role === 'user') {
|
|
|
|
|
|
// 如果content是数组格式(LangChain多模态消息),提取文本部分
|
|
|
|
|
|
if (Array.isArray(result.content)) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const textPart = result.content.find((item) => item.type === 'text')
|
|
|
|
|
|
result.content = textPart ? textPart.text : ''
|
2025-11-12 14:04:34 +08:00
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
result.content = result.content || ''
|
2025-11-12 14:04:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
result.content = result.content || ''
|
2025-11-12 14:04:34 +08:00
|
|
|
|
}
|
2025-08-25 13:57:47 +08:00
|
|
|
|
|
|
|
|
|
|
// 合并后续chunks
|
|
|
|
|
|
for (let i = 1; i < chunks.length; i++) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const chunk = chunks[i]
|
2025-10-04 22:21:30 +08:00
|
|
|
|
|
2025-08-25 13:57:47 +08:00
|
|
|
|
// 合并内容
|
|
|
|
|
|
if (chunk.content) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
result.content += chunk.content
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 合并reasoning_content
|
|
|
|
|
|
if (chunk.reasoning_content) {
|
|
|
|
|
|
if (!result.reasoning_content) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
result.reasoning_content = ''
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
result.reasoning_content += chunk.reasoning_content
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 合并additional_kwargs中的reasoning_content
|
|
|
|
|
|
if (chunk.additional_kwargs?.reasoning_content) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (!result.additional_kwargs) result.additional_kwargs = {}
|
2025-08-25 13:57:47 +08:00
|
|
|
|
if (!result.additional_kwargs.reasoning_content) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
result.additional_kwargs.reasoning_content = ''
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
result.additional_kwargs.reasoning_content += chunk.additional_kwargs.reasoning_content
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-27 18:41:35 +08:00
|
|
|
|
// 合并tool_calls (处理新的数据结构)
|
2026-01-15 06:01:34 +08:00
|
|
|
|
MessageProcessor._mergeToolCalls(result, chunk)
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理AIMessageChunk类型
|
|
|
|
|
|
if (result.type === 'AIMessageChunk') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
result.type = 'ai'
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return result
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 合并工具调用
|
|
|
|
|
|
* @private
|
|
|
|
|
|
* @param {Object} result - 结果对象
|
|
|
|
|
|
* @param {Object} chunk - 当前块
|
|
|
|
|
|
*/
|
|
|
|
|
|
static _mergeToolCalls(result, chunk) {
|
2025-11-01 21:34:16 +08:00
|
|
|
|
if (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) {
|
|
|
|
|
|
// 确保 result 有 tool_calls 数组
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (!result.tool_calls) result.tool_calls = []
|
2025-10-27 18:41:35 +08:00
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
|
for (const toolCallChunk of chunk.tool_call_chunks) {
|
|
|
|
|
|
// 使用 index 来标识工具调用(因为可能有多个工具调用)
|
|
|
|
|
|
const existingToolCallIndex = result.tool_calls.findIndex(
|
2026-01-15 06:01:34 +08:00
|
|
|
|
(t) => t.index === toolCallChunk.index
|
|
|
|
|
|
)
|
2025-10-27 18:41:35 +08:00
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
|
if (existingToolCallIndex !== -1) {
|
|
|
|
|
|
// 合并相同index的tool call
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const existingToolCall = result.tool_calls[existingToolCallIndex]
|
2025-10-27 18:41:35 +08:00
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
|
// 更新名称和ID(如果存在)
|
|
|
|
|
|
if (toolCallChunk.name && !existingToolCall.function?.name) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (!existingToolCall.function) existingToolCall.function = {}
|
|
|
|
|
|
existingToolCall.function.name = toolCallChunk.name
|
2025-10-27 18:41:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
|
if (toolCallChunk.id && !existingToolCall.id) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
existingToolCall.id = toolCallChunk.id
|
2025-10-27 18:41:35 +08:00
|
|
|
|
}
|
2025-11-01 21:34:16 +08:00
|
|
|
|
|
|
|
|
|
|
// 合并参数
|
|
|
|
|
|
if (toolCallChunk.args) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (!existingToolCall.function) existingToolCall.function = {}
|
|
|
|
|
|
if (!existingToolCall.function.arguments) existingToolCall.function.arguments = ''
|
|
|
|
|
|
existingToolCall.function.arguments += toolCallChunk.args
|
2025-10-27 18:41:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-08-25 13:57:47 +08:00
|
|
|
|
// 添加新的tool call
|
2025-11-01 21:34:16 +08:00
|
|
|
|
const newToolCall = {
|
|
|
|
|
|
index: toolCallChunk.index,
|
|
|
|
|
|
id: toolCallChunk.id,
|
|
|
|
|
|
function: {
|
|
|
|
|
|
name: toolCallChunk.name || null,
|
|
|
|
|
|
arguments: toolCallChunk.args || ''
|
|
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
result.tool_calls.push(newToolCall)
|
2025-10-27 18:41:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-25 13:57:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
export default MessageProcessor
|