diff --git a/dotnet/src/Connectors/Connectors.Amazon.UnitTests/Core/BedrockModelUtilitiesTests.cs b/dotnet/src/Connectors/Connectors.Amazon.UnitTests/Core/BedrockModelUtilitiesTests.cs
new file mode 100644
index 000000000000..4e00a010f7d5
--- /dev/null
+++ b/dotnet/src/Connectors/Connectors.Amazon.UnitTests/Core/BedrockModelUtilitiesTests.cs
@@ -0,0 +1,380 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+using Amazon.BedrockRuntime;
+using Amazon.BedrockRuntime.Model;
+using Microsoft.SemanticKernel.ChatCompletion;
+using Xunit;
+
+namespace Microsoft.SemanticKernel.Connectors.Amazon.UnitTests;
+
+///
+/// Unit tests for BedrockModelUtilities, specifically the BuildMessageList method
+/// and its handling of tool call / tool result merging.
+///
+public sealed class BedrockModelUtilitiesTests
+{
+ ///
+ /// Verifies that simple text messages are converted correctly.
+ ///
+ [Fact]
+ public void BuildMessageListShouldConvertSimpleTextMessages()
+ {
+ // Arrange
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage("Hello");
+ chatHistory.AddAssistantMessage("Hi there");
+ chatHistory.AddUserMessage("How are you?");
+
+ // Act
+ var messages = Core.BedrockModelUtilities.BuildMessageList(chatHistory);
+
+ // Assert
+ Assert.Equal(3, messages.Count);
+ Assert.Equal(ConversationRole.User, messages[0].Role);
+ Assert.Equal("Hello", messages[0].Content[0].Text);
+ Assert.Equal(ConversationRole.Assistant, messages[1].Role);
+ Assert.Equal("Hi there", messages[1].Content[0].Text);
+ Assert.Equal(ConversationRole.User, messages[2].Role);
+ Assert.Equal("How are you?", messages[2].Content[0].Text);
+ }
+
+ ///
+ /// Verifies that system messages are excluded from the message list.
+ ///
+ [Fact]
+ public void BuildMessageListShouldExcludeSystemMessages()
+ {
+ // Arrange
+ var chatHistory = new ChatHistory();
+ chatHistory.AddSystemMessage("You are an AI assistant");
+ chatHistory.AddUserMessage("Hello");
+
+ // Act
+ var messages = Core.BedrockModelUtilities.BuildMessageList(chatHistory);
+
+ // Assert
+ Assert.Single(messages);
+ Assert.Equal(ConversationRole.User, messages[0].Role);
+ Assert.Equal("Hello", messages[0].Content[0].Text);
+ }
+
+ ///
+ /// Verifies that consecutive assistant messages with FunctionCallContent are merged into a single message.
+ /// This is the core scenario from issue #13647.
+ ///
+ [Fact]
+ public void BuildMessageListShouldMergeConsecutiveAssistantToolCalls()
+ {
+ // Arrange
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage("Get feeds and distribution list");
+
+ // First assistant message with a tool call
+ var assistantMessage1 = new ChatMessageContent(AuthorRole.Assistant, (string?)null);
+ assistantMessage1.Items.Add(new FunctionCallContent(
+ functionName: "GetFeeds",
+ pluginName: "Feeds",
+ id: "tooluse_G64hibpFmRqXEcAYwOfP5s"));
+ chatHistory.Add(assistantMessage1);
+
+ // Second assistant message with another tool call (parallel tool invocation)
+ var assistantMessage2 = new ChatMessageContent(AuthorRole.Assistant, (string?)null);
+ assistantMessage2.Items.Add(new FunctionCallContent(
+ functionName: "GetTeamsDistributionList",
+ pluginName: "DistributionList",
+ id: "tooluse_sMmlRWbbCGd0lnhiLjvk8H",
+ arguments: new KernelArguments { { "distributionListName", "developers" } }));
+ chatHistory.Add(assistantMessage2);
+
+ // Act
+ var messages = Core.BedrockModelUtilities.BuildMessageList(chatHistory);
+
+ // Assert
+ Assert.Equal(2, messages.Count); // user + single merged assistant
+
+ // The assistant message should have been merged
+ var assistantMsg = messages[1];
+ Assert.Equal(ConversationRole.Assistant, assistantMsg.Role);
+ Assert.Equal(2, assistantMsg.Content.Count);
+
+ // First tool use
+ Assert.NotNull(assistantMsg.Content[0].ToolUse);
+ Assert.Equal("tooluse_G64hibpFmRqXEcAYwOfP5s", assistantMsg.Content[0].ToolUse.ToolUseId);
+ Assert.Equal("Feeds-GetFeeds", assistantMsg.Content[0].ToolUse.Name);
+
+ // Second tool use
+ Assert.NotNull(assistantMsg.Content[1].ToolUse);
+ Assert.Equal("tooluse_sMmlRWbbCGd0lnhiLjvk8H", assistantMsg.Content[1].ToolUse.ToolUseId);
+ Assert.Equal("DistributionList-GetTeamsDistributionList", assistantMsg.Content[1].ToolUse.Name);
+ }
+
+ ///
+ /// Verifies that consecutive tool result messages are merged into a single user message.
+ /// This is part of the scenario from issue #13647.
+ ///
+ [Fact]
+ public void BuildMessageListShouldMergeConsecutiveToolResults()
+ {
+ // Arrange
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage("Get feeds and distribution list");
+
+ // Assistant with tool calls (single message with multiple items)
+ var assistantMessage = new ChatMessageContent(AuthorRole.Assistant, (string?)null);
+ assistantMessage.Items.Add(new FunctionCallContent(
+ functionName: "GetFeeds",
+ pluginName: "Feeds",
+ id: "tooluse_G64hibpFmRqXEcAYwOfP5s"));
+ assistantMessage.Items.Add(new FunctionCallContent(
+ functionName: "GetTeamsDistributionList",
+ pluginName: "DistributionList",
+ id: "tooluse_sMmlRWbbCGd0lnhiLjvk8H",
+ arguments: new KernelArguments { { "distributionListName", "developers" } }));
+ chatHistory.Add(assistantMessage);
+
+ // First tool result
+ var toolResult1 = new ChatMessageContent(AuthorRole.Tool, (string?)null);
+ toolResult1.Items.Add(new FunctionResultContent(
+ functionName: "GetFeeds",
+ pluginName: "Feeds",
+ callId: "tooluse_G64hibpFmRqXEcAYwOfP5s",
+ result: "Dataset DataReference: memory://8df2be02a23c4ebfb77e1170dea6c4b4. Dataset size: 78 items."));
+ chatHistory.Add(toolResult1);
+
+ // Second tool result
+ var toolResult2 = new ChatMessageContent(AuthorRole.Tool, (string?)null);
+ toolResult2.Items.Add(new FunctionResultContent(
+ functionName: "GetTeamsDistributionList",
+ pluginName: "DistributionList",
+ callId: "tooluse_sMmlRWbbCGd0lnhiLjvk8H",
+ result: "19:791df3464b4f4c3fac8429041e8e2540@thread.v2"));
+ chatHistory.Add(toolResult2);
+
+ // Act
+ var messages = Core.BedrockModelUtilities.BuildMessageList(chatHistory);
+
+ // Assert
+ Assert.Equal(3, messages.Count); // user + assistant + single merged tool results (as user)
+
+ // Tool results should be merged into a single user message
+ var toolResultMsg = messages[2];
+ Assert.Equal(ConversationRole.User, toolResultMsg.Role); // Tool role maps to User in Bedrock
+ Assert.Equal(2, toolResultMsg.Content.Count);
+
+ // First tool result
+ Assert.NotNull(toolResultMsg.Content[0].ToolResult);
+ Assert.Equal("tooluse_G64hibpFmRqXEcAYwOfP5s", toolResultMsg.Content[0].ToolResult.ToolUseId);
+ Assert.Equal("Dataset DataReference: memory://8df2be02a23c4ebfb77e1170dea6c4b4. Dataset size: 78 items.",
+ toolResultMsg.Content[0].ToolResult.Content[0].Text);
+
+ // Second tool result
+ Assert.NotNull(toolResultMsg.Content[1].ToolResult);
+ Assert.Equal("tooluse_sMmlRWbbCGd0lnhiLjvk8H", toolResultMsg.Content[1].ToolResult.ToolUseId);
+ Assert.Equal("19:791df3464b4f4c3fac8429041e8e2540@thread.v2",
+ toolResultMsg.Content[1].ToolResult.Content[0].Text);
+ }
+
+ ///
+ /// Full round-trip test matching the exact scenario from issue #13647:
+ /// separate assistant tool-call messages + separate tool-result messages.
+ /// After merging, the Bedrock message list should have properly coalesced messages.
+ ///
+ [Fact]
+ public void BuildMessageListShouldProduceValidBedrockMessagesForParallelToolCalls()
+ {
+ // Arrange: Reproduce the exact ChatHistory from the issue
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage("Audit the feeds and get the developers distribution list");
+
+ // Two separate assistant messages with tool calls (as SK creates them)
+ var assistantMsg1 = new ChatMessageContent(AuthorRole.Assistant, (string?)null);
+ assistantMsg1.Items.Add(new FunctionCallContent(
+ functionName: "GetFeeds",
+ pluginName: "Feeds",
+ id: "tooluse_G64hibpFmRqXEcAYwOfP5s"));
+ chatHistory.Add(assistantMsg1);
+
+ var assistantMsg2 = new ChatMessageContent(AuthorRole.Assistant, (string?)null);
+ assistantMsg2.Items.Add(new FunctionCallContent(
+ functionName: "GetTeamsDistributionList",
+ pluginName: "DistributionList",
+ id: "tooluse_sMmlRWbbCGd0lnhiLjvk8H",
+ arguments: new KernelArguments { { "distributionListName", "developers" } }));
+ chatHistory.Add(assistantMsg2);
+
+ // Two separate tool result messages
+ var toolResult1 = new ChatMessageContent(AuthorRole.Tool, (string?)null);
+ toolResult1.Items.Add(new FunctionResultContent(
+ functionName: "GetFeeds",
+ pluginName: "Feeds",
+ callId: "tooluse_G64hibpFmRqXEcAYwOfP5s",
+ result: "Dataset DataReference: memory://8df2be02a23c4ebfb77e1170dea6c4b4. Dataset size: 78 items."));
+ chatHistory.Add(toolResult1);
+
+ var toolResult2 = new ChatMessageContent(AuthorRole.Tool, (string?)null);
+ toolResult2.Items.Add(new FunctionResultContent(
+ functionName: "GetTeamsDistributionList",
+ pluginName: "DistributionList",
+ callId: "tooluse_sMmlRWbbCGd0lnhiLjvk8H",
+ result: "19:791df3464b4f4c3fac8429041e8e2540@thread.v2"));
+ chatHistory.Add(toolResult2);
+
+ // Act
+ var messages = Core.BedrockModelUtilities.BuildMessageList(chatHistory);
+
+ // Assert: Should produce exactly 3 messages: user, assistant (merged), user/tool-results (merged)
+ Assert.Equal(3, messages.Count);
+
+ // Message 1: User prompt
+ Assert.Equal(ConversationRole.User, messages[0].Role);
+ Assert.Single(messages[0].Content);
+ Assert.Equal("Audit the feeds and get the developers distribution list", messages[0].Content[0].Text);
+
+ // Message 2: Merged assistant with both tool use blocks
+ Assert.Equal(ConversationRole.Assistant, messages[1].Role);
+ Assert.Equal(2, messages[1].Content.Count);
+ Assert.NotNull(messages[1].Content[0].ToolUse);
+ Assert.NotNull(messages[1].Content[1].ToolUse);
+
+ // Verify tool use IDs are preserved correctly
+ var toolUseIds = messages[1].Content.Select(c => c.ToolUse.ToolUseId).ToList();
+ Assert.Contains("tooluse_G64hibpFmRqXEcAYwOfP5s", toolUseIds);
+ Assert.Contains("tooluse_sMmlRWbbCGd0lnhiLjvk8H", toolUseIds);
+
+ // Message 3: Merged tool results (as user role)
+ Assert.Equal(ConversationRole.User, messages[2].Role);
+ Assert.Equal(2, messages[2].Content.Count);
+ Assert.NotNull(messages[2].Content[0].ToolResult);
+ Assert.NotNull(messages[2].Content[1].ToolResult);
+
+ // Verify tool result IDs match the tool use IDs
+ var toolResultIds = messages[2].Content.Select(c => c.ToolResult.ToolUseId).ToList();
+ Assert.Contains("tooluse_G64hibpFmRqXEcAYwOfP5s", toolResultIds);
+ Assert.Contains("tooluse_sMmlRWbbCGd0lnhiLjvk8H", toolResultIds);
+ }
+
+ ///
+ /// Verifies that Tool role maps to User in the Bedrock conversation role.
+ ///
+ [Fact]
+ public void MapAuthorRoleToConversationRoleShouldMapToolToUser()
+ {
+ // Act
+ var result = Core.BedrockModelUtilities.MapAuthorRoleToConversationRole(AuthorRole.Tool);
+
+ // Assert
+ Assert.Equal(ConversationRole.User, result);
+ }
+
+ ///
+ /// Verifies that FunctionCallContent without a plugin name uses just the function name.
+ ///
+ [Fact]
+ public void BuildMessageListShouldUseOnlyFunctionNameWhenNoPluginName()
+ {
+ // Arrange
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage("Do something");
+
+ var assistantMessage = new ChatMessageContent(AuthorRole.Assistant, (string?)null);
+ assistantMessage.Items.Add(new FunctionCallContent(
+ functionName: "MyFunction",
+ id: "tool_123"));
+ chatHistory.Add(assistantMessage);
+
+ // Act
+ var messages = Core.BedrockModelUtilities.BuildMessageList(chatHistory);
+
+ // Assert
+ Assert.Equal(2, messages.Count);
+ Assert.NotNull(messages[1].Content[0].ToolUse);
+ Assert.Equal("MyFunction", messages[1].Content[0].ToolUse.Name);
+ }
+
+ ///
+ /// Verifies that tool call arguments are properly converted to Document.
+ ///
+ [Fact]
+ public void BuildMessageListShouldConvertFunctionCallArguments()
+ {
+ // Arrange
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage("Search for something");
+
+ var assistantMessage = new ChatMessageContent(AuthorRole.Assistant, (string?)null);
+ assistantMessage.Items.Add(new FunctionCallContent(
+ functionName: "Search",
+ pluginName: "Web",
+ id: "tool_456",
+ arguments: new KernelArguments
+ {
+ { "query", "semantic kernel" },
+ { "maxResults", 10 }
+ }));
+ chatHistory.Add(assistantMessage);
+
+ // Act
+ var messages = Core.BedrockModelUtilities.BuildMessageList(chatHistory);
+
+ // Assert
+ var toolUse = messages[1].Content[0].ToolUse;
+ Assert.NotNull(toolUse);
+ Assert.Equal("Web-Search", toolUse.Name);
+ Assert.True(toolUse.Input.IsDictionary());
+ var inputDict = toolUse.Input.AsDictionary();
+ Assert.Equal("semantic kernel", inputDict["query"].AsString());
+ Assert.Equal(10, inputDict["maxResults"].AsInt());
+ }
+
+ ///
+ /// Verifies that non-consecutive same-role messages are NOT merged.
+ ///
+ [Fact]
+ public void BuildMessageListShouldNotMergeNonConsecutiveSameRoleMessages()
+ {
+ // Arrange
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage("Hello");
+ chatHistory.AddAssistantMessage("Hi");
+ chatHistory.AddUserMessage("How are you?");
+
+ // Act
+ var messages = Core.BedrockModelUtilities.BuildMessageList(chatHistory);
+
+ // Assert: All three messages should be separate
+ Assert.Equal(3, messages.Count);
+ }
+
+ ///
+ /// Verifies that mixed text and tool content in a single assistant message works.
+ ///
+ [Fact]
+ public void BuildMessageListShouldHandleAssistantMessageWithTextAndToolCall()
+ {
+ // Arrange
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage("What's the weather?");
+
+ var assistantMessage = new ChatMessageContent(AuthorRole.Assistant, (string?)null);
+ assistantMessage.Items.Add(new TextContent("Let me check the weather for you."));
+ assistantMessage.Items.Add(new FunctionCallContent(
+ functionName: "GetWeather",
+ pluginName: "Weather",
+ id: "tool_789",
+ arguments: new KernelArguments { { "city", "Seattle" } }));
+ chatHistory.Add(assistantMessage);
+
+ // Act
+ var messages = Core.BedrockModelUtilities.BuildMessageList(chatHistory);
+
+ // Assert
+ Assert.Equal(2, messages.Count);
+ var assistantMsg = messages[1];
+ Assert.Equal(2, assistantMsg.Content.Count);
+ Assert.Equal("Let me check the weather for you.", assistantMsg.Content[0].Text);
+ Assert.NotNull(assistantMsg.Content[1].ToolUse);
+ Assert.Equal("Weather-GetWeather", assistantMsg.Content[1].ToolUse.Name);
+ }
+}
diff --git a/dotnet/src/Connectors/Connectors.Amazon/Bedrock/Core/BedrockModelUtilities.cs b/dotnet/src/Connectors/Connectors.Amazon/Bedrock/Core/BedrockModelUtilities.cs
index dac71cd8e142..4a884dfe02c1 100644
--- a/dotnet/src/Connectors/Connectors.Amazon/Bedrock/Core/BedrockModelUtilities.cs
+++ b/dotnet/src/Connectors/Connectors.Amazon/Bedrock/Core/BedrockModelUtilities.cs
@@ -5,6 +5,7 @@
using System.Linq;
using Amazon.BedrockRuntime;
using Amazon.BedrockRuntime.Model;
+using Amazon.Runtime.Documents;
using Microsoft.SemanticKernel.ChatCompletion;
namespace Microsoft.SemanticKernel.Connectors.Amazon.Core;
@@ -32,6 +33,12 @@ internal static ConversationRole MapAuthorRoleToConversationRole(AuthorRole role
return ConversationRole.Assistant;
}
+ // Tool role maps to User in Bedrock (tool results are sent as user messages).
+ if (role == AuthorRole.Tool)
+ {
+ return ConversationRole.User;
+ }
+
throw new ArgumentOutOfRangeException($"Invalid role: {role}");
}
@@ -50,27 +57,159 @@ internal static List GetSystemMessages(ChatHistory chatHisto
///
/// Creates the list of user and assistant messages for the Converse Request from the Chat History.
+ /// Handles FunctionCallContent (tool use) and FunctionResultContent (tool result) items,
+ /// and merges consecutive messages with the same Bedrock role into a single message.
+ /// This is required because Bedrock expects all tool_use blocks from one assistant turn
+ /// in a single message and all tool_result blocks in a single user message.
///
/// The ChatHistory object to be building the message list from.
/// The list of messages for the converse request.
- /// Thrown if invalid last message in chat history.
+ /// Thrown if the chat history is empty.
internal static List BuildMessageList(ChatHistory chatHistory)
{
- // Check that the text from the latest message in the chat history is not empty.
Verify.NotNullOrEmpty(chatHistory);
- string? text = chatHistory[chatHistory.Count - 1].Content;
- if (string.IsNullOrWhiteSpace(text))
+
+ var messages = new List();
+
+ foreach (var chatMessage in chatHistory)
{
- throw new ArgumentException("Last message in chat history was null or whitespace.");
+ if (chatMessage.Role == AuthorRole.System)
+ {
+ continue;
+ }
+
+ var bedrockRole = MapAuthorRoleToConversationRole(chatMessage.Role);
+ var contentBlocks = BuildContentBlocks(chatMessage);
+
+ if (contentBlocks.Count == 0)
+ {
+ continue;
+ }
+
+ // Merge with the previous message if it has the same role.
+ // This handles consecutive assistant tool-call messages and consecutive tool-result messages.
+ if (messages.Count > 0 && messages[messages.Count - 1].Role == bedrockRole)
+ {
+ messages[messages.Count - 1].Content.AddRange(contentBlocks);
+ }
+ else
+ {
+ messages.Add(new Message
+ {
+ Role = bedrockRole,
+ Content = contentBlocks
+ });
+ }
}
- return chatHistory
- .Where(m => m.Role != AuthorRole.System)
- .Select(m => new Message
+
+ return messages;
+ }
+
+ ///
+ /// Builds the list of Bedrock ContentBlock objects from a ChatMessageContent,
+ /// handling text, FunctionCallContent (ToolUse), and FunctionResultContent (ToolResult).
+ ///
+ /// The chat message to convert.
+ /// A list of ContentBlock objects.
+ private static List BuildContentBlocks(ChatMessageContent chatMessage)
+ {
+ var contentBlocks = new List();
+
+ foreach (var item in chatMessage.Items)
+ {
+ switch (item)
{
- Role = MapAuthorRoleToConversationRole(m.Role),
- Content = [new() { Text = m.Content }]
- })
- .ToList();
+ case FunctionCallContent functionCall:
+ contentBlocks.Add(new ContentBlock
+ {
+ ToolUse = new ToolUseBlock
+ {
+ ToolUseId = functionCall.Id,
+ Name = functionCall.PluginName is not null
+ ? $"{functionCall.PluginName}-{functionCall.FunctionName}"
+ : functionCall.FunctionName,
+ Input = ConvertArgumentsToDocument(functionCall.Arguments)
+ }
+ });
+ break;
+
+ case FunctionResultContent functionResult:
+ contentBlocks.Add(new ContentBlock
+ {
+ ToolResult = new ToolResultBlock
+ {
+ ToolUseId = functionResult.CallId,
+ Content = [new ToolResultContentBlock { Text = functionResult.Result?.ToString() ?? string.Empty }]
+ }
+ });
+ break;
+
+ case TextContent textContent:
+ if (!string.IsNullOrEmpty(textContent.Text))
+ {
+ contentBlocks.Add(new ContentBlock { Text = textContent.Text });
+ }
+ break;
+
+ default:
+ // For other content types, fall back to using ToString.
+ var text = item.ToString();
+ if (!string.IsNullOrEmpty(text))
+ {
+ contentBlocks.Add(new ContentBlock { Text = text });
+ }
+ break;
+ }
+ }
+
+ // If no items were processed but there's text content on the message itself, use that.
+ if (contentBlocks.Count == 0 && !string.IsNullOrEmpty(chatMessage.Content))
+ {
+ contentBlocks.Add(new ContentBlock { Text = chatMessage.Content });
+ }
+
+ return contentBlocks;
+ }
+
+ ///
+ /// Converts KernelArguments to an Amazon.Runtime.Documents.Document
+ /// for use as ToolUseBlock input.
+ ///
+ /// The arguments to convert.
+ /// A Document representing the arguments as a JSON-like structure.
+ private static Document ConvertArgumentsToDocument(KernelArguments? arguments)
+ {
+ if (arguments == null || arguments.Count == 0)
+ {
+ return new Document(new Dictionary());
+ }
+
+ var dict = new Dictionary();
+ foreach (var kvp in arguments)
+ {
+ dict[kvp.Key] = ConvertValueToDocument(kvp.Value);
+ }
+
+ return new Document(dict);
+ }
+
+ ///
+ /// Converts a single value to a Document, handling common types.
+ ///
+ private static Document ConvertValueToDocument(object? value)
+ {
+ return value switch
+ {
+ null => new Document(),
+ string s => new Document(s),
+ bool b => new Document(b),
+ int i => new Document(i),
+ long l => new Document(l),
+ float f => new Document(f),
+ double d => new Document(d),
+ decimal dec => new Document((double)dec),
+ _ => new Document(value.ToString() ?? string.Empty)
+ };
}
///
diff --git a/dotnet/src/Connectors/Connectors.Amazon/Bedrock/Core/Clients/BedrockChatCompletionClient.cs b/dotnet/src/Connectors/Connectors.Amazon/Bedrock/Core/Clients/BedrockChatCompletionClient.cs
index fde1f2c0af3d..b8eb8d8047bd 100644
--- a/dotnet/src/Connectors/Connectors.Amazon/Bedrock/Core/Clients/BedrockChatCompletionClient.cs
+++ b/dotnet/src/Connectors/Connectors.Amazon/Bedrock/Core/Clients/BedrockChatCompletionClient.cs
@@ -144,11 +144,97 @@ private static ChatMessageContentItemCollection CreateChatMessageContentItemColl
var itemCollection = new ChatMessageContentItemCollection();
foreach (var contentBlock in contentBlocks)
{
- itemCollection.Add(new TextContent(contentBlock.Text));
+ if (contentBlock.ToolUse != null)
+ {
+ var toolUse = contentBlock.ToolUse;
+ KernelArguments? arguments = null;
+ if (toolUse.Input.IsDictionary())
+ {
+ arguments = ParseDocumentToArguments(toolUse.Input);
+ }
+
+ // Parse plugin name and function name from the tool name.
+ // The Bedrock connector uses "PluginName-FunctionName" format.
+ string? pluginName = null;
+ string functionName = toolUse.Name ?? string.Empty;
+ var separatorIndex = functionName.IndexOf('-');
+ if (separatorIndex > 0)
+ {
+ pluginName = functionName.Substring(0, separatorIndex);
+ functionName = functionName.Substring(separatorIndex + 1);
+ }
+
+ itemCollection.Add(new FunctionCallContent(
+ functionName: functionName,
+ pluginName: pluginName,
+ id: toolUse.ToolUseId,
+ arguments: arguments));
+ }
+ else if (!string.IsNullOrEmpty(contentBlock.Text))
+ {
+ itemCollection.Add(new TextContent(contentBlock.Text));
+ }
}
return itemCollection;
}
+ ///
+ /// Parses an Amazon.Runtime.Documents.Document to KernelArguments.
+ ///
+ private static KernelArguments? ParseDocumentToArguments(global::Amazon.Runtime.Documents.Document document)
+ {
+ if (!document.IsDictionary())
+ {
+ return null;
+ }
+
+ var arguments = new KernelArguments();
+ foreach (var kvp in document.AsDictionary())
+ {
+ arguments[kvp.Key] = DocumentToObject(kvp.Value);
+ }
+
+ return arguments;
+ }
+
+ ///
+ /// Converts a Document to a plain object for use in KernelArguments.
+ ///
+ private static object? DocumentToObject(global::Amazon.Runtime.Documents.Document doc)
+ {
+ if (doc.IsNull())
+ {
+ return null;
+ }
+
+ if (doc.IsString())
+ {
+ return doc.AsString();
+ }
+
+ if (doc.IsBool())
+ {
+ return doc.AsBool();
+ }
+
+ if (doc.IsInt())
+ {
+ return doc.AsInt();
+ }
+
+ if (doc.IsLong())
+ {
+ return doc.AsLong();
+ }
+
+ if (doc.IsDouble())
+ {
+ return doc.AsDouble();
+ }
+
+ return doc.ToString();
+ }
+
internal async IAsyncEnumerable StreamChatMessageAsync(
ChatHistory chatHistory,
PromptExecutionSettings? executionSettings = null,