Skip to content

Commit 99b0758

Browse files
kibanamachineKDKHDelasticmachineqn895
authored
[9.2] [Bug][Inference chat model] Fix format for parallel tool call results. Consecutive tool results need to be merged into a single message (#241103) (#241389)
# Backport This will backport the following commits from `main` to `9.2`: - [[Bug][Inference chat model] Fix format for parallel tool call results. Consecutive tool results need to be merged into a single message (#241103)](#241103) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Kenneth Kreindler","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-10-30T22:29:13Z","message":"[Bug][Inference chat model] Fix format for parallel tool call results. Consecutive tool results need to be merged into a single message (#241103)\n\n## Summary\n\nSummarize your PR. If it involves visual changes include a screenshot or\ngif.\n\nFixes the following issue:\n\n<img width=\"847\" height=\"395\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/c38c89a9-5a81-4a5e-b371-f54fb027de6a\"\n/>\n\n```\nAn error occurred sending your message.\n\nError calling connector: Status code: 400. Message: API Error: Bad Request - Expected toolResult blocks at messages.2.content for the following Ids: tooluse_440cJcPjQhqjZSH5z5vbMg\n```\n\nFor Bedrock, the tool results from parallel tool calls need to be merged\ninto a single message containing all of the tool results in the content\narray. Without this, the Bedrock API raises an error.\n\n<details>\n\n<summary>Example</summary>\n\n### Incorrect\n\n```\n[ \n {\n \"role\": \"user\",\n \"content\": [\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_YdiyO2LeSImJoPDq4UYfSA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"Error: index_not_found_exception\\n\\tRoot causes:\\n\\t\\tindex_not_found_exception: no such index [.alerts-security.alerts-12]\\n Please fix your mistakes.\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_VHLTnO0sSbmWhHIpxg52YA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"[].\"\n }\n }\n ]\n }\n }\n ]\n }\n]\n```\n\n### Correct\n\n```\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_YdiyO2LeSImJoPDq4UYfSA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"Error: index_not_found_exception\\n\\tRoot causes:\\n\\t\\tindex_not_found_exception: no such index [.alerts-security.alerts-12]\\n Please fix your mistakes.\"\n }\n }\n ]\n }\n },\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_VHLTnO0sSbmWhHIpxg52YA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"[].\"\n }\n }\n ]\n }\n }\n ]\n }\n```\n\n</details>\n\n### How to test:\n- Create an Amazon Bedrock connector with Sonnet 4.5\n- Open an AI assistant and ask a question that produces parallel tool\ncalls. e.g.\n```\nCan you get me the count of my alerts and tell me how many acknowledged alerts I have? Call both tools in parallel. When making the parallel tool calls, also explain what you are doing. Always call both tools.\n```\nCheck this a few times and ensure there are no errors.\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [X] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [X]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials\n- [X] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [X] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n- [X] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n- [X] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [X] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n- [X] Review the [backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand apply applicable `backport:*` labels.\n\n### Identify risks\n\nDoes this PR introduce any risks? For example, consider risks like hard\nto test bugs, performance regression, potential of data loss.\n\nDescribe the risk, its severity, and mitigation for each identified\nrisk. Invite stakeholders and evaluate how to proceed before merging.\n\n- [ ] [See some risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n- [ ] ...\n\n---------\n\nCo-authored-by: Elastic Machine <[email protected]>\nCo-authored-by: Quynh Nguyen <[email protected]>","sha":"c78f74a7f227019ad12e923513c912172092d902","branchLabelMapping":{"^v9.3.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:Security Generative AI","backport:version","v9.1.0","v8.19.0","v9.2.0","v9.3.0"],"title":"[Bug][Inference chat model] Fix format for parallel tool call results. Consecutive tool results need to be merged into a single message","number":241103,"url":"https://github.com/elastic/kibana/pull/241103","mergeCommit":{"message":"[Bug][Inference chat model] Fix format for parallel tool call results. Consecutive tool results need to be merged into a single message (#241103)\n\n## Summary\n\nSummarize your PR. If it involves visual changes include a screenshot or\ngif.\n\nFixes the following issue:\n\n<img width=\"847\" height=\"395\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/c38c89a9-5a81-4a5e-b371-f54fb027de6a\"\n/>\n\n```\nAn error occurred sending your message.\n\nError calling connector: Status code: 400. Message: API Error: Bad Request - Expected toolResult blocks at messages.2.content for the following Ids: tooluse_440cJcPjQhqjZSH5z5vbMg\n```\n\nFor Bedrock, the tool results from parallel tool calls need to be merged\ninto a single message containing all of the tool results in the content\narray. Without this, the Bedrock API raises an error.\n\n<details>\n\n<summary>Example</summary>\n\n### Incorrect\n\n```\n[ \n {\n \"role\": \"user\",\n \"content\": [\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_YdiyO2LeSImJoPDq4UYfSA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"Error: index_not_found_exception\\n\\tRoot causes:\\n\\t\\tindex_not_found_exception: no such index [.alerts-security.alerts-12]\\n Please fix your mistakes.\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_VHLTnO0sSbmWhHIpxg52YA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"[].\"\n }\n }\n ]\n }\n }\n ]\n }\n]\n```\n\n### Correct\n\n```\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_YdiyO2LeSImJoPDq4UYfSA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"Error: index_not_found_exception\\n\\tRoot causes:\\n\\t\\tindex_not_found_exception: no such index [.alerts-security.alerts-12]\\n Please fix your mistakes.\"\n }\n }\n ]\n }\n },\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_VHLTnO0sSbmWhHIpxg52YA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"[].\"\n }\n }\n ]\n }\n }\n ]\n }\n```\n\n</details>\n\n### How to test:\n- Create an Amazon Bedrock connector with Sonnet 4.5\n- Open an AI assistant and ask a question that produces parallel tool\ncalls. e.g.\n```\nCan you get me the count of my alerts and tell me how many acknowledged alerts I have? Call both tools in parallel. When making the parallel tool calls, also explain what you are doing. Always call both tools.\n```\nCheck this a few times and ensure there are no errors.\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [X] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [X]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials\n- [X] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [X] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n- [X] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n- [X] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [X] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n- [X] Review the [backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand apply applicable `backport:*` labels.\n\n### Identify risks\n\nDoes this PR introduce any risks? For example, consider risks like hard\nto test bugs, performance regression, potential of data loss.\n\nDescribe the risk, its severity, and mitigation for each identified\nrisk. Invite stakeholders and evaluate how to proceed before merging.\n\n- [ ] [See some risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n- [ ] ...\n\n---------\n\nCo-authored-by: Elastic Machine <[email protected]>\nCo-authored-by: Quynh Nguyen <[email protected]>","sha":"c78f74a7f227019ad12e923513c912172092d902"}},"sourceBranch":"main","suggestedTargetBranches":["9.1","8.19","9.2"],"targetPullRequestStates":[{"branch":"9.1","label":"v9.1.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.19","label":"v8.19.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.2","label":"v9.2.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.3.0","branchLabelMappingKey":"^v9.3.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/241103","number":241103,"mergeCommit":{"message":"[Bug][Inference chat model] Fix format for parallel tool call results. Consecutive tool results need to be merged into a single message (#241103)\n\n## Summary\n\nSummarize your PR. If it involves visual changes include a screenshot or\ngif.\n\nFixes the following issue:\n\n<img width=\"847\" height=\"395\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/c38c89a9-5a81-4a5e-b371-f54fb027de6a\"\n/>\n\n```\nAn error occurred sending your message.\n\nError calling connector: Status code: 400. Message: API Error: Bad Request - Expected toolResult blocks at messages.2.content for the following Ids: tooluse_440cJcPjQhqjZSH5z5vbMg\n```\n\nFor Bedrock, the tool results from parallel tool calls need to be merged\ninto a single message containing all of the tool results in the content\narray. Without this, the Bedrock API raises an error.\n\n<details>\n\n<summary>Example</summary>\n\n### Incorrect\n\n```\n[ \n {\n \"role\": \"user\",\n \"content\": [\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_YdiyO2LeSImJoPDq4UYfSA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"Error: index_not_found_exception\\n\\tRoot causes:\\n\\t\\tindex_not_found_exception: no such index [.alerts-security.alerts-12]\\n Please fix your mistakes.\"\n }\n }\n ]\n }\n }\n ]\n },\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_VHLTnO0sSbmWhHIpxg52YA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"[].\"\n }\n }\n ]\n }\n }\n ]\n }\n]\n```\n\n### Correct\n\n```\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_YdiyO2LeSImJoPDq4UYfSA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"Error: index_not_found_exception\\n\\tRoot causes:\\n\\t\\tindex_not_found_exception: no such index [.alerts-security.alerts-12]\\n Please fix your mistakes.\"\n }\n }\n ]\n }\n },\n {\n \"toolResult\": {\n \"toolUseId\": \"tooluse_VHLTnO0sSbmWhHIpxg52YA\",\n \"content\": [\n {\n \"json\": {\n \"response\": \"[].\"\n }\n }\n ]\n }\n }\n ]\n }\n```\n\n</details>\n\n### How to test:\n- Create an Amazon Bedrock connector with Sonnet 4.5\n- Open an AI assistant and ask a question that produces parallel tool\ncalls. e.g.\n```\nCan you get me the count of my alerts and tell me how many acknowledged alerts I have? Call both tools in parallel. When making the parallel tool calls, also explain what you are doing. Always call both tools.\n```\nCheck this a few times and ensure there are no errors.\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [X] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [X]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials\n- [X] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [X] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n- [X] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n- [X] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [X] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n- [X] Review the [backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand apply applicable `backport:*` labels.\n\n### Identify risks\n\nDoes this PR introduce any risks? For example, consider risks like hard\nto test bugs, performance regression, potential of data loss.\n\nDescribe the risk, its severity, and mitigation for each identified\nrisk. Invite stakeholders and evaluate how to proceed before merging.\n\n- [ ] [See some risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n- [ ] ...\n\n---------\n\nCo-authored-by: Elastic Machine <[email protected]>\nCo-authored-by: Quynh Nguyen <[email protected]>","sha":"c78f74a7f227019ad12e923513c912172092d902"}}]}] BACKPORT--> Co-authored-by: Kenneth Kreindler <[email protected]> Co-authored-by: Elastic Machine <[email protected]> Co-authored-by: Quynh Nguyen <[email protected]>
1 parent 1b0620b commit 99b0758

File tree

2 files changed

+162
-1
lines changed

2 files changed

+162
-1
lines changed

x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/bedrock/bedrock_claude_adapter.test.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,146 @@ Human:`,
236236
]);
237237
});
238238

239+
it('correctly format consecutive tool result messages', () => {
240+
bedrockClaudeAdapter
241+
.chatComplete({
242+
executor: executorMock,
243+
logger,
244+
messages: [
245+
{
246+
role: MessageRole.User,
247+
content: 'question',
248+
},
249+
{
250+
role: MessageRole.Assistant,
251+
content: 'answer',
252+
},
253+
{
254+
role: MessageRole.User,
255+
content: 'another question',
256+
},
257+
{
258+
role: MessageRole.Assistant,
259+
content: null,
260+
toolCalls: [
261+
{
262+
function: {
263+
name: 'my_function',
264+
arguments: {
265+
foo: 'bar',
266+
},
267+
},
268+
toolCallId: '0',
269+
},
270+
{
271+
function: {
272+
name: 'my_other_function',
273+
arguments: {
274+
baz: 'qux',
275+
},
276+
},
277+
toolCallId: '1',
278+
},
279+
],
280+
},
281+
{
282+
name: 'my_function',
283+
role: MessageRole.Tool,
284+
toolCallId: '0',
285+
response: {
286+
bar: 'foo',
287+
},
288+
},
289+
{
290+
name: 'my_other_function',
291+
role: MessageRole.Tool,
292+
toolCallId: '1',
293+
response: {
294+
qux: 'baz',
295+
},
296+
},
297+
{
298+
role: MessageRole.Assistant,
299+
content: null,
300+
toolCalls: [
301+
{
302+
function: {
303+
name: 'my_function_2',
304+
arguments: {
305+
foo: 'bar',
306+
},
307+
},
308+
toolCallId: '2',
309+
},
310+
{
311+
function: {
312+
name: 'my_other_function_2',
313+
arguments: {
314+
baz: 'qux',
315+
},
316+
},
317+
toolCallId: '3',
318+
},
319+
],
320+
},
321+
{
322+
name: 'my_function_2',
323+
role: MessageRole.Tool,
324+
toolCallId: '2',
325+
response: {
326+
bar: 'foo',
327+
},
328+
},
329+
{
330+
name: 'my_other_function_2',
331+
role: MessageRole.Tool,
332+
toolCallId: '3',
333+
response: {
334+
qux: 'baz',
335+
},
336+
},
337+
],
338+
})
339+
.subscribe(noop);
340+
341+
expect(executorMock.invoke).toHaveBeenCalledTimes(1);
342+
343+
const { messages } = getCallParams();
344+
expect(messages).toEqual([
345+
{ role: 'user', content: [{ text: 'question' }] },
346+
{ role: 'assistant', content: [{ text: 'answer' }] },
347+
{ role: 'user', content: [{ text: 'another question' }] },
348+
{
349+
role: 'assistant',
350+
content: [
351+
{ toolUse: { toolUseId: '0', name: 'my_function', input: { foo: 'bar' } } },
352+
{ toolUse: { toolUseId: '1', name: 'my_other_function', input: { baz: 'qux' } } },
353+
],
354+
},
355+
{
356+
role: 'user',
357+
content: [
358+
{ toolResult: { toolUseId: '0', content: [{ json: { bar: 'foo' } }] } },
359+
{ toolResult: { toolUseId: '1', content: [{ json: { qux: 'baz' } }] } },
360+
],
361+
},
362+
{
363+
role: 'assistant',
364+
content: [
365+
{ toolUse: { toolUseId: '2', name: 'my_function_2', input: { foo: 'bar' } } },
366+
{ toolUse: { toolUseId: '3', name: 'my_other_function_2', input: { baz: 'qux' } } },
367+
],
368+
},
369+
{
370+
role: 'user',
371+
content: [
372+
{ toolResult: { toolUseId: '2', content: [{ json: { bar: 'foo' } }] } },
373+
{ toolResult: { toolUseId: '3', content: [{ json: { qux: 'baz' } }] } },
374+
],
375+
},
376+
]);
377+
});
378+
239379
it('correctly format system message', () => {
240380
bedrockClaudeAdapter
241381
.chatComplete({

x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/bedrock/bedrock_claude_adapter.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export const bedrockClaudeAdapter: InferenceConnectorAdapter = {
9696
};
9797

9898
const messagesToBedrock = (messages: Message[]): BedRockMessage[] => {
99-
return messages.map((message): BedRockMessage => {
99+
const converseMessages: BedRockMessage[] = messages.map((message): BedRockMessage => {
100100
switch (message.role) {
101101
case MessageRole.User: {
102102
const rawContent: BedRockMessage['rawContent'] = [];
@@ -180,4 +180,25 @@ const messagesToBedrock = (messages: Message[]): BedRockMessage[] => {
180180
}
181181
}
182182
});
183+
184+
// Combine consecutive user tool result messages into a single message. This format is required by Bedrock.
185+
const combinedConverseMessages = converseMessages.reduce<BedRockMessage[]>((acc, curr) => {
186+
const lastMessage = acc[acc.length - 1];
187+
188+
if (
189+
lastMessage &&
190+
lastMessage.role === 'user' &&
191+
lastMessage.rawContent?.some((c) => 'toolResult' in c) &&
192+
curr.role === 'user' &&
193+
curr.rawContent?.some((c) => 'toolResult' in c)
194+
) {
195+
lastMessage.rawContent = lastMessage.rawContent.concat(curr.rawContent);
196+
} else {
197+
acc.push(curr);
198+
}
199+
200+
return acc;
201+
}, []);
202+
203+
return combinedConverseMessages;
183204
};

0 commit comments

Comments
 (0)