Skip to content

Conversation

@mcblum
Copy link
Contributor

@mcblum mcblum commented Sep 16, 2025

This PR updates the logic so that if tool chunk indices match and one chunk does not have an ID, the tool chunks are then merged.

Fixes #8985

@changeset-bot
Copy link

changeset-bot bot commented Sep 16, 2025

🦋 Changeset detected

Latest commit: e8cd966

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@langchain/core Patch
@langchain/standard-tests Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Sep 16, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
langchainjs-docs Ready Ready Preview Sep 17, 2025 4:51pm
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
langchainjs-api-refs Ignored Ignored Sep 17, 2025 4:51pm

@mcblum mcblum changed the title Update logic for merging tool call chunks to fix AWS Bedrock tool chunking implementation (fix) Update logic for merging tool call chunks to fix AWS Bedrock tool chunking implementation Sep 16, 2025
@hntrl hntrl changed the title (fix) Update logic for merging tool call chunks to fix AWS Bedrock tool chunking implementation fix(core): update chunk concat logic to match on missing ID fields Sep 16, 2025
@mcblum
Copy link
Contributor Author

mcblum commented Sep 16, 2025

@hntrl ok pushed a couple changes. I think either I'm losing it or we are reaching the logic where one or more IDs are undefined, because the tests still pass. Maybe the named conditions make it clearer?

@flipace
Copy link

flipace commented Sep 17, 2025

@mcblum i really have no idea anymore what's going on, but i tested this change locally w anthropic claude and I see chunks with 'id' being undefined so the check needs to also merge if id values are falsy?

(note: we're using portkey ai gateway in between our service and anthropic so maybe this is useful information too. we only have this issue with claude/anthropic atm though)

(sorry for the verbose output)

17:12:25.734 info {
  leftItem: {
    index: 0,
    id: 'toolu_01JZCDc1Y43MHPCsiyyZp9SU',
    type: 'function',
    function: { name: 'text_to_speech', arguments: '' }
  },
  item: { index: 0, function: { arguments: '' } }
}
17:12:25.734 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:12:25.734 info { toMerge: 0 }
17:12:25.735 info {
  leftItem: {
    name: 'text_to_speech',
    args: '',
    id: 'toolu_01JZCDc1Y43MHPCsiyyZp9SU',
    index: 0,
    type: 'tool_call_chunk'
  },
  item: {
    name: undefined,
    args: '',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  }
}
17:12:25.735 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: false }
17:12:25.735 info { toMerge: -1 }
17:12:25.863 info {
  leftItem: {
    index: 0,
    id: 'toolu_01JZCDc1Y43MHPCsiyyZp9SU',
    type: 'function',
    function: { name: 'text_to_speech', arguments: '' }
  },
  item: { index: 0, function: { arguments: '{"text": "h' } }
}
17:12:25.863 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:12:25.863 info { toMerge: 0 }
17:12:25.863 info {
  leftItem: {
    name: 'text_to_speech',
    args: '',
    id: 'toolu_01JZCDc1Y43MHPCsiyyZp9SU',
    index: 0,
    type: 'tool_call_chunk'
  },
  item: {
    name: undefined,
    args: '{"text": "h',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  }
}
17:12:25.863 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: false }
17:12:25.863 info {
  leftItem: {
    name: undefined,
    args: '',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  },
  item: {
    name: undefined,
    args: '{"text": "h',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  }
}
17:12:25.863 info { indiciesMatch: true, idsMatch: true, eitherItemMissingID: false }
17:12:25.863 info { toMerge: 1 }
17:12:25.864 info {
  leftItem: {
    index: 0,
    id: 'toolu_01JZCDc1Y43MHPCsiyyZp9SU',
    type: 'function',
    function: { name: 'text_to_speech', arguments: '{"text": "h' }
  },
  item: { index: 0, function: { arguments: 'allo welt"}' } }
}
17:12:25.864 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:12:25.864 info { toMerge: 0 }
17:12:25.864 info {
  leftItem: {
    name: 'text_to_speech',
    args: '',
    id: 'toolu_01JZCDc1Y43MHPCsiyyZp9SU',
    index: 0,
    type: 'tool_call_chunk'
  },
  item: {
    name: undefined,
    args: 'allo welt"}',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  }
}
17:12:25.864 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: false }
17:12:25.864 info {
  leftItem: {
    name: undefined,
    args: '{"text": "h',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  },
  item: {
    name: undefined,
    args: 'allo welt"}',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  }
}
17:12:25.864 info { indiciesMatch: true, idsMatch: true, eitherItemMissingID: false }
17:12:25.864 info { toMerge: 1 }

this ends up as (note: args are in a separate chunk now):

