Skip to content

Commit 5f2dab2

Browse files
fix(langchain): create summary as human message instead of system message (langchain-ai#9365)
1 parent 9d6ca27 commit 5f2dab2

File tree

2 files changed

+35
-22
lines changed

2 files changed

+35
-22
lines changed

libs/langchain/src/agents/middleware/summarization.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
ToolMessage,
88
RemoveMessage,
99
trimMessages,
10+
HumanMessage,
1011
} from "@langchain/core/messages";
1112
import { BaseLanguageModel } from "@langchain/core/language_models/base";
1213
import {
@@ -154,7 +155,9 @@ export function summarizationMiddleware(
154155
? await initChatModel(config.model)
155156
: config.model;
156157

157-
// Ensure all messages have IDs
158+
/**
159+
* Ensure all messages have IDs
160+
*/
158161
ensureMessageIds(messages);
159162

160163
const tokenCounter = config.tokenCounter || countTokensApproximately;
@@ -191,7 +194,7 @@ export function summarizationMiddleware(
191194
tokenCounter
192195
);
193196

194-
const updatedSystemMessage = buildUpdatedSystemMessage(
197+
const summaryMessage = buildSummaryMessage(
195198
systemPrompt,
196199
summary,
197200
config.summaryPrefix
@@ -200,7 +203,7 @@ export function summarizationMiddleware(
200203
return {
201204
messages: [
202205
new RemoveMessage({ id: REMOVE_ALL_MESSAGES }),
203-
updatedSystemMessage,
206+
summaryMessage,
204207
...preservedMessages,
205208
],
206209
};
@@ -258,13 +261,13 @@ function partitionMessages(
258261
}
259262

260263
/**
261-
* Build updated system message incorporating the summary
264+
* Build summary message incorporating the summary
262265
*/
263-
function buildUpdatedSystemMessage(
266+
function buildSummaryMessage(
264267
originalSystemMessage: SystemMessage | null,
265268
summary: string,
266269
summaryPrefix: string
267-
): SystemMessage {
270+
): HumanMessage {
268271
let originalContent = "";
269272
if (originalSystemMessage) {
270273
const { content } = originalSystemMessage;
@@ -277,7 +280,7 @@ function buildUpdatedSystemMessage(
277280
? `${originalContent}\n${summaryPrefix}\n${summary}`
278281
: `${summaryPrefix}\n${summary}`;
279282

280-
return new SystemMessage({
283+
return new HumanMessage({
281284
content,
282285
id: originalSystemMessage?.id || uuid(),
283286
});
@@ -316,7 +319,9 @@ function isSafeCutoffPoint(
316319
return true;
317320
}
318321

319-
// Prevent preserved messages from starting with AI message containing tool calls
322+
/**
323+
* Prevent preserved messages from starting with AI message containing tool calls
324+
*/
320325
if (
321326
cutoffIndex < messages.length &&
322327
AIMessage.isInstance(messages[cutoffIndex]) &&
@@ -440,7 +445,9 @@ async function trimMessagesForSummary(
440445
includeSystem: true,
441446
});
442447
} catch {
443-
// Fallback to last N messages if trimming fails
448+
/**
449+
* Fallback to last N messages if trimming fails
450+
*/
444451
return messages.slice(-DEFAULT_FALLBACK_MESSAGE_COUNT);
445452
}
446453
}

libs/langchain/src/agents/middleware/tests/summarization.test.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,12 @@ describe("summarizationMiddleware", () => {
127127
expect(summarizationModel.invoke).toHaveBeenCalled();
128128

129129
// Verify the result has a system message with summary
130-
expect(result.messages[0]).toBeInstanceOf(SystemMessage);
131-
const systemPrompt = result.messages[0] as SystemMessage;
132-
expect(systemPrompt.content).toContain("## Previous conversation summary:");
133-
expect(systemPrompt.content).toContain("Previous conversation covered:");
130+
expect(result.messages[0]).toBeInstanceOf(HumanMessage);
131+
const summaryMessage = result.messages[0] as HumanMessage;
132+
expect(summaryMessage.content).toContain(
133+
"## Previous conversation summary:"
134+
);
135+
expect(summaryMessage.content).toContain("Previous conversation covered:");
134136

135137
// Verify only recent messages are kept (plus the new response)
136138
expect(result.messages.length).toBeLessThanOrEqual(5); // system + kept messages + new response
@@ -253,16 +255,18 @@ describe("summarizationMiddleware", () => {
253255
const result = await agent.invoke({ messages });
254256

255257
// Verify system message is updated with new summary
256-
expect(result.messages[0]).toBeInstanceOf(SystemMessage);
257-
const systemPrompt = result.messages[0] as SystemMessage;
258-
expect(systemPrompt.content).toContain("You are a helpful assistant");
259-
expect(systemPrompt.content).toContain("## Previous conversation summary:");
258+
expect(result.messages[0]).toBeInstanceOf(HumanMessage);
259+
const summaryMessage = result.messages[0] as HumanMessage;
260+
expect(summaryMessage.content).toContain("You are a helpful assistant");
261+
expect(summaryMessage.content).toContain(
262+
"## Previous conversation summary:"
263+
);
260264

261265
// Should have replaced the old summary with new one
262-
expect(systemPrompt.content).not.toContain(
266+
expect(summaryMessage.content).not.toContain(
263267
"Previous discussion about databases"
264268
);
265-
expect(systemPrompt.content).toContain("Previous conversation covered:");
269+
expect(summaryMessage.content).toContain("Previous conversation covered:");
266270
});
267271

268272
it("should use custom token counter when provided", async () => {
@@ -428,9 +432,11 @@ describe("summarizationMiddleware", () => {
428432
const result = await agent.invoke({ messages });
429433

430434
// Verify summarization occurred
431-
expect(result.messages[0]).toBeInstanceOf(SystemMessage);
432-
const systemPrompt = result.messages[0] as SystemMessage;
433-
expect(systemPrompt.content).toContain("## Previous conversation summary:");
435+
expect(result.messages[0]).toBeInstanceOf(HumanMessage);
436+
const summaryMessage = result.messages[0] as HumanMessage;
437+
expect(summaryMessage.content).toContain(
438+
"## Previous conversation summary:"
439+
);
434440

435441
// Verify preserved messages don't start with AI(tool calls)
436442
const preservedMessages = result.messages.filter(

0 commit comments

Comments
 (0)