mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-30 20:38:09 +00:00
* fix(chat): preserve messages after summarization * make format * fix(chat): address summarization review comments
257 lines
6.2 KiB
TypeScript
257 lines
6.2 KiB
TypeScript
import type { Message } from "@langchain/langgraph-sdk";
|
|
import { expect, test } from "vitest";
|
|
|
|
import {
|
|
getSummarizationMiddlewareMessages,
|
|
getVisibleOptimisticMessages,
|
|
mergeMessages,
|
|
} from "@/core/threads/hooks";
|
|
|
|
test("mergeMessages removes duplicate messages already present in history", () => {
|
|
const human = {
|
|
id: "human-1",
|
|
type: "human",
|
|
content: "Design an agent",
|
|
} as Message;
|
|
const ai = {
|
|
id: "ai-1",
|
|
type: "ai",
|
|
content: "Let's design it.",
|
|
} as Message;
|
|
|
|
expect(mergeMessages([human, ai, human, ai], [], [])).toEqual([human, ai]);
|
|
});
|
|
|
|
test("mergeMessages lets live thread messages replace overlapping history", () => {
|
|
const oldHuman = {
|
|
id: "human-1",
|
|
type: "human",
|
|
content: "old",
|
|
} as Message;
|
|
const liveHuman = {
|
|
id: "human-1",
|
|
type: "human",
|
|
content: "live",
|
|
} as Message;
|
|
const oldAi = {
|
|
id: "ai-1",
|
|
type: "ai",
|
|
content: "old",
|
|
} as Message;
|
|
const liveAi = {
|
|
id: "ai-1",
|
|
type: "ai",
|
|
content: "live",
|
|
} as Message;
|
|
|
|
expect(mergeMessages([oldHuman, oldAi], [liveHuman, liveAi], [])).toEqual([
|
|
liveHuman,
|
|
liveAi,
|
|
]);
|
|
});
|
|
|
|
test("mergeMessages deduplicates tool messages by tool_call_id", () => {
|
|
const oldTool = {
|
|
id: "tool-message-old",
|
|
type: "tool",
|
|
tool_call_id: "call-1",
|
|
content: "old",
|
|
} as Message;
|
|
const liveTool = {
|
|
id: "tool-message-live",
|
|
type: "tool",
|
|
tool_call_id: "call-1",
|
|
content: "live",
|
|
} as Message;
|
|
|
|
expect(mergeMessages([oldTool], [liveTool], [])).toEqual([liveTool]);
|
|
});
|
|
|
|
test("mergeMessages keeps a visible history message when a hidden live message reuses its id", () => {
|
|
const historyHuman = {
|
|
id: "human-1",
|
|
type: "human",
|
|
content: "visible user prompt",
|
|
} as Message;
|
|
const hiddenReminder = {
|
|
id: "human-1",
|
|
type: "human",
|
|
content: "<system-reminder>hidden</system-reminder>",
|
|
additional_kwargs: { hide_from_ui: true },
|
|
} as Message;
|
|
const liveAi = {
|
|
id: "ai-1",
|
|
type: "ai",
|
|
content: "live answer",
|
|
} as Message;
|
|
|
|
expect(mergeMessages([historyHuman], [hiddenReminder, liveAi], [])).toEqual([
|
|
historyHuman,
|
|
liveAi,
|
|
]);
|
|
});
|
|
|
|
test("mergeMessages lets a visible live message replace overlapping hidden history", () => {
|
|
const hiddenHistoryHuman = {
|
|
id: "human-1",
|
|
type: "human",
|
|
content: "<system-reminder>hidden</system-reminder>",
|
|
additional_kwargs: { hide_from_ui: true },
|
|
} as Message;
|
|
const liveHuman = {
|
|
id: "human-1",
|
|
type: "human",
|
|
content: "visible user prompt",
|
|
} as Message;
|
|
|
|
expect(mergeMessages([hiddenHistoryHuman], [liveHuman], [])).toEqual([
|
|
liveHuman,
|
|
]);
|
|
});
|
|
|
|
test("getSummarizationMiddlewareMessages matches DeerFlow summarization update keys", () => {
|
|
const removeAll = {
|
|
id: "__remove_all__",
|
|
type: "remove",
|
|
content: "",
|
|
} as Message;
|
|
const summary = {
|
|
id: "summary-1",
|
|
type: "human",
|
|
name: "summary",
|
|
content: "summary",
|
|
} as Message;
|
|
|
|
expect(
|
|
getSummarizationMiddlewareMessages({
|
|
"DeerFlowSummarizationMiddleware.before_model": {
|
|
messages: [removeAll, summary],
|
|
},
|
|
}),
|
|
).toEqual([removeAll, summary]);
|
|
});
|
|
|
|
test("getSummarizationMiddlewareMessages matches base LangChain summarization update keys", () => {
|
|
const summary = {
|
|
id: "summary-1",
|
|
type: "human",
|
|
name: "summary",
|
|
content: "summary",
|
|
} as Message;
|
|
|
|
expect(
|
|
getSummarizationMiddlewareMessages({
|
|
"SummarizationMiddleware.before_model": {
|
|
messages: [summary],
|
|
},
|
|
}),
|
|
).toEqual([summary]);
|
|
});
|
|
|
|
test("getSummarizationMiddlewareMessages ignores unrelated suffix-sharing update keys", () => {
|
|
const summary = {
|
|
id: "summary-1",
|
|
type: "human",
|
|
name: "summary",
|
|
content: "summary",
|
|
} as Message;
|
|
|
|
expect(
|
|
getSummarizationMiddlewareMessages({
|
|
"OtherSummarizationMiddleware.before_model": {
|
|
messages: [summary],
|
|
},
|
|
}),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
test("getVisibleOptimisticMessages hides optimistic user input after server human arrives", () => {
|
|
const optimisticHuman = {
|
|
id: "opt-human-1",
|
|
type: "human",
|
|
content: "hello",
|
|
} as Message;
|
|
|
|
expect(getVisibleOptimisticMessages([optimisticHuman], 0, 1)).toEqual([]);
|
|
});
|
|
|
|
test("mergeMessages shows server human instead of optimistic duplicate after first response", () => {
|
|
const serverHuman = {
|
|
id: "server-human-1",
|
|
type: "human",
|
|
content: "hello",
|
|
} as Message;
|
|
const optimisticHuman = {
|
|
id: "opt-human-1",
|
|
type: "human",
|
|
content: "hello",
|
|
} as Message;
|
|
const visibleOptimistic = getVisibleOptimisticMessages(
|
|
[optimisticHuman],
|
|
0,
|
|
1,
|
|
);
|
|
|
|
expect(mergeMessages([], [serverHuman], visibleOptimistic)).toEqual([
|
|
serverHuman,
|
|
]);
|
|
});
|
|
|
|
test("getVisibleOptimisticMessages keeps optimistic user input until server human arrives", () => {
|
|
const optimisticHuman = {
|
|
id: "opt-human-1",
|
|
type: "human",
|
|
content: "hello",
|
|
} as Message;
|
|
|
|
expect(getVisibleOptimisticMessages([optimisticHuman], 0, 0)).toEqual([
|
|
optimisticHuman,
|
|
]);
|
|
});
|
|
|
|
test("getVisibleOptimisticMessages keeps non-human optimistic status messages", () => {
|
|
const optimisticAi = {
|
|
id: "opt-ai-1",
|
|
type: "ai",
|
|
content: "Uploading files...",
|
|
} as Message;
|
|
|
|
expect(getVisibleOptimisticMessages([optimisticAi], 0, 1)).toEqual([
|
|
optimisticAi,
|
|
]);
|
|
});
|
|
|
|
test("getVisibleOptimisticMessages hides the upload optimistic pair after server human arrives", () => {
|
|
const optimisticHuman = {
|
|
id: "opt-human-1",
|
|
type: "human",
|
|
content: "upload this",
|
|
} as Message;
|
|
const optimisticUploadingAi = {
|
|
id: "opt-ai-uploading",
|
|
type: "ai",
|
|
content: "Uploading files...",
|
|
} as Message;
|
|
|
|
expect(
|
|
getVisibleOptimisticMessages(
|
|
[optimisticHuman, optimisticUploadingAi],
|
|
0,
|
|
1,
|
|
),
|
|
).toEqual([]);
|
|
});
|
|
|
|
test("getVisibleOptimisticMessages hides optimistic user input after later server turns", () => {
|
|
const optimisticHuman = {
|
|
id: "opt-human-2",
|
|
type: "human",
|
|
content: "follow up",
|
|
} as Message;
|
|
|
|
expect(getVisibleOptimisticMessages([optimisticHuman], 3, 4)).toEqual([]);
|
|
expect(getVisibleOptimisticMessages([optimisticHuman], 3, 3)).toEqual([
|
|
optimisticHuman,
|
|
]);
|
|
});
|