"tool_call_chunks": [
    {
      "name": "text_to_speech",
      "args": "",
      "id": "toolu_01JZCDc1Y43MHPCsiyyZp9SU",
      "index": 0,
      "type": "tool_call_chunk"
    },
    {
      "args": "{\"text\": \"hallo welt\"}",
      "index": 0,
      "type": "tool_call_chunk"
    }
  ]

also checking for falsy id values

adjusting this pr change slightly to:

const eitherItemMissingID = !("id" in leftItem) || !("id" in item) || !leftItem?.id || !item?.id;

leads to this (expected output):

17:14:17.925 info {
  leftItem: {
    index: 0,
    id: 'toolu_019JviDh5ExdUjZszB5KA1VA',
    type: 'function',
    function: { name: 'text_to_speech', arguments: '' }
  },
  item: { index: 0, function: { arguments: '' } }
}
17:14:17.926 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:14:17.926 info { toMerge: 0 }
17:14:17.926 info {
  leftItem: {
    name: 'text_to_speech',
    args: '',
    id: 'toolu_019JviDh5ExdUjZszB5KA1VA',
    index: 0,
    type: 'tool_call_chunk'
  },
  item: {
    name: undefined,
    args: '',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  }
}
17:14:17.926 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:14:17.926 info { toMerge: 0 }
17:14:18.300 info {
  leftItem: {
    index: 0,
    id: 'toolu_019JviDh5ExdUjZszB5KA1VA',
    type: 'function',
    function: { name: 'text_to_speech', arguments: '' }
  },
  item: { index: 0, function: { arguments: '{"t' } }
}
17:14:18.300 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:14:18.300 info { toMerge: 0 }
17:14:18.301 info {
  leftItem: {
    name: 'text_to_speech',
    args: '',
    id: 'toolu_019JviDh5ExdUjZszB5KA1VA',
    index: 0,
    type: 'tool_call_chunk'
  },
  item: {
    name: undefined,
    args: '{"t',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  }
}
17:14:18.301 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:14:18.301 info { toMerge: 0 }
17:14:18.301 info {
  leftItem: {
    index: 0,
    id: 'toolu_019JviDh5ExdUjZszB5KA1VA',
    type: 'function',
    function: { name: 'text_to_speech', arguments: '{"t' }
  },
  item: { index: 0, function: { arguments: 'ext": "hallo' } }
}
17:14:18.301 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:14:18.301 info { toMerge: 0 }
17:14:18.301 info {
  leftItem: {
    name: 'text_to_speech',
    args: '{"t',
    id: 'toolu_019JviDh5ExdUjZszB5KA1VA',
    index: 0,
    type: 'tool_call_chunk'
  },
  item: {
    name: undefined,
    args: 'ext": "hallo',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  }
}
17:14:18.302 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:14:18.302 info { toMerge: 0 }
17:14:18.320 info {
  leftItem: {
    index: 0,
    id: 'toolu_019JviDh5ExdUjZszB5KA1VA',
    type: 'function',
    function: { name: 'text_to_speech', arguments: '{"text": "hallo' }
  },
  item: { index: 0, function: { arguments: ' welt"}' } }
}
17:14:18.320 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:14:18.320 info { toMerge: 0 }
17:14:18.320 info {
  leftItem: {
    name: 'text_to_speech',
    args: '{"text": "hallo',
    id: 'toolu_019JviDh5ExdUjZszB5KA1VA',
    index: 0,
    type: 'tool_call_chunk'
  },
  item: {
    name: undefined,
    args: ' welt"}',
    id: undefined,
    index: 0,
    type: 'tool_call_chunk'
  }
}
17:14:18.320 info { indiciesMatch: true, idsMatch: false, eitherItemMissingID: true }
17:14:18.320 info { toMerge: 0 }

leads to the expected output:

"tool_call_chunks": [
    {
      "name": "text_to_speech",
      "args": "{\"text\": \"hallo welt\"}",
      "id": "toolu_019JviDh5ExdUjZszB5KA1VA",
      "index": 0,
      "type": "tool_call_chunk"
    }
  ]

@hntrl
Copy link
Member

hntrl commented Sep 17, 2025

@flipace yeah what you're seeing is a different issue that got reported this morning, but is relevant for this PR. Thanks for the suggestion, have added it!

@hntrl hntrl merged commit 1519a97 into langchain-ai:main Sep 17, 2025
41 checks passed
hntrl added a commit that referenced this pull request Sep 18, 2025
hntrl added a commit that referenced this pull request Sep 18, 2025
hntrl added a commit that referenced this pull request Sep 18, 2025
hntrl added a commit that referenced this pull request Oct 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bedrock tool call issues -- tool call chunking is unpredictable

3 participants