fix(chat): preserve messages after summarization (#3280)

* fix(chat): preserve messages after summarization

* make format

* fix(chat): address summarization review comments
This commit is contained in:
Nan Gao
2026-05-29 02:24:47 +02:00
committed by GitHub
parent 2ace78d1e5
commit d46a5779bc
4 changed files with 229 additions and 12 deletions
@@ -2,6 +2,7 @@ import type { Message } from "@langchain/langgraph-sdk";
import { expect, test } from "vitest";
import {
getSummarizationMiddlewareMessages,
getVisibleOptimisticMessages,
mergeMessages,
} from "@/core/threads/hooks";
@@ -66,6 +67,104 @@ test("mergeMessages deduplicates tool messages by tool_call_id", () => {
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",