Skip to content

Commit 93862f4

Browse files
authored
fix(core): Fix trim messages mutation (#7585)
1 parent a71067d commit 93862f4

File tree

2 files changed

+62
-11
lines changed

2 files changed

+62
-11
lines changed

langchain-core/src/messages/tests/message_utils.test.ts

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { it, describe, test, expect } from "@jest/globals";
2+
import { v4 } from "uuid";
23
import {
34
filterMessages,
45
mergeMessageRuns,
@@ -198,6 +199,34 @@ describe("trimMessages can trim", () => {
198199
};
199200
};
200201

202+
it("should not mutate messages", async () => {
203+
const messages: BaseMessage[] = [
204+
new HumanMessage({
205+
content: `My name is Jane Doe.
206+
this is a long text
207+
`,
208+
id: v4(),
209+
}),
210+
new HumanMessage({
211+
content: `My name is Jane Doe.feiowfjoaejfioewaijof ewoif ioawej foiaew iofewi ao
212+
this is a longer text than the first text.
213+
`,
214+
id: v4(),
215+
}),
216+
];
217+
218+
const repr = JSON.stringify(messages);
219+
220+
await trimMessages(messages, {
221+
maxTokens: 14,
222+
strategy: "last",
223+
tokenCounter: () => 100,
224+
allowPartial: true,
225+
});
226+
227+
expect(repr).toEqual(JSON.stringify(messages));
228+
});
229+
201230
it("should not mutate messages if no trimming occurs with strategy last", async () => {
202231
const trimmer = trimMessages({
203232
maxTokens: 128000,
@@ -211,6 +240,8 @@ describe("trimMessages can trim", () => {
211240
content: "Fetch the last 5 emails from Flora Testington's inbox.",
212241
additional_kwargs: {},
213242
response_metadata: {},
243+
id: undefined,
244+
name: undefined,
214245
}),
215246
new AIMessageChunk({
216247
id: "chatcmpl-abcdefg",
@@ -258,18 +289,21 @@ describe("trimMessages can trim", () => {
258289
name: "getEmails",
259290
args: '{"inboxName":"[email protected]","amount":5,"folder":"Inbox","searchString":null,"from":null,"subject":null,"cc":[],"bcc":[]}',
260291
id: "foobarbaz",
261-
index: 0,
262292
type: "tool_call_chunk",
263293
},
264294
],
265295
invalid_tool_calls: [],
296+
name: undefined,
266297
}),
267298
new ToolMessage({
268299
content: "a whole bunch of emails!",
269300
name: "getEmails",
270301
additional_kwargs: {},
271302
response_metadata: {},
272303
tool_call_id: "foobarbaz",
304+
artifact: undefined,
305+
id: undefined,
306+
status: undefined,
273307
}),
274308
];
275309
const trimmedMessages = await trimmer.invoke(messages);

langchain-core/src/messages/transformers.ts

+27-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
MessageType,
88
BaseMessageChunk,
99
BaseMessageFields,
10+
isBaseMessageChunk,
1011
} from "./base.js";
1112
import {
1213
ChatMessage,
@@ -56,16 +57,16 @@ const _isMessageType = (msg: BaseMessage, types: MessageTypeOrClass[]) => {
5657
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5758
const instantiatedMsgClass = new (t as any)({});
5859
if (
59-
!("_getType" in instantiatedMsgClass) ||
60-
typeof instantiatedMsgClass._getType !== "function"
60+
!("getType" in instantiatedMsgClass) ||
61+
typeof instantiatedMsgClass.getType !== "function"
6162
) {
6263
throw new Error("Invalid type provided.");
6364
}
64-
return instantiatedMsgClass._getType();
65+
return instantiatedMsgClass.getType();
6566
})
6667
),
6768
];
68-
const msgType = msg._getType();
69+
const msgType = msg.getType();
6970
return typesAsStrings.some((t) => t === msgType);
7071
};
7172

@@ -279,8 +280,8 @@ function _mergeMessageRuns(messages: BaseMessage[]): BaseMessage[] {
279280
if (!last) {
280281
merged.push(curr);
281282
} else if (
282-
curr._getType() === "tool" ||
283-
!(curr._getType() === last._getType())
283+
curr.getType() === "tool" ||
284+
!(curr.getType() === last.getType())
284285
) {
285286
merged.push(last, curr);
286287
} else {
@@ -767,7 +768,7 @@ async function _firstMaxTokens(
767768
([k]) => k !== "type" && !k.startsWith("lc_")
768769
)
769770
) as BaseMessageFields;
770-
const updatedMessage = _switchTypeToMessage(excluded._getType(), {
771+
const updatedMessage = _switchTypeToMessage(excluded.getType(), {
771772
...fields,
772773
content: partialContent,
773774
});
@@ -862,7 +863,18 @@ async function _lastMaxTokens(
862863
} = options;
863864

864865
// Create a copy of messages to avoid mutation
865-
let messagesCopy = [...messages];
866+
let messagesCopy = messages.map((message) => {
867+
const fields = Object.fromEntries(
868+
Object.entries(message).filter(
869+
([k]) => k !== "type" && !k.startsWith("lc_")
870+
)
871+
) as BaseMessageFields;
872+
return _switchTypeToMessage(
873+
message.getType(),
874+
fields,
875+
isBaseMessageChunk(message)
876+
);
877+
});
866878

867879
if (endOn) {
868880
const endOnArr = Array.isArray(endOn) ? endOn : [endOn];
@@ -875,7 +887,7 @@ async function _lastMaxTokens(
875887
}
876888

877889
const swappedSystem =
878-
includeSystem && messagesCopy[0]?._getType() === "system";
890+
includeSystem && messagesCopy[0]?.getType() === "system";
879891
let reversed_ = swappedSystem
880892
? messagesCopy.slice(0, 1).concat(messagesCopy.slice(1).reverse())
881893
: messagesCopy.reverse();
@@ -943,6 +955,11 @@ function _switchTypeToMessage(
943955
fields: BaseMessageFields,
944956
returnChunk: true
945957
): BaseMessageChunk;
958+
function _switchTypeToMessage(
959+
messageType: MessageType,
960+
fields: BaseMessageFields,
961+
returnChunk?: boolean
962+
): BaseMessageChunk | BaseMessage;
946963
function _switchTypeToMessage(
947964
messageType: MessageType,
948965
fields: BaseMessageFields,
@@ -1058,7 +1075,7 @@ function _switchTypeToMessage(
10581075
}
10591076

10601077
function _chunkToMsg(chunk: BaseMessageChunk): BaseMessage {
1061-
const chunkType = chunk._getType();
1078+
const chunkType = chunk.getType();
10621079
let msg: BaseMessage | undefined;
10631080
const fields = Object.fromEntries(
10641081
Object.entries(chunk).filter(

0 commit comments

Comments
 (0)