Skip to content

Commit 8c9e1c5

Browse files
mcblumhntrl
andcommitted
fix(core): update chunk concat logic to match on missing ID fields (#8987)
Co-authored-by: Hunter Lovell <[email protected]> Co-authored-by: Hunter Lovell <[email protected]>
1 parent ba39987 commit 8c9e1c5

File tree

3 files changed

+86
-12
lines changed

3 files changed

+86
-12
lines changed

.changeset/afraid-paws-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@langchain/core": patch
3+
---
4+
5+
update chunk concat logic to match on missing ID fields

libs/langchain-core/src/messages/base.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -473,17 +473,19 @@ export function _mergeLists<Content extends ContentBlock>(
473473
"index" in item &&
474474
typeof item.index === "number"
475475
) {
476-
const toMerge = merged.findIndex(
477-
(leftItem) =>
478-
leftItem !== null &&
479-
typeof leftItem === "object" &&
480-
"index" in leftItem &&
481-
leftItem.index === item.index &&
482-
// Only merge if IDs match (or both are undefined)
483-
("id" in leftItem && "id" in item
484-
? leftItem.id === item.id
485-
: !("id" in leftItem) && !("id" in item))
486-
);
476+
const toMerge = merged.findIndex((leftItem) => {
477+
const isObject = typeof leftItem === "object";
478+
const indiciesMatch =
479+
"index" in leftItem && leftItem.index === item.index;
480+
const idsMatch =
481+
"id" in leftItem && "id" in item && leftItem?.id === item?.id;
482+
const eitherItemMissingID =
483+
!("id" in leftItem) ||
484+
!leftItem?.id ||
485+
!("id" in item) ||
486+
!item?.id;
487+
return isObject && indiciesMatch && (idsMatch || eitherItemMissingID);
488+
});
487489
if (
488490
toMerge !== -1 &&
489491
typeof merged[toMerge] === "object" &&

libs/langchain-core/src/messages/tests/ai.test.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ describe("AIMessage", () => {
163163
});
164164

165165
describe("AIMessageChunk", () => {
166-
it("should properly merge tool call chunks", () => {
166+
it("should properly merge tool call chunks that have matching indices and ids", () => {
167167
const chunk1 = new AIMessageChunk({
168168
content: "",
169169
tool_call_chunks: [
@@ -206,4 +206,71 @@ describe("AIMessageChunk", () => {
206206
);
207207
expect(secondCall?.id).toBe("5abf542e-87f3-4899-87c6-8f7d9cb6a28d");
208208
});
209+
210+
it("should properly merge tool call chunks that have matching indices and at least one id is blank", () => {
211+
const chunk1 = new AIMessageChunk({
212+
content: "",
213+
tool_call_chunks: [
214+
{
215+
name: "add_new_task",
216+
type: "tool_call_chunk",
217+
index: 0,
218+
id: "9fb5c937-6944-4173-84be-ad1caee1cedd",
219+
},
220+
],
221+
});
222+
const chunk2 = new AIMessageChunk({
223+
content: "",
224+
tool_call_chunks: [
225+
{
226+
args: '{"tasks":["buy tomatoes","help child with math"]}',
227+
type: "tool_call_chunk",
228+
index: 0,
229+
},
230+
],
231+
});
232+
233+
const merged = chunk1.concat(chunk2);
234+
expect(merged.tool_call_chunks).toHaveLength(1);
235+
236+
const firstCall = merged.tool_call_chunks?.[0];
237+
expect(firstCall?.name).toBe("add_new_task");
238+
expect(firstCall?.args).toBe(
239+
'{"tasks":["buy tomatoes","help child with math"]}'
240+
);
241+
expect(firstCall?.id).toBe("9fb5c937-6944-4173-84be-ad1caee1cedd");
242+
});
243+
244+
it("should properly merge tool call chunks that have matching indices no IDs at all", () => {
245+
const chunk1 = new AIMessageChunk({
246+
content: "",
247+
tool_call_chunks: [
248+
{
249+
name: "add_new_task",
250+
type: "tool_call_chunk",
251+
index: 0,
252+
},
253+
],
254+
});
255+
const chunk2 = new AIMessageChunk({
256+
content: "",
257+
tool_call_chunks: [
258+
{
259+
args: '{"tasks":["buy tomatoes","help child with math"]}',
260+
type: "tool_call_chunk",
261+
index: 0,
262+
},
263+
],
264+
});
265+
266+
const merged = chunk1.concat(chunk2);
267+
expect(merged.tool_call_chunks).toHaveLength(1);
268+
269+
const firstCall = merged.tool_call_chunks?.[0];
270+
expect(firstCall?.name).toBe("add_new_task");
271+
expect(firstCall?.args).toBe(
272+
'{"tasks":["buy tomatoes","help child with math"]}'
273+
);
274+
expect(firstCall?.id).toBeUndefined();
275+
});
209276
});

0 commit comments

Comments
 (0)