Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/afraid-paws-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@langchain/core": patch
---

update chunk concat logic to match on missing ID fields
24 changes: 13 additions & 11 deletions langchain-core/src/messages/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,17 +456,19 @@ export function _mergeLists<Content extends MessageContentComplex>(
"index" in item &&
typeof item.index === "number"
) {
const toMerge = merged.findIndex(
(leftItem) =>
leftItem !== null &&
typeof leftItem === "object" &&
"index" in leftItem &&
leftItem.index === item.index &&
// Only merge if IDs match (or both are undefined)
("id" in leftItem && "id" in item
? leftItem.id === item.id
: !("id" in leftItem) && !("id" in item))
);
const toMerge = merged.findIndex((leftItem) => {
const isObject = typeof leftItem === "object";
const indiciesMatch =
"index" in leftItem && leftItem.index === item.index;
const idsMatch =
"id" in leftItem && "id" in item && leftItem?.id === item?.id;
const eitherItemMissingID =
!("id" in leftItem) ||
!leftItem?.id ||
!("id" in item) ||
!item?.id;
return isObject && indiciesMatch && (idsMatch || eitherItemMissingID);
});
if (
toMerge !== -1 &&
typeof merged[toMerge] === "object" &&
Expand Down
69 changes: 68 additions & 1 deletion langchain-core/src/messages/tests/ai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it, expect } from "@jest/globals";
import { AIMessageChunk } from "../ai.js";

describe("AIMessageChunk", () => {
it("should properly merge tool call chunks", () => {
it("should properly merge tool call chunks that have matching indices and ids", () => {
const chunk1 = new AIMessageChunk({
content: "",
tool_call_chunks: [
Expand Down Expand Up @@ -45,4 +45,71 @@ describe("AIMessageChunk", () => {
);
expect(secondCall?.id).toBe("5abf542e-87f3-4899-87c6-8f7d9cb6a28d");
});

it("should properly merge tool call chunks that have matching indices and at least one id is blank", () => {
const chunk1 = new AIMessageChunk({
content: "",
tool_call_chunks: [
{
name: "add_new_task",
type: "tool_call_chunk",
index: 0,
id: "9fb5c937-6944-4173-84be-ad1caee1cedd",
},
],
});
const chunk2 = new AIMessageChunk({
content: "",
tool_call_chunks: [
{
args: '{"tasks":["buy tomatoes","help child with math"]}',
type: "tool_call_chunk",
index: 0,
},
],
});

const merged = chunk1.concat(chunk2);
expect(merged.tool_call_chunks).toHaveLength(1);

const firstCall = merged.tool_call_chunks?.[0];
expect(firstCall?.name).toBe("add_new_task");
expect(firstCall?.args).toBe(
'{"tasks":["buy tomatoes","help child with math"]}'
);
expect(firstCall?.id).toBe("9fb5c937-6944-4173-84be-ad1caee1cedd");
});

it("should properly merge tool call chunks that have matching indices no IDs at all", () => {
const chunk1 = new AIMessageChunk({
content: "",
tool_call_chunks: [
{
name: "add_new_task",
type: "tool_call_chunk",
index: 0,
},
],
});
const chunk2 = new AIMessageChunk({
content: "",
tool_call_chunks: [
{
args: '{"tasks":["buy tomatoes","help child with math"]}',
type: "tool_call_chunk",
index: 0,
},
],
});

const merged = chunk1.concat(chunk2);
expect(merged.tool_call_chunks).toHaveLength(1);

const firstCall = merged.tool_call_chunks?.[0];
expect(firstCall?.name).toBe("add_new_task");
expect(firstCall?.args).toBe(
'{"tasks":["buy tomatoes","help child with math"]}'
);
expect(firstCall?.id).toBeUndefined();
});
});
Loading