diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 750bc1d6..e7bc36cf 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -504,10 +504,15 @@ export default function ChatPage() { diff --git a/frontend/src/core/messages/utils.ts b/frontend/src/core/messages/utils.ts index d73417db..92951ca6 100644 --- a/frontend/src/core/messages/utils.ts +++ b/frontend/src/core/messages/utils.ts @@ -33,9 +33,47 @@ export function groupMessages( if (messages.length === 0) { return []; } + + // 预处理:收集所有 ToolMessage 的 tool_call_id + const toolMessageIds = new Set(); + for (const message of messages) { + if (message.type === "tool" && message.tool_call_id) { + toolMessageIds.add(message.tool_call_id); + } + } + + // 预处理:检查哪些 tool_calls 没有对应的响应 + const danglingToolCallIds = new Set(); + for (const message of messages) { + if (message.type === "ai" && message.tool_calls) { + for (const tc of message.tool_calls) { + const tcId = tc.id; + if (tcId && !toolMessageIds.has(tcId)) { + danglingToolCallIds.add(tcId); + } + } + } + } + + // 过滤掉只有悬空 tool_calls 且没有其他内容的 AI 消息 + const filteredMessages = messages.filter((message) => { + if (message.type === "ai" && hasToolCalls(message)) { + // 检查是否所有 tool_calls 都是悬空的 + const allDangling = message.tool_calls?.every((tc) => + danglingToolCallIds.has(tc.id!), + ); + // 如果全部悬空且没有其他内容,跳过该消息 + if (allDangling && !hasReasoning(message) && !hasContent(message)) { + console.warn("过滤只有悬空 tool_calls 的 AI 消息:", message.id); + return false; + } + } + return true; + }); + const groups: MessageGroup[] = []; - for (const message of messages) { + for (const message of filteredMessages) { const lastGroup = groups[groups.length - 1]; if (message.type === "human") { groups.push({ @@ -44,9 +82,9 @@ export function groupMessages( messages: [message], }); } else if (message.type === "tool") { - // Check if this is a clarification tool message + // 检查是否为澄清问题的工具消息 if (isClarificationToolMessage(message)) { - // Add to processing group if available (to maintain tool call association) + // 如果有可用的处理组,添加到其中(保持工具调用关联) if ( lastGroup && lastGroup.type !== "human" && @@ -55,7 +93,7 @@ export function groupMessages( ) { lastGroup.messages.push(message); } - // Also create a separate clarification group for prominent display + // 同时创建单独的澄清组以便突出显示 groups.push({ id: message.id, type: "assistant:clarification", @@ -69,9 +107,17 @@ export function groupMessages( ) { lastGroup.messages.push(message); } else { - throw new Error( - "Tool message must be matched with a previous assistant message with tool calls", + // 悬空的工具消息(如生成被中断导致) + // 创建独立的处理组以便显示 + console.warn( + "检测到悬空的工具消息,创建独立组:", + message.tool_call_id, ); + groups.push({ + id: message.id, + type: "assistant:processing", + messages: [message], + }); } } else if (message.type === "ai") { if (hasReasoning(message) || hasToolCalls(message)) { @@ -100,7 +146,7 @@ export function groupMessages( currentGroup.messages.push(message); } else { throw new Error( - "Assistant message with reasoning or tool calls must be preceded by a processing group", + "带有推理或工具调用的 AI 消息必须位于处理组之后", ); } }