diff --git a/backend/tests/test_serialization.py b/backend/tests/test_serialization.py index b707d7143f..9c699f6aff 100644 --- a/backend/tests/test_serialization.py +++ b/backend/tests/test_serialization.py @@ -123,6 +123,25 @@ def test_serialize_messages_tuple(): assert result == [{"key": "v2"}, {"langgraph_node": "agent"}] +def test_serialize_messages_tuple_preserves_reasoning_only_chunk(): + from langchain_core.messages import AIMessageChunk + + from deerflow.runtime.serialization import serialize_messages_tuple + + chunk = AIMessageChunk( + content="", + id="run-1", + additional_kwargs={"reasoning_content": "Thinking through the task."}, + ) + metadata = {"langgraph_node": "agent"} + + result = serialize_messages_tuple((chunk, metadata)) + + assert result[0]["content"] == "" + assert result[0]["additional_kwargs"]["reasoning_content"] == "Thinking through the task." + assert result[1] == metadata + + def test_serialize_messages_tuple_non_dict_metadata(): from deerflow.runtime.serialization import serialize_messages_tuple diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts index 249ea366b1..f53780c483 100644 --- a/frontend/src/core/threads/hooks.ts +++ b/frontend/src/core/threads/hooks.ts @@ -484,6 +484,7 @@ export function useThreadStream({ }, { threadId: threadId, + streamMode: ["messages-tuple", "values"], streamSubgraphs: true, streamResumable: true, config: { diff --git a/frontend/tests/unit/core/messages/utils.test.ts b/frontend/tests/unit/core/messages/utils.test.ts index 24d014c7ef..8635b320ea 100644 --- a/frontend/tests/unit/core/messages/utils.test.ts +++ b/frontend/tests/unit/core/messages/utils.test.ts @@ -2,8 +2,10 @@ import type { Message } from "@langchain/langgraph-sdk"; import { expect, test } from "vitest"; import { + extractReasoningContentFromMessage, getAssistantTurnUsageMessages, getMessageGroups, + hasReasoning, } from "@/core/messages/utils"; test("aggregates token usage messages once per assistant turn", () => { @@ -63,3 +65,26 @@ test("aggregates token usage messages once per assistant turn", () => { ), ).toEqual([null, null, ["ai-1", "ai-2"], null, ["ai-3"]]); }); + +test("recognizes reasoning-only AI messages", () => { + const message = { + id: "reasoning-1", + type: "ai", + content: "", + additional_kwargs: { + reasoning_content: "I need to compare the options first.", + }, + } as Message; + + expect(hasReasoning(message)).toBe(true); + expect(extractReasoningContentFromMessage(message)).toBe( + "I need to compare the options first.", + ); + expect(getMessageGroups([message])).toEqual([ + { + id: "reasoning-1", + type: "assistant:processing", + messages: [message], + }, + ]); +});