From ea795b479883472c554b9eb9ebdbc9c213a48251 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Thu, 6 Feb 2025 19:11:05 +0000 Subject: [PATCH 01/27] Add Bedrock Agent to .Net SDK --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/microsoft/semantic-kernel?shareId=XXXX-XXXX-XXXX-XXXX). --- .../src/Agents/Bedrock/Agents.Bedrock.csproj | 47 +++++++++++++++++ dotnet/src/Agents/Bedrock/BedrockAgent.cs | 51 ++++++++++++++++++ .../src/Agents/Bedrock/BedrockAgentChannel.cs | 51 ++++++++++++++++++ .../UnitTests/Bedrock/BedrockAgentTests.cs | 52 +++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj create mode 100644 dotnet/src/Agents/Bedrock/BedrockAgent.cs create mode 100644 dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs create mode 100644 dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs diff --git a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj new file mode 100644 index 000000000000..c473e37f6a88 --- /dev/null +++ b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj @@ -0,0 +1,47 @@ + + + + + Microsoft.SemanticKernel.Agents.Bedrock + Microsoft.SemanticKernel.Agents.Bedrock + net8.0;netstandard2.0 + false + alpha + + + + + + + Semantic Kernel Agents - Bedrock + Defines a concrete Agent based on the Bedrock API. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs new file mode 100644 index 000000000000..d629b94f52b5 --- /dev/null +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel.Agents; + +namespace Microsoft.SemanticKernel.Agents.Bedrock +{ + public class BedrockAgent : KernelAgent + { + public BedrockAgent() + { + // Initialize the BedrockAgent + } + + protected override IEnumerable GetChannelKeys() + { + // Return the channel keys for the BedrockAgent + yield return typeof(BedrockAgentChannel).FullName!; + } + + protected override Task CreateChannelAsync(CancellationToken cancellationToken) + { + // Create and return a new BedrockAgentChannel + return Task.FromResult(new BedrockAgentChannel()); + } + + protected override Task RestoreChannelAsync(string channelState, CancellationToken cancellationToken) + { + // Restore and return a BedrockAgentChannel from the given state + return Task.FromResult(new BedrockAgentChannel()); + } + + public Task CreateAgentAsync(CancellationToken cancellationToken) + { + // Implement the logic to create the Bedrock agent + return Task.CompletedTask; + } + + public Task RetrieveAgentAsync(CancellationToken cancellationToken) + { + // Implement the logic to retrieve the Bedrock agent + return Task.CompletedTask; + } + + public Task InvokeAgentAsync(CancellationToken cancellationToken) + { + // Implement the logic to invoke the Bedrock agent + return Task.CompletedTask; + } + } +} diff --git a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs new file mode 100644 index 000000000000..8895ab3576cc --- /dev/null +++ b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel.Agents; + +namespace Microsoft.SemanticKernel.Agents.Bedrock +{ + public class BedrockAgentChannel : AgentChannel + { + public BedrockAgentChannel() + { + // Initialize the BedrockAgentChannel + } + + protected override Task ReceiveAsync(IEnumerable history, CancellationToken cancellationToken) + { + // Implement the logic to receive messages from the Bedrock service + return Task.CompletedTask; + } + + protected override IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync(BedrockAgent agent, CancellationToken cancellationToken) + { + // Implement the logic to invoke the Bedrock service + return AsyncEnumerable.Empty<(bool IsVisible, ChatMessageContent Message)>(); + } + + protected override IAsyncEnumerable InvokeStreamingAsync(BedrockAgent agent, IList messages, CancellationToken cancellationToken) + { + // Implement the logic to invoke the Bedrock service with streaming results + return AsyncEnumerable.Empty(); + } + + protected override IAsyncEnumerable GetHistoryAsync(CancellationToken cancellationToken) + { + // Implement the logic to retrieve the message history from the Bedrock service + return AsyncEnumerable.Empty(); + } + + protected override Task ResetAsync(CancellationToken cancellationToken) + { + // Implement the logic to reset the BedrockAgentChannel + return Task.CompletedTask; + } + + protected override string Serialize() + { + // Implement the logic to serialize the BedrockAgentChannel state + return string.Empty; + } + } +} diff --git a/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs b/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs new file mode 100644 index 000000000000..a1260056d629 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs @@ -0,0 +1,52 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel.Agents.Bedrock; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.Bedrock +{ + public class BedrockAgentTests + { + [Fact] + public async Task CreateAgentAsync_ShouldCreateAgent() + { + // Arrange + var agent = new BedrockAgent(); + var cancellationToken = CancellationToken.None; + + // Act + await agent.CreateAgentAsync(cancellationToken); + + // Assert + // Add assertions to verify the agent creation logic + } + + [Fact] + public async Task RetrieveAgentAsync_ShouldRetrieveAgent() + { + // Arrange + var agent = new BedrockAgent(); + var cancellationToken = CancellationToken.None; + + // Act + await agent.RetrieveAgentAsync(cancellationToken); + + // Assert + // Add assertions to verify the agent retrieval logic + } + + [Fact] + public async Task InvokeAgentAsync_ShouldInvokeAgent() + { + // Arrange + var agent = new BedrockAgent(); + var cancellationToken = CancellationToken.None; + + // Act + await agent.InvokeAgentAsync(cancellationToken); + + // Assert + // Add assertions to verify the agent invocation logic + } + } +} From e311fe6016abd2ed152c9fb29604898ea85d71d6 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Sun, 9 Feb 2025 14:55:05 -0800 Subject: [PATCH 02/27] Simple invoke is working --- dotnet/Directory.Packages.props | 5 +- dotnet/SK-dotnet.sln | 9 + .../BedrockAgent/Step01_BedrockAgent.cs | 61 +++++ .../GettingStartedWithAgents.csproj | 3 +- .../src/Agents/Bedrock/Agents.Bedrock.csproj | 16 +- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 239 +++++++++++++++--- .../src/Agents/Bedrock/BedrockAgentChannel.cs | 86 ++++--- .../Extensions/AgentInvokeExtensions.cs | 139 ++++++++++ .../Extensions/AgentStatusExtensions.cs | 45 ++++ .../Agents/Bedrock/Properties/AssemblyInfo.cs | 6 + .../Agents/UnitTests/Agents.UnitTests.csproj | 3 +- .../UnitTests/Bedrock/BedrockAgentTests.cs | 40 --- .../InternalUtilities/TestConfiguration.cs | 8 +- 13 files changed, 533 insertions(+), 127 deletions(-) create mode 100644 dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs create mode 100644 dotnet/src/Agents/Bedrock/Extensions/AgentInvokeExtensions.cs create mode 100644 dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs create mode 100644 dotnet/src/Agents/Bedrock/Properties/AssemblyInfo.cs diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index e7d6790c1ed7..c4067c3887cf 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -5,6 +5,8 @@ true + + @@ -59,6 +61,7 @@ + @@ -170,4 +173,4 @@ - + \ No newline at end of file diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 21f3cbc1da67..34d4ad104c10 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -445,6 +445,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugins.AI.UnitTests", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connectors.Postgres.UnitTests", "src\Connectors\Connectors.Postgres.UnitTests\Connectors.Postgres.UnitTests.csproj", "{2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agents.Bedrock", "src\Agents\Bedrock\Agents.Bedrock.csproj", "{8C658E1E-83C8-4127-B8BF-27A638A45DDD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1196,6 +1198,12 @@ Global {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Publish|Any CPU.Build.0 = Debug|Any CPU {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Release|Any CPU.Build.0 = Release|Any CPU + {8C658E1E-83C8-4127-B8BF-27A638A45DDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C658E1E-83C8-4127-B8BF-27A638A45DDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C658E1E-83C8-4127-B8BF-27A638A45DDD}.Publish|Any CPU.ActiveCfg = Publish|Any CPU + {8C658E1E-83C8-4127-B8BF-27A638A45DDD}.Publish|Any CPU.Build.0 = Publish|Any CPU + {8C658E1E-83C8-4127-B8BF-27A638A45DDD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C658E1E-83C8-4127-B8BF-27A638A45DDD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1360,6 +1368,7 @@ Global {0C64EC81-8116-4388-87AD-BA14D4B59974} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} {03ACF9DD-00C9-4F2B-80F1-537E2151AF5F} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC} = {5A7028A7-4DDF-4E4F-84A9-37CE8F8D7E89} + {8C658E1E-83C8-4127-B8BF-27A638A45DDD} = {6823CD5E-2ABE-41EB-B865-F86EC13F0CF9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs new file mode 100644 index 000000000000..66e5db2b78d4 --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Amazon.BedrockAgent.Model; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.Bedrock; + +namespace GettingStarted.BedrockAgents; + +/// +/// This example demonstrates similarity between using +/// and (see: Step 2). +/// +public class Step01_BedrockAgent(ITestOutputHelper output) : BaseAgentsTest(output) +{ + private const string AgentName = "Semantic-Kernel-Test-Agent"; + private const string AgentDescription = "A helpful assistant who helps users find information."; + private const string AgentInstruction = "You're a helpful assistant who helps users find information."; + private const string UserQuery = "Why is the sky blue?"; + + [Fact] + public async Task UseNewAgentAsync() + { + // Define the agent + CreateAgentRequest createAgentRequest = new() + { + AgentName = AgentName, + Description = AgentDescription, + Instruction = AgentInstruction, + AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, + FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, + }; + + var bedrock_agent = await BedrockAgent.CreateAsync(createAgentRequest); + + // Respond to user input + try + { + var responses = bedrock_agent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + await foreach (var response in responses) + { + output.WriteLine(response.Content); + } + } + finally + { + await bedrock_agent.DeleteAsync(CancellationToken.None); + } + } + + [Fact] + public async Task UseTemplateForAssistantAgentAsync() + { + // Define the agent + + // Instructions, Name and Description properties defined via the config. + + // Create a thread for the agent conversation. + + // Local function to invoke agent and display the response. + } +} diff --git a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj index 8efaac336d6b..ffc4734e10d6 100644 --- a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj +++ b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj @@ -45,6 +45,7 @@ + @@ -66,4 +67,4 @@ - + \ No newline at end of file diff --git a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj index c473e37f6a88..aec2e8d9e037 100644 --- a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj +++ b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj @@ -1,10 +1,11 @@ - + Microsoft.SemanticKernel.Agents.Bedrock Microsoft.SemanticKernel.Agents.Bedrock net8.0;netstandard2.0 + $(NoWarn);SKEXP0110 false alpha @@ -14,17 +15,15 @@ Semantic Kernel Agents - Bedrock - Defines a concrete Agent based on the Bedrock API. + Defines a concrete Agent based on the Bedrock Agent Service. - - @@ -34,9 +33,9 @@ - - - + + + @@ -44,4 +43,5 @@ - + + \ No newline at end of file diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index d629b94f52b5..8b76addbb8e0 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -1,51 +1,224 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.SemanticKernel.Agents; +using AmazonBedrockAgent = Amazon.BedrockAgent; +using Amazon.BedrockAgentRuntime; +using AmazonBedrockAgentModel = Amazon.BedrockAgent.Model; +using AmazonBedrockAgentRuntimeModel = Amazon.BedrockAgentRuntime.Model; +using Microsoft.SemanticKernel.Agents.Extensions; +using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; + +namespace Microsoft.SemanticKernel.Agents.Bedrock; -namespace Microsoft.SemanticKernel.Agents.Bedrock +/// +/// Provides a specialized for the Bedrock Agent service. +/// +public class BedrockAgent : KernelAgent { - public class BedrockAgent : KernelAgent + private readonly AmazonBedrockAgent.AmazonBedrockAgentClient _client; + + private readonly AmazonBedrockAgentRuntimeClient _runtimeClient; + + private readonly AmazonBedrockAgentModel.Agent _agentModel; + + // There is a default alias created by Bedrock for the working draft version of the agent. + // https://docs.aws.amazon.com/bedrock/latest/userguide/agents-deploy.html + private const string WORKING_DRAFT_AGENT_ALIAS = "TSTALIASID"; + + /// + /// Initializes a new instance of the class. + /// + /// The agent model of an agent that exists on the Bedrock Agent service. + /// A client used to interact with the Bedrock Agent service. + /// A client used to interact with the Bedrock Agent runtime service. + public BedrockAgent( + AmazonBedrockAgentModel.Agent agentModel, + AmazonBedrockAgent.AmazonBedrockAgentClient client, + AmazonBedrockAgentRuntimeClient runtimeClient) { - public BedrockAgent() - { - // Initialize the BedrockAgent - } + this._agentModel = agentModel; + this._client = client; + this._runtimeClient = runtimeClient; - protected override IEnumerable GetChannelKeys() - { - // Return the channel keys for the BedrockAgent - yield return typeof(BedrockAgentChannel).FullName!; - } + this.Id = agentModel.AgentId; + this.Name = agentModel.AgentName; + this.Description = agentModel.Description; + this.Instructions = agentModel.Instruction; + } - protected override Task CreateChannelAsync(CancellationToken cancellationToken) - { - // Create and return a new BedrockAgentChannel - return Task.FromResult(new BedrockAgentChannel()); - } + /// + /// Create a Bedrock agent on the service. + /// + /// The request to create the agent. + /// The client to use. + /// The runtime client to use. + /// A kernel instance for the agent to use. + /// Optional default arguments. + /// The to monitor for cancellation requests. The default is . + /// An instance of the . + public static async Task CreateAsync( + AmazonBedrockAgentModel.CreateAgentRequest request, + AmazonBedrockAgent.AmazonBedrockAgentClient? client = null, + AmazonBedrockAgentRuntimeClient? runtimeClient = null, + Kernel? kernel = null, + KernelArguments? defaultArguments = null, + CancellationToken cancellationToken = default) + { + client ??= new AmazonBedrockAgent.AmazonBedrockAgentClient(); + runtimeClient ??= new AmazonBedrockAgentRuntimeClient(); + var createAgentResponse = await client.CreateAgentAsync(request, cancellationToken).ConfigureAwait(false); - protected override Task RestoreChannelAsync(string channelState, CancellationToken cancellationToken) + BedrockAgent agent = new(createAgentResponse.Agent, client, runtimeClient) { - // Restore and return a BedrockAgentChannel from the given state - return Task.FromResult(new BedrockAgentChannel()); - } + Kernel = kernel ?? new(), + Arguments = defaultArguments, + }; - public Task CreateAgentAsync(CancellationToken cancellationToken) + // The agent will first enter the CREATING status. + // When the agent is created, it will enter the NOT_PREPARED status. + await agent.WaitForAgentStatusAsync(AmazonBedrockAgent.AgentStatus.NOT_PREPARED, cancellationToken).ConfigureAwait(false); + await agent.PrepareAsync(cancellationToken).ConfigureAwait(false); + + return agent; + } + + /// + /// Retrieve a Bedrock agent from the service by id. + /// + /// The id of the agent that exists on the Bedrock Agent service. + /// The client to use. + /// The runtime client to use. + /// A kernel instance for the agent to use. + /// Optional default arguments. + /// The to monitor for cancellation requests. The default is . + /// An instance of the . + public static async Task RetrieveAsync( + string id, + AmazonBedrockAgent.AmazonBedrockAgentClient? client = null, + AmazonBedrockAgentRuntimeClient? runtimeClient = null, + Kernel? kernel = null, + KernelArguments? defaultArguments = null, + CancellationToken cancellationToken = default) + { + client ??= new AmazonBedrockAgent.AmazonBedrockAgentClient(); + runtimeClient ??= new AmazonBedrockAgentRuntimeClient(); + var getAgentResponse = await client.GetAgentAsync(new() { AgentId = id }, cancellationToken).ConfigureAwait(false); + + return new(getAgentResponse.Agent, client, runtimeClient) { - // Implement the logic to create the Bedrock agent - return Task.CompletedTask; - } + Kernel = kernel ?? new(), + Arguments = defaultArguments, + }; + } - public Task RetrieveAgentAsync(CancellationToken cancellationToken) + /// + /// Convenient method to create an unique session id. + /// + public static string CreateSessionId() + { + return Guid.NewGuid().ToString(); + } + + /// + /// Delete the Bedrock agent from the service. + /// + /// The to monitor for cancellation requests. The default is . + public async Task DeleteAsync(CancellationToken cancellationToken) + { + await this._client.DeleteAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); + } + + /// + /// Prepare the Bedrock agent for use. + /// + public async Task PrepareAsync(CancellationToken cancellationToken) + { + await this._client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); + await this.WaitForAgentStatusAsync(AmazonBedrockAgent.AgentStatus.PREPARED, cancellationToken).ConfigureAwait(false); + } + + /// + /// Invoke the Bedrock agent with the given message. + /// + /// The session id. + /// The message to send to the agent. + /// The arguments to use when invoking the agent. + /// The to monitor for cancellation requests. The default is . + /// An of . + public IAsyncEnumerable InvokeAsync( + string sessionId, + string message, + KernelArguments? arguments, + CancellationToken cancellationToken) + { + var invokeAgentRequest = new AmazonBedrockAgentRuntimeModel.InvokeAgentRequest { - // Implement the logic to retrieve the Bedrock agent - return Task.CompletedTask; - } + AgentAliasId = WORKING_DRAFT_AGENT_ALIAS, + AgentId = this.Id, + SessionId = sessionId, + InputText = message, + }; + + return ActivityExtensions.RunWithActivityAsync( + () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), + () => this.InternalInvokeAsync(invokeAgentRequest, arguments, cancellationToken), + cancellationToken); + } - public Task InvokeAgentAsync(CancellationToken cancellationToken) + /// + /// Invoke the Bedrock agent with the given request and streaming response. + /// + /// The session id. + /// The message to send to the agent. /// The arguments to use when invoking the agent. + /// The to monitor for cancellation requests. The default is . + /// An of . + public IAsyncEnumerable InvokeStreamingAsync( + string sessionId, + string message, + KernelArguments? arguments, + CancellationToken cancellationToken) + { + var invokeAgentRequest = new AmazonBedrockAgentRuntimeModel.InvokeAgentRequest { - // Implement the logic to invoke the Bedrock agent - return Task.CompletedTask; - } + AgentAliasId = WORKING_DRAFT_AGENT_ALIAS, + AgentId = this.Id, + SessionId = sessionId, + InputText = message, + StreamingConfigurations = new() + { + StreamFinalResponse = true, + }, + }; + + return ActivityExtensions.RunWithActivityAsync( + () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), + () => this.InternalInvokeStreamingAsync(invokeAgentRequest, arguments, cancellationToken), + cancellationToken); } + + protected override IEnumerable GetChannelKeys() + { + // Return the channel keys for the BedrockAgent + yield return typeof(BedrockAgentChannel).FullName!; + } + + protected override Task CreateChannelAsync(CancellationToken cancellationToken) + { + // Create and return a new BedrockAgentChannel + return Task.FromResult(new BedrockAgentChannel()); + } + + protected override Task RestoreChannelAsync(string channelState, CancellationToken cancellationToken) + { + // Restore and return a BedrockAgentChannel from the given state + return Task.FromResult(new BedrockAgentChannel()); + } + + internal AmazonBedrockAgent.AmazonBedrockAgentClient GetClient() => this._client; + internal AmazonBedrockAgentRuntimeClient GetRuntimeClient() => this._runtimeClient; + internal AmazonBedrockAgentModel.Agent GetAgentModel() => this._agentModel; } diff --git a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs index 8895ab3576cc..83fbf2b22b6a 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs @@ -1,51 +1,53 @@ +// Copyright (c) Microsoft. All rights reserved. + using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.SemanticKernel.Agents; -namespace Microsoft.SemanticKernel.Agents.Bedrock +namespace Microsoft.SemanticKernel.Agents.Bedrock; + +public class BedrockAgentChannel : AgentChannel { - public class BedrockAgentChannel : AgentChannel + public BedrockAgentChannel() + { + // Initialize the BedrockAgentChannel + } + + protected override Task ReceiveAsync(IEnumerable history, CancellationToken cancellationToken) + { + // Implement the logic to receive messages from the Bedrock service + return Task.CompletedTask; + } + + protected override IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync(BedrockAgent agent, CancellationToken cancellationToken) + { + // Implement the logic to invoke the Bedrock service + return AsyncEnumerable.Empty<(bool IsVisible, ChatMessageContent Message)>(); + } + + protected override IAsyncEnumerable InvokeStreamingAsync(BedrockAgent agent, IList messages, CancellationToken cancellationToken) + { + // Implement the logic to invoke the Bedrock service with streaming results + return AsyncEnumerable.Empty(); + } + + protected override IAsyncEnumerable GetHistoryAsync(CancellationToken cancellationToken) + { + // Implement the logic to retrieve the message history from the Bedrock service + return AsyncEnumerable.Empty(); + } + + protected override Task ResetAsync(CancellationToken cancellationToken) + { + // Implement the logic to reset the BedrockAgentChannel + return Task.CompletedTask; + } + + protected override string Serialize() { - public BedrockAgentChannel() - { - // Initialize the BedrockAgentChannel - } - - protected override Task ReceiveAsync(IEnumerable history, CancellationToken cancellationToken) - { - // Implement the logic to receive messages from the Bedrock service - return Task.CompletedTask; - } - - protected override IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync(BedrockAgent agent, CancellationToken cancellationToken) - { - // Implement the logic to invoke the Bedrock service - return AsyncEnumerable.Empty<(bool IsVisible, ChatMessageContent Message)>(); - } - - protected override IAsyncEnumerable InvokeStreamingAsync(BedrockAgent agent, IList messages, CancellationToken cancellationToken) - { - // Implement the logic to invoke the Bedrock service with streaming results - return AsyncEnumerable.Empty(); - } - - protected override IAsyncEnumerable GetHistoryAsync(CancellationToken cancellationToken) - { - // Implement the logic to retrieve the message history from the Bedrock service - return AsyncEnumerable.Empty(); - } - - protected override Task ResetAsync(CancellationToken cancellationToken) - { - // Implement the logic to reset the BedrockAgentChannel - return Task.CompletedTask; - } - - protected override string Serialize() - { - // Implement the logic to serialize the BedrockAgentChannel state - return string.Empty; - } + // Implement the logic to serialize the BedrockAgentChannel state + return string.Empty; } } diff --git a/dotnet/src/Agents/Bedrock/Extensions/AgentInvokeExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/AgentInvokeExtensions.cs new file mode 100644 index 000000000000..75d9567df7a3 --- /dev/null +++ b/dotnet/src/Agents/Bedrock/Extensions/AgentInvokeExtensions.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Amazon.BedrockAgentRuntime.Model; +using Amazon.Runtime.EventStreams.Internal; +using Microsoft.SemanticKernel.Agents.Extensions; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; + +/// +/// Extensions associated with the status of a . +/// +internal static class AgentInvokeExtensions +{ + public static async IAsyncEnumerable InternalInvokeAsync( + this BedrockAgent agent, + InvokeAgentRequest invokeAgentRequest, + KernelArguments? arguments, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); + + if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new HttpOperationException($"Failed to invoke agent. Status code: {invokeAgentResponse.HttpStatusCode}"); + } + + await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) + { + var chatMessageContent = + HandleChunkEvent(agent, responseEvent) ?? + HandleFilesEvent(agent, responseEvent) ?? + HandleReturnControlEvent(agent, responseEvent) ?? + HandleTraceEvent(agent, responseEvent) ?? + throw new KernelException($"Failed to handle Bedrock Agent stream event: {responseEvent}"); + yield return chatMessageContent; + } + } + + public static async IAsyncEnumerable InternalInvokeStreamingAsync( + this BedrockAgent agent, + InvokeAgentRequest invokeAgentRequest, + KernelArguments? arguments, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); + + if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new HttpOperationException($"Failed to invoke agent. Status code: {invokeAgentResponse.HttpStatusCode}"); + } + + await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) + { + var chatMessageContent = + HandleChunkEvent(agent, responseEvent) ?? + HandleFilesEvent(agent, responseEvent) ?? + HandleReturnControlEvent(agent, responseEvent) ?? + HandleTraceEvent(agent, responseEvent) ?? + throw new KernelException($"Failed to handle Bedrock Agent stream event: {responseEvent}"); + yield return new(chatMessageContent.Role, chatMessageContent.Content) + { + AuthorName = chatMessageContent.AuthorName, + ModelId = chatMessageContent.ModelId, + InnerContent = chatMessageContent.InnerContent, + }; + } + } + + private static ChatMessageContent? HandleChunkEvent( + BedrockAgent agent, + IEventStreamEvent responseEvent) + { + return responseEvent is not PayloadPart payload + ? null + : new ChatMessageContent() + { + Role = AuthorRole.Assistant, + AuthorName = agent.GetDisplayName(), + Content = Encoding.UTF8.GetString(payload.Bytes.ToArray()), + ModelId = agent.GetAgentModel().FoundationModel, + InnerContent = responseEvent, + }; + } + + private static ChatMessageContent? HandleFilesEvent( + BedrockAgent agent, + IEventStreamEvent responseEvent) + { + return responseEvent is not FilePart files + ? null + : new ChatMessageContent() + { + Role = AuthorRole.Assistant, + AuthorName = agent.GetDisplayName(), + Content = "Files received", + ModelId = agent.GetAgentModel().FoundationModel, + InnerContent = responseEvent, + }; + } + + private static ChatMessageContent? HandleReturnControlEvent( + BedrockAgent agent, + IEventStreamEvent responseEvent) + { + return responseEvent is not ReturnControlPayload returnControl + ? null + : new ChatMessageContent() + { + Role = AuthorRole.Assistant, + AuthorName = agent.GetDisplayName(), + Content = "Return control received", + ModelId = agent.GetAgentModel().FoundationModel, + InnerContent = responseEvent, + }; + } + + private static ChatMessageContent? HandleTraceEvent( + BedrockAgent agent, + IEventStreamEvent responseEvent) + { + return responseEvent is not TracePart trace + ? null + : new ChatMessageContent() + { + Role = AuthorRole.Assistant, + AuthorName = agent.GetDisplayName(), + Content = "Trace received", + ModelId = agent.GetAgentModel().FoundationModel, + InnerContent = responseEvent, + }; + } +} \ No newline at end of file diff --git a/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs new file mode 100644 index 000000000000..122cf44a5844 --- /dev/null +++ b/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Threading; +using System.Threading.Tasks; +using AmazonBedrockAgent = Amazon.BedrockAgent; + + +namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; + +/// +/// Extensions associated with the status of a . +/// +internal static class AgentStatusExtensions +{ + /// + /// Wait for the agent to reach the specified status. + /// + /// The to monitor. + /// The status to wait for. + /// The to monitor for cancellation requests. + /// The interval in seconds to wait between attempts. The default is 2 seconds. + /// The maximum number of attempts to make. The default is 5 attempts. + public static async Task WaitForAgentStatusAsync( + this BedrockAgent agent, + AmazonBedrockAgent.AgentStatus status, + CancellationToken cancellationToken, + int interval = 2, + int maxAttempts = 5) + { + for (var i = 0; i < maxAttempts; i++) + { + var getAgentResponse = await agent.GetClient().GetAgentAsync(new() { AgentId = agent.Id }, cancellationToken).ConfigureAwait(false); + + if (getAgentResponse.Agent.AgentStatus == status) + { + return; + } + + await Task.Delay(interval * 1000, cancellationToken).ConfigureAwait(false); + } + + throw new TimeoutException($"Agent did not reach status {status} within the specified time."); + } +} \ No newline at end of file diff --git a/dotnet/src/Agents/Bedrock/Properties/AssemblyInfo.cs b/dotnet/src/Agents/Bedrock/Properties/AssemblyInfo.cs new file mode 100644 index 000000000000..c4c5cac19736 --- /dev/null +++ b/dotnet/src/Agents/Bedrock/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +// This assembly is currently experimental. +[assembly: Experimental("SKEXP0110")] diff --git a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj index 32d31f65c776..e28d01551ba7 100644 --- a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj +++ b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj @@ -34,6 +34,7 @@ + @@ -45,4 +46,4 @@ - + \ No newline at end of file diff --git a/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs b/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs index a1260056d629..4041a765994b 100644 --- a/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs @@ -7,46 +7,6 @@ namespace SemanticKernel.Agents.UnitTests.Bedrock { public class BedrockAgentTests { - [Fact] - public async Task CreateAgentAsync_ShouldCreateAgent() - { - // Arrange - var agent = new BedrockAgent(); - var cancellationToken = CancellationToken.None; - // Act - await agent.CreateAgentAsync(cancellationToken); - - // Assert - // Add assertions to verify the agent creation logic - } - - [Fact] - public async Task RetrieveAgentAsync_ShouldRetrieveAgent() - { - // Arrange - var agent = new BedrockAgent(); - var cancellationToken = CancellationToken.None; - - // Act - await agent.RetrieveAgentAsync(cancellationToken); - - // Assert - // Add assertions to verify the agent retrieval logic - } - - [Fact] - public async Task InvokeAgentAsync_ShouldInvokeAgent() - { - // Arrange - var agent = new BedrockAgent(); - var cancellationToken = CancellationToken.None; - - // Act - await agent.InvokeAgentAsync(cancellationToken); - - // Assert - // Add assertions to verify the agent invocation logic - } } } diff --git a/dotnet/src/InternalUtilities/samples/InternalUtilities/TestConfiguration.cs b/dotnet/src/InternalUtilities/samples/InternalUtilities/TestConfiguration.cs index 18809cac87f1..e45f52216a14 100644 --- a/dotnet/src/InternalUtilities/samples/InternalUtilities/TestConfiguration.cs +++ b/dotnet/src/InternalUtilities/samples/InternalUtilities/TestConfiguration.cs @@ -49,8 +49,8 @@ public static void Initialize(IConfigurationRoot configRoot) public static VertexAIConfig VertexAI => LoadSection(); public static AzureCosmosDbMongoDbConfig AzureCosmosDbMongoDb => LoadSection(); public static ApplicationInsightsConfig ApplicationInsights => LoadSection(); - public static CrewAIConfig CrewAI => LoadSection(); + public static BedrockAgentConfig BedrockAgent => LoadSection(); private static T LoadSection([CallerMemberName] string? caller = null) { @@ -323,4 +323,10 @@ public class CrewAIConfig public string Endpoint { get; set; } public string AuthToken { get; set; } } + + public class BedrockAgentConfig + { + public string AgentResourceRoleArn { get; set; } + public string FoundationModel { get; set; } + } } From 441207b4da440e5417453b24800fcac452ce869e Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Mon, 10 Feb 2025 17:39:26 -0800 Subject: [PATCH 03/27] Kernel function WIP --- .../BedrockAgent/Step01_BedrockAgent.cs | 36 ++- .../Step02_BedrockAgent_CodeInterpreter.cs | 84 +++++++ .../Step03_BedrockAgent_Functions.cs | 68 ++++++ .../src/Agents/Bedrock/Agents.Bedrock.csproj | 1 + dotnet/src/Agents/Bedrock/BedrockAgent.cs | 53 ++++ .../Extensions/AgentInvokeExtensions.cs | 139 ----------- .../BedrockAgentInvokeExtensions.cs | 231 ++++++++++++++++++ ...ons.cs => BedrockAgentStatusExtensions.cs} | 2 +- .../BedrockFunctionSchemaExtensions.cs | 92 +++++++ .../Contents/BinaryContent.cs | 27 ++ 10 files changed, 589 insertions(+), 144 deletions(-) create mode 100644 dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs create mode 100644 dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs delete mode 100644 dotnet/src/Agents/Bedrock/Extensions/AgentInvokeExtensions.cs create mode 100644 dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs rename dotnet/src/Agents/Bedrock/Extensions/{AgentStatusExtensions.cs => BedrockAgentStatusExtensions.cs} (97%) create mode 100644 dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs index 66e5db2b78d4..ec880661104c 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs @@ -1,14 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. using Amazon.BedrockAgent.Model; -using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Bedrock; namespace GettingStarted.BedrockAgents; /// -/// This example demonstrates similarity between using -/// and (see: Step 2). +/// This example demonstrates how to interact with a in the most basic way. /// public class Step01_BedrockAgent(ITestOutputHelper output) : BaseAgentsTest(output) { @@ -38,7 +36,37 @@ public async Task UseNewAgentAsync() var responses = bedrock_agent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); await foreach (var response in responses) { - output.WriteLine(response.Content); + this.Output.WriteLine(response.Content); + } + } + finally + { + await bedrock_agent.DeleteAsync(CancellationToken.None); + } + } + + [Fact] + public async Task UseNewAgentStreamingAsync() + { + // Define the agent + CreateAgentRequest createAgentRequest = new() + { + AgentName = AgentName, + Description = AgentDescription, + Instruction = AgentInstruction, + AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, + FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, + }; + + var bedrock_agent = await BedrockAgent.CreateAsync(createAgentRequest); + + // Respond to user input + try + { + var streamingResponses = bedrock_agent.InvokeStreamingAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + await foreach (var response in streamingResponses) + { + this.Output.WriteLine(response.Content); } } finally diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs new file mode 100644 index 000000000000..35496f8495f9 --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Reflection; +using Amazon.BedrockAgent.Model; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents.Bedrock; + +namespace GettingStarted.BedrockAgents; + +/// +/// This example demonstrates how to interact with a with code interpreter enabled. +/// +public class Step02_BedrockAgent_CodeInterpreter(ITestOutputHelper output) : BaseAgentsTest(output) +{ + private const string AgentName = "Semantic-Kernel-Test-Agent"; + private const string AgentDescription = "A helpful assistant who helps users find information."; + private const string AgentInstruction = "You're a helpful assistant who helps users find information."; + private const string UserQuery = @"Create a bar chart for the following data: +Panda 5 +Tiger 8 +Lion 3 +Monkey 6 +Dolphin 2"; + + [Fact] + public async Task UseAgentWithCodeInterpreterAsync() + { + // Define the agent + CreateAgentRequest createAgentRequest = new() + { + AgentName = AgentName, + Description = AgentDescription, + Instruction = AgentInstruction, + AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, + FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, + }; + + var bedrock_agent = await BedrockAgent.CreateAsync(createAgentRequest, enableCodeInterpreter: true); + + // Respond to user input + try + { + BinaryContent? binaryContent = null; + var responses = bedrock_agent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + await foreach (var response in responses) + { + if (response.Content != null) + { + this.Output.WriteLine(response.Content); + } + if (binaryContent == null && response.Items.Count > 0 && response.Items[0] is BinaryContent binary) + { + binaryContent = binary; + } + } + + if (binaryContent == null) + { + throw new InvalidOperationException("No file found in the response."); + } + + // Save the file to the same directory as the test assembly + var filePath = Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, + binaryContent.Metadata!["Name"]!.ToString()!); + this.Output.WriteLine($"Saving file to {filePath}"); + binaryContent.WriteToFile(filePath); + + // Expected output: + // Here is the bar chart for the given data: + // [A bar chart showing the following data: + // Panda 5 + // Tiger 8 + // Lion 3 + // Monkey 6 + // Dolphin 2] + // Saving file to ... + } + finally + { + await bedrock_agent.DeleteAsync(CancellationToken.None); + } + } +} diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs new file mode 100644 index 000000000000..223a1966e1a2 --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.ComponentModel; +using Amazon.BedrockAgent.Model; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents.Bedrock; + +namespace GettingStarted.BedrockAgents; + +/// +/// This example demonstrates how to interact with a with kernel functions. +/// +public class Step02_BedrockAgent_Functions(ITestOutputHelper output) : BaseAgentsTest(output) +{ + private const string AgentName = "Semantic-Kernel-Test-Agent"; + private const string AgentDescription = "A helpful assistant who helps users find information."; + private const string AgentInstruction = "You're a helpful assistant who helps users find information."; + private const string UserQuery = "What is the weather in Seattle?"; + + [Fact] + public async Task UseAgentWithFunctionsAsync() + { + // Define the agent + CreateAgentRequest createAgentRequest = new() + { + AgentName = AgentName, + Description = AgentDescription, + Instruction = AgentInstruction, + AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, + FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, + }; + + Kernel kernel = new(); + kernel.Plugins.Add(KernelPluginFactory.CreateFromType()); + + var bedrock_agent = await BedrockAgent.CreateAsync( + createAgentRequest, + kernel: kernel, + enableKernelFunctions: true); + + // Respond to user input + try + { + var responses = bedrock_agent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + await foreach (var response in responses) + { + if (response.Content != null) + { + this.Output.WriteLine(response.Content); + } + } + } + finally + { + await bedrock_agent.DeleteAsync(CancellationToken.None); + } + } + + private sealed class WeatherPlugin + { + [KernelFunction, Description("Provides realtime weather information.")] + // [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")] + public string Current([Description("The location to get the weather for.")] string location) + { + return $"The current weather in {location} is 72 degrees."; + } + } +} diff --git a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj index aec2e8d9e037..79d142eb072c 100644 --- a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj +++ b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj @@ -24,6 +24,7 @@ + diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index 8b76addbb8e0..099540a2c976 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -54,6 +54,8 @@ public BedrockAgent( /// Create a Bedrock agent on the service. /// /// The request to create the agent. + /// Whether to enable the code interpreter for the agent. + /// Whether to enable kernel functions for the agent. /// The client to use. /// The runtime client to use. /// A kernel instance for the agent to use. @@ -62,6 +64,8 @@ public BedrockAgent( /// An instance of the . public static async Task CreateAsync( AmazonBedrockAgentModel.CreateAgentRequest request, + bool enableCodeInterpreter = false, + bool enableKernelFunctions = false, AmazonBedrockAgent.AmazonBedrockAgentClient? client = null, AmazonBedrockAgentRuntimeClient? runtimeClient = null, Kernel? kernel = null, @@ -81,6 +85,17 @@ public static async Task CreateAsync( // The agent will first enter the CREATING status. // When the agent is created, it will enter the NOT_PREPARED status. await agent.WaitForAgentStatusAsync(AmazonBedrockAgent.AgentStatus.NOT_PREPARED, cancellationToken).ConfigureAwait(false); + + if (enableCodeInterpreter) + { + await agent.CreateCodeInterpreterActionGroupAsync(cancellationToken).ConfigureAwait(false); + } + if (enableKernelFunctions) + { + await agent.CreateKernelFunctionActionGroupAsync(cancellationToken).ConfigureAwait(false); + } + + // Need to prepare the agent before it can be invoked. await agent.PrepareAsync(cancellationToken).ConfigureAwait(false); return agent; @@ -200,6 +215,44 @@ public IAsyncEnumerable InvokeStreamingAsync( cancellationToken); } + /// + /// Create a code interpreter action group for the agent. + /// + private async Task CreateCodeInterpreterActionGroupAsync(CancellationToken cancellationToken) + { + var createAgentActionGroupRequest = new AmazonBedrockAgentModel.CreateAgentActionGroupRequest + { + AgentId = this.Id, + AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", + ActionGroupName = $"{this.GetDisplayName()}_CodeInterpreter", + ActionGroupState = AmazonBedrockAgent.ActionGroupState.ENABLED, + ParentActionGroupSignature = new(AmazonBedrockAgent.ActionGroupSignature.AMAZONCodeInterpreter), + }; + + await this._client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + } + + /// + /// Create a kernel function action group for the agent. + /// + private async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancellationToken) + { + var createAgentActionGroupRequest = new AmazonBedrockAgentModel.CreateAgentActionGroupRequest + { + AgentId = this.Id, + AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", + ActionGroupName = $"{this.GetDisplayName()}_KernelFunctions", + ActionGroupState = AmazonBedrockAgent.ActionGroupState.ENABLED, + ActionGroupExecutor = new() + { + CustomControl = AmazonBedrockAgent.CustomControlMethod.RETURN_CONTROL, + }, + FunctionSchema = this.Kernel.ToFunctionSchema(), + }; + + await this._client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + } + protected override IEnumerable GetChannelKeys() { // Return the channel keys for the BedrockAgent diff --git a/dotnet/src/Agents/Bedrock/Extensions/AgentInvokeExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/AgentInvokeExtensions.cs deleted file mode 100644 index 75d9567df7a3..000000000000 --- a/dotnet/src/Agents/Bedrock/Extensions/AgentInvokeExtensions.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Amazon.BedrockAgentRuntime.Model; -using Amazon.Runtime.EventStreams.Internal; -using Microsoft.SemanticKernel.Agents.Extensions; -using Microsoft.SemanticKernel.ChatCompletion; - -namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; - -/// -/// Extensions associated with the status of a . -/// -internal static class AgentInvokeExtensions -{ - public static async IAsyncEnumerable InternalInvokeAsync( - this BedrockAgent agent, - InvokeAgentRequest invokeAgentRequest, - KernelArguments? arguments, - [EnumeratorCancellation] CancellationToken cancellationToken) - { - var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); - - if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new HttpOperationException($"Failed to invoke agent. Status code: {invokeAgentResponse.HttpStatusCode}"); - } - - await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) - { - var chatMessageContent = - HandleChunkEvent(agent, responseEvent) ?? - HandleFilesEvent(agent, responseEvent) ?? - HandleReturnControlEvent(agent, responseEvent) ?? - HandleTraceEvent(agent, responseEvent) ?? - throw new KernelException($"Failed to handle Bedrock Agent stream event: {responseEvent}"); - yield return chatMessageContent; - } - } - - public static async IAsyncEnumerable InternalInvokeStreamingAsync( - this BedrockAgent agent, - InvokeAgentRequest invokeAgentRequest, - KernelArguments? arguments, - [EnumeratorCancellation] CancellationToken cancellationToken) - { - var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); - - if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new HttpOperationException($"Failed to invoke agent. Status code: {invokeAgentResponse.HttpStatusCode}"); - } - - await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) - { - var chatMessageContent = - HandleChunkEvent(agent, responseEvent) ?? - HandleFilesEvent(agent, responseEvent) ?? - HandleReturnControlEvent(agent, responseEvent) ?? - HandleTraceEvent(agent, responseEvent) ?? - throw new KernelException($"Failed to handle Bedrock Agent stream event: {responseEvent}"); - yield return new(chatMessageContent.Role, chatMessageContent.Content) - { - AuthorName = chatMessageContent.AuthorName, - ModelId = chatMessageContent.ModelId, - InnerContent = chatMessageContent.InnerContent, - }; - } - } - - private static ChatMessageContent? HandleChunkEvent( - BedrockAgent agent, - IEventStreamEvent responseEvent) - { - return responseEvent is not PayloadPart payload - ? null - : new ChatMessageContent() - { - Role = AuthorRole.Assistant, - AuthorName = agent.GetDisplayName(), - Content = Encoding.UTF8.GetString(payload.Bytes.ToArray()), - ModelId = agent.GetAgentModel().FoundationModel, - InnerContent = responseEvent, - }; - } - - private static ChatMessageContent? HandleFilesEvent( - BedrockAgent agent, - IEventStreamEvent responseEvent) - { - return responseEvent is not FilePart files - ? null - : new ChatMessageContent() - { - Role = AuthorRole.Assistant, - AuthorName = agent.GetDisplayName(), - Content = "Files received", - ModelId = agent.GetAgentModel().FoundationModel, - InnerContent = responseEvent, - }; - } - - private static ChatMessageContent? HandleReturnControlEvent( - BedrockAgent agent, - IEventStreamEvent responseEvent) - { - return responseEvent is not ReturnControlPayload returnControl - ? null - : new ChatMessageContent() - { - Role = AuthorRole.Assistant, - AuthorName = agent.GetDisplayName(), - Content = "Return control received", - ModelId = agent.GetAgentModel().FoundationModel, - InnerContent = responseEvent, - }; - } - - private static ChatMessageContent? HandleTraceEvent( - BedrockAgent agent, - IEventStreamEvent responseEvent) - { - return responseEvent is not TracePart trace - ? null - : new ChatMessageContent() - { - Role = AuthorRole.Assistant, - AuthorName = agent.GetDisplayName(), - Content = "Trace received", - ModelId = agent.GetAgentModel().FoundationModel, - InnerContent = responseEvent, - }; - } -} \ No newline at end of file diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs new file mode 100644 index 000000000000..6e927cee2a23 --- /dev/null +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs @@ -0,0 +1,231 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Amazon.BedrockAgentRuntime.Model; +using Amazon.Runtime.EventStreams.Internal; +using Microsoft.SemanticKernel.Agents.Extensions; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; + +/// +/// Extensions associated with the status of a . +/// +internal static class BedrockAgentInvokeExtensions +{ + public static async IAsyncEnumerable InternalInvokeAsync( + this BedrockAgent agent, + InvokeAgentRequest invokeAgentRequest, + KernelArguments? arguments, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); + + if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new HttpOperationException($"Failed to invoke agent. Status code: {invokeAgentResponse.HttpStatusCode}"); + } + + List functionCallContents = []; + await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) + { + // TODO: Handle exception events + var chatMessageContent = + HandleChunkEvent(agent, responseEvent) ?? + HandleFilesEvent(agent, responseEvent) ?? + HandleReturnControlEvent(agent, responseEvent, arguments) ?? + HandleTraceEvent(agent, responseEvent) ?? + throw new KernelException($"Failed to handle Bedrock Agent stream event: {responseEvent}"); + if (chatMessageContent.Items.Count > 0 && chatMessageContent.Items[0] is FunctionCallContent functionCallContent) + { + functionCallContents.AddRange(chatMessageContent.Items.Where(item => item is FunctionCallContent).Cast()); + } + else + { + yield return chatMessageContent; + } + } + + if (functionCallContents.Count > 0) + { + var functionResults = await InvokeFunctionCallsAsync(agent, functionCallContents, cancellationToken).ConfigureAwait(false); + var sessionState = CreateSessionStateWithFunctionResults(functionResults); + } + } + + public static async IAsyncEnumerable InternalInvokeStreamingAsync( + this BedrockAgent agent, + InvokeAgentRequest invokeAgentRequest, + KernelArguments? arguments, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); + + if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new HttpOperationException($"Failed to invoke agent. Status code: {invokeAgentResponse.HttpStatusCode}"); + } + + await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) + { + // TODO: Handle exception events + var chatMessageContent = + HandleChunkEvent(agent, responseEvent) ?? + HandleFilesEvent(agent, responseEvent) ?? + HandleReturnControlEvent(agent, responseEvent, arguments) ?? + HandleTraceEvent(agent, responseEvent) ?? + throw new KernelException($"Failed to handle Bedrock Agent stream event: {responseEvent}"); + yield return new(chatMessageContent.Role, chatMessageContent.Content) + { + AuthorName = chatMessageContent.AuthorName, + ModelId = chatMessageContent.ModelId, + InnerContent = chatMessageContent.InnerContent, + }; + } + } + + private static ChatMessageContent? HandleChunkEvent( + BedrockAgent agent, + IEventStreamEvent responseEvent) + { + return responseEvent is not PayloadPart payload + ? null + : new ChatMessageContent() + { + Role = AuthorRole.Assistant, + AuthorName = agent.GetDisplayName(), + Content = Encoding.UTF8.GetString(payload.Bytes.ToArray()), + ModelId = agent.GetAgentModel().FoundationModel, + InnerContent = responseEvent, + }; + } + + private static ChatMessageContent? HandleFilesEvent( + BedrockAgent agent, + IEventStreamEvent responseEvent) + { + if (responseEvent is not FilePart files) + { + return null; + } + + ChatMessageContentItemCollection binaryContents = []; + foreach (var file in files.Files) + { + binaryContents.Add(new BinaryContent(file.Bytes.ToArray(), file.Type) + { + Metadata = new Dictionary() + { + { "Name", file.Name }, + }, + }); + } + + return new ChatMessageContent() + { + Role = AuthorRole.Assistant, + AuthorName = agent.GetDisplayName(), + Items = binaryContents, + ModelId = agent.GetAgentModel().FoundationModel, + InnerContent = responseEvent, + }; + } + + private static ChatMessageContent? HandleReturnControlEvent( + BedrockAgent agent, + IEventStreamEvent responseEvent, + KernelArguments? arguments) + { + if (responseEvent is not ReturnControlPayload returnControlPayload) + { + return null; + } + + ChatMessageContentItemCollection functionCallContents = []; + foreach (var invocationInput in returnControlPayload.InvocationInputs) + { + var functionInvocationInput = invocationInput.FunctionInvocationInput; + functionCallContents.Add(new FunctionCallContent( + functionInvocationInput.Function, + id: returnControlPayload.InvocationId, + arguments: functionInvocationInput.Parameters.FromFunctionParameters(arguments)) + { + Metadata = new Dictionary() + { + { "ActionGroup", functionInvocationInput.ActionGroup }, + { "ActionInvocationType", functionInvocationInput.ActionInvocationType }, + }, + }); + } + + return new ChatMessageContent() + { + Role = AuthorRole.Assistant, + AuthorName = agent.GetDisplayName(), + Items = functionCallContents, + ModelId = agent.GetAgentModel().FoundationModel, + InnerContent = responseEvent, + }; + } + + private static ChatMessageContent? HandleTraceEvent( + BedrockAgent agent, + IEventStreamEvent responseEvent) + { + return responseEvent is not TracePart trace + ? null + : new ChatMessageContent() + { + Role = AuthorRole.Assistant, + AuthorName = agent.GetDisplayName(), + Content = "Trace received", + ModelId = agent.GetAgentModel().FoundationModel, + InnerContent = responseEvent, + }; + } + + private static async Task> InvokeFunctionCallsAsync( + BedrockAgent agent, + List functionCallContents, + CancellationToken cancellationToken) + { + var functionResults = await Task.WhenAll(functionCallContents.Select(async functionCallContent => + { + return await functionCallContent.InvokeAsync(agent.Kernel, cancellationToken).ConfigureAwait(false); + })).ConfigureAwait(false); + + return [.. functionResults]; + } + + private static SessionState CreateSessionStateWithFunctionResults(List functionResults) + { + return new SessionState() + { + ReturnControlInvocationResults = [.. functionResults.Select(functionResult => + { + return new InvocationResultMember() + { + FunctionResult = new() + { + ActionGroup = functionResult.Metadata!["ActionGroup"] as string, + Function = functionResult.FunctionName, + ResponseBody = { + { + "TEXT", + new() + { + Body = functionResult.Result as string, + } + }, + }, + }, + }; + })], + }; + } +} \ No newline at end of file diff --git a/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs similarity index 97% rename from dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs rename to dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs index 122cf44a5844..c6c2b34b84c3 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs @@ -11,7 +11,7 @@ namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; /// /// Extensions associated with the status of a . /// -internal static class AgentStatusExtensions +internal static class BedrockAgentStatusExtensions { /// /// Wait for the agent to reach the specified status. diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs new file mode 100644 index 000000000000..5aaf39374d9f --- /dev/null +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using AmazonBedrockAgentModel = Amazon.BedrockAgent.Model; +using AmazonBedrockAgentRuntimeModel = Amazon.BedrockAgentRuntime.Model; + +namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; + +/// +/// Extensions associated with the status of a . +/// +internal static class BedrockFunctionSchemaExtensions +{ + public static KernelArguments FromFunctionParameters(this List parameters, KernelArguments? arguments) + { + KernelArguments kernelArguments = arguments ?? []; + foreach (var parameter in parameters) + { + kernelArguments.Add(parameter.Name, parameter.Value); + } + + return kernelArguments; + } + + public static AmazonBedrockAgentModel.FunctionSchema ToFunctionSchema(this Kernel kernel) + { + var plugins = kernel.Plugins; + List functions = []; + foreach (var plugin in plugins) + { + foreach (KernelFunction function in plugin) + { + functions.Add(new AmazonBedrockAgentModel.Function + { + Name = function.Name, + Description = function.Description, + Parameters = function.Metadata.Parameters.CreateParameterSpec(), + // This field controls whether user confirmation is required to invoke the function. + // If this is set to "ENABLED", the user will be prompted to confirm the function invocation. + // Only after the user confirms, the function call request will be issued by the agent. + // If the user denies the confirmation, the agent will act as if the function does not exist. + // Currently, we do not support this feature, so we set it to "DISABLED". + RequireConfirmation = Amazon.BedrockAgent.RequireConfirmation.DISABLED, + }); + } + } + + return new AmazonBedrockAgentModel.FunctionSchema + { + Functions = functions, + }; + } + + private static Dictionary CreateParameterSpec( + this IReadOnlyList parameters) + { + Dictionary parameterSpec = []; + foreach (var parameter in parameters) + { + parameterSpec.Add(parameter.Name, new AmazonBedrockAgentModel.ParameterDetail + { + Description = parameter.Description, + Required = parameter.IsRequired, + Type = parameter.ParameterType.ToAmazonType(), + }); + } + + return parameterSpec; + } + + private static Amazon.BedrockAgent.Type ToAmazonType(this System.Type? parameterType) + { + var typeString = parameterType?.GetFriendlyTypeName(); + return typeString switch + { + "String" => Amazon.BedrockAgent.Type.String, + "Boolean" => Amazon.BedrockAgent.Type.Boolean, + "Int16" => Amazon.BedrockAgent.Type.Integer, + "UInt16" => Amazon.BedrockAgent.Type.Integer, + "Int32" => Amazon.BedrockAgent.Type.Integer, + "UInt32" => Amazon.BedrockAgent.Type.Integer, + "Int64" => Amazon.BedrockAgent.Type.Integer, + "UInt64" => Amazon.BedrockAgent.Type.Integer, + "Single" => Amazon.BedrockAgent.Type.Number, + "Double" => Amazon.BedrockAgent.Type.Number, + "Decimal" => Amazon.BedrockAgent.Type.Number, + // TODO: Add support for array type. + _ => throw new ArgumentException($"Unsupported parameter type: {typeString}"), + }; + } +} \ No newline at end of file diff --git a/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs b/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs index d7424eca571a..9dfe17744192 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Text; using System.Text.Json.Serialization; +using System.Threading.Tasks; using Microsoft.SemanticKernel.Text; #pragma warning disable CA1056 // URI-like properties should not be strings @@ -118,6 +120,31 @@ public BinaryContent( this.Data = data; } + /// + /// Write the binary content to a file. + /// + /// The path to the file to write the content to. + /// Whether to overwrite the file if it already exists. + public void WriteToFile(string filePath, bool overwrite = false) + { + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new ArgumentException("File path cannot be null or empty", nameof(filePath)); + } + + if (!overwrite && File.Exists(filePath)) + { + throw new InvalidOperationException("File already exists."); + } + + if (!this.CanRead) + { + throw new InvalidOperationException("No content to write to file."); + } + + File.WriteAllBytes(filePath, this.Data!.Value.ToArray()); + } + #region Private /// /// Sets the Uri of the content. From 9a8e06cff29f844a1d76c75ffa0a8df3d03f9161 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 11 Feb 2025 14:38:04 -0800 Subject: [PATCH 04/27] Kernel function done --- .../BedrockAgent/Step01_BedrockAgent.cs | 39 ++---- .../Step02_BedrockAgent_CodeInterpreter.cs | 26 ++-- .../Step03_BedrockAgent_Functions.cs | 100 +++++++++++--- .../src/Agents/Bedrock/Agents.Bedrock.csproj | 1 + dotnet/src/Agents/Bedrock/BedrockAgent.cs | 31 ++++- ...Extensions.cs => AgentStatusExtensions.cs} | 0 .../BedrockAgentInvokeExtensions.cs | 129 ++++++++---------- .../AgentUtilities/BaseBedrockAgentTest.cs | 29 ++++ 8 files changed, 211 insertions(+), 144 deletions(-) rename dotnet/src/Agents/Bedrock/Extensions/{BedrockAgentStatusExtensions.cs => AgentStatusExtensions.cs} (100%) create mode 100644 dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs index ec880661104c..8ffba1ed8b54 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using Amazon.BedrockAgent.Model; using Microsoft.SemanticKernel.Agents.Bedrock; namespace GettingStarted.BedrockAgents; @@ -8,27 +7,15 @@ namespace GettingStarted.BedrockAgents; /// /// This example demonstrates how to interact with a in the most basic way. /// -public class Step01_BedrockAgent(ITestOutputHelper output) : BaseAgentsTest(output) +public class Step01_BedrockAgent(ITestOutputHelper output) : BaseBedrockAgentTest(output) { - private const string AgentName = "Semantic-Kernel-Test-Agent"; - private const string AgentDescription = "A helpful assistant who helps users find information."; - private const string AgentInstruction = "You're a helpful assistant who helps users find information."; - private const string UserQuery = "Why is the sky blue?"; + private const string UserQuery = "Why is the sky blue in one sentence?"; [Fact] public async Task UseNewAgentAsync() { - // Define the agent - CreateAgentRequest createAgentRequest = new() - { - AgentName = AgentName, - Description = AgentDescription, - Instruction = AgentInstruction, - AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, - FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, - }; - - var bedrock_agent = await BedrockAgent.CreateAsync(createAgentRequest); + // Create the agent + var bedrock_agent = await this.CreateAgentAsync("Step01_BedrockAgent"); // Respond to user input try @@ -48,17 +35,8 @@ public async Task UseNewAgentAsync() [Fact] public async Task UseNewAgentStreamingAsync() { - // Define the agent - CreateAgentRequest createAgentRequest = new() - { - AgentName = AgentName, - Description = AgentDescription, - Instruction = AgentInstruction, - AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, - FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, - }; - - var bedrock_agent = await BedrockAgent.CreateAsync(createAgentRequest); + // Create the agent + var bedrock_agent = await this.CreateAgentAsync("Step01_BedrockAgent_Streaming"); // Respond to user input try @@ -86,4 +64,9 @@ public async Task UseTemplateForAssistantAgentAsync() // Local function to invoke agent and display the response. } + + protected override async Task CreateAgentAsync(string agentName) + { + return await BedrockAgent.CreateAsync(this.GetCreateAgentRequest(agentName)); + } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs index 35496f8495f9..ff981fb824ee 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Reflection; -using Amazon.BedrockAgent.Model; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.Bedrock; @@ -10,11 +9,8 @@ namespace GettingStarted.BedrockAgents; /// /// This example demonstrates how to interact with a with code interpreter enabled. /// -public class Step02_BedrockAgent_CodeInterpreter(ITestOutputHelper output) : BaseAgentsTest(output) +public class Step02_BedrockAgent_CodeInterpreter(ITestOutputHelper output) : BaseBedrockAgentTest(output) { - private const string AgentName = "Semantic-Kernel-Test-Agent"; - private const string AgentDescription = "A helpful assistant who helps users find information."; - private const string AgentInstruction = "You're a helpful assistant who helps users find information."; private const string UserQuery = @"Create a bar chart for the following data: Panda 5 Tiger 8 @@ -25,17 +21,8 @@ Monkey 6 [Fact] public async Task UseAgentWithCodeInterpreterAsync() { - // Define the agent - CreateAgentRequest createAgentRequest = new() - { - AgentName = AgentName, - Description = AgentDescription, - Instruction = AgentInstruction, - AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, - FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, - }; - - var bedrock_agent = await BedrockAgent.CreateAsync(createAgentRequest, enableCodeInterpreter: true); + // Create the agent + var bedrock_agent = await this.CreateAgentAsync("Step02_BedrockAgent_CodeInterpreter"); // Respond to user input try @@ -64,7 +51,7 @@ public async Task UseAgentWithCodeInterpreterAsync() Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, binaryContent.Metadata!["Name"]!.ToString()!); this.Output.WriteLine($"Saving file to {filePath}"); - binaryContent.WriteToFile(filePath); + binaryContent.WriteToFile(filePath, overwrite: true); // Expected output: // Here is the bar chart for the given data: @@ -81,4 +68,9 @@ public async Task UseAgentWithCodeInterpreterAsync() await bedrock_agent.DeleteAsync(CancellationToken.None); } } + + protected override async Task CreateAgentAsync(string agentName) + { + return await BedrockAgent.CreateAsync(this.GetCreateAgentRequest(agentName), enableCodeInterpreter: true); + } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs index 223a1966e1a2..ea56174010a1 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.ComponentModel; -using Amazon.BedrockAgent.Model; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.Bedrock; @@ -10,38 +9,78 @@ namespace GettingStarted.BedrockAgents; /// /// This example demonstrates how to interact with a with kernel functions. /// -public class Step02_BedrockAgent_Functions(ITestOutputHelper output) : BaseAgentsTest(output) +public class Step02_BedrockAgent_Functions(ITestOutputHelper output) : BaseBedrockAgentTest(output) { - private const string AgentName = "Semantic-Kernel-Test-Agent"; - private const string AgentDescription = "A helpful assistant who helps users find information."; - private const string AgentInstruction = "You're a helpful assistant who helps users find information."; - private const string UserQuery = "What is the weather in Seattle?"; - [Fact] public async Task UseAgentWithFunctionsAsync() { - // Define the agent - CreateAgentRequest createAgentRequest = new() + // Create the agent + var bedrock_agent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions"); + + // Respond to user input + try + { + var responses = bedrock_agent.InvokeAsync( + BedrockAgent.CreateSessionId(), + "What is the weather in Seattle?", + null, + CancellationToken.None); + await foreach (var response in responses) + { + if (response.Content != null) + { + this.Output.WriteLine(response.Content); + } + } + } + finally { - AgentName = AgentName, - Description = AgentDescription, - Instruction = AgentInstruction, - AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, - FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, - }; + await bedrock_agent.DeleteAsync(CancellationToken.None); + } + } - Kernel kernel = new(); - kernel.Plugins.Add(KernelPluginFactory.CreateFromType()); + [Fact] + public async Task UseAgentStreamingWithFunctionsAsync() + { + // Create the agent + var bedrock_agent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions_Streaming"); - var bedrock_agent = await BedrockAgent.CreateAsync( - createAgentRequest, - kernel: kernel, - enableKernelFunctions: true); + // Respond to user input + try + { + var streamingResponses = bedrock_agent.InvokeStreamingAsync( + BedrockAgent.CreateSessionId(), + "What is the weather forecast in Seattle?", + null, + CancellationToken.None); + await foreach (var response in streamingResponses) + { + if (response.Content != null) + { + this.Output.WriteLine(response.Content); + } + } + } + finally + { + await bedrock_agent.DeleteAsync(CancellationToken.None); + } + } + + [Fact] + public async Task UseAgentWithParallelFunctionsAsync() + { + // Create the agent + var bedrock_agent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions_Parallel"); // Respond to user input try { - var responses = bedrock_agent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + var responses = bedrock_agent.InvokeAsync( + BedrockAgent.CreateSessionId(), + "What is the current weather in Seattle and what is the weather forecast in Seattle?", + null, + CancellationToken.None); await foreach (var response in responses) { if (response.Content != null) @@ -56,6 +95,17 @@ public async Task UseAgentWithFunctionsAsync() } } + protected override async Task CreateAgentAsync(string agentName) + { + Kernel kernel = new(); + kernel.Plugins.Add(KernelPluginFactory.CreateFromType()); + + return await BedrockAgent.CreateAsync( + this.GetCreateAgentRequest(agentName), + kernel: kernel, + enableKernelFunctions: true); + } + private sealed class WeatherPlugin { [KernelFunction, Description("Provides realtime weather information.")] @@ -64,5 +114,11 @@ public string Current([Description("The location to get the weather for.")] stri { return $"The current weather in {location} is 72 degrees."; } + + [KernelFunction, Description("Forecast weather information.")] + public string Forecast([Description("The location to get the weather for.")] string location) + { + return $"The forecast for {location} is 75 degrees tomorrow."; + } } } diff --git a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj index 79d142eb072c..085d2726c26b 100644 --- a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj +++ b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj @@ -25,6 +25,7 @@ + diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index 099540a2c976..e74bcfc8aa02 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -11,6 +11,7 @@ using Microsoft.SemanticKernel.Agents.Extensions; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; +using System.Runtime.CompilerServices; namespace Microsoft.SemanticKernel.Agents.Bedrock; @@ -191,11 +192,11 @@ public IAsyncEnumerable InvokeAsync( /// The message to send to the agent. /// The arguments to use when invoking the agent. /// The to monitor for cancellation requests. The default is . /// An of . - public IAsyncEnumerable InvokeStreamingAsync( + public async IAsyncEnumerable InvokeStreamingAsync( string sessionId, string message, KernelArguments? arguments, - CancellationToken cancellationToken) + [EnumeratorCancellation] CancellationToken cancellationToken) { var invokeAgentRequest = new AmazonBedrockAgentRuntimeModel.InvokeAgentRequest { @@ -209,10 +210,22 @@ public IAsyncEnumerable InvokeStreamingAsync( }, }; - return ActivityExtensions.RunWithActivityAsync( + // The Bedrock agent service has the same API for both streaming and non-streaming responses. + // We are invoking the same method as the non-streaming response with the streaming configuration set, + // and converting the chat message content to streaming chat message content. + await foreach (var chatMessageContent in ActivityExtensions.RunWithActivityAsync( () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), - () => this.InternalInvokeStreamingAsync(invokeAgentRequest, arguments, cancellationToken), - cancellationToken); + () => this.InternalInvokeAsync(invokeAgentRequest, arguments, cancellationToken), + cancellationToken).ConfigureAwait(false)) + { + yield return new StreamingChatMessageContent(chatMessageContent.Role, chatMessageContent.Content) + { + AuthorName = chatMessageContent.AuthorName, + ModelId = chatMessageContent.ModelId, + InnerContent = chatMessageContent.InnerContent, + Metadata = chatMessageContent.Metadata, + }; + } } /// @@ -224,7 +237,7 @@ private async Task CreateCodeInterpreterActionGroupAsync(CancellationToken cance { AgentId = this.Id, AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", - ActionGroupName = $"{this.GetDisplayName()}_CodeInterpreter", + ActionGroupName = this.GetCodeInterpreterActionGroupSignature(), ActionGroupState = AmazonBedrockAgent.ActionGroupState.ENABLED, ParentActionGroupSignature = new(AmazonBedrockAgent.ActionGroupSignature.AMAZONCodeInterpreter), }; @@ -241,7 +254,7 @@ private async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancel { AgentId = this.Id, AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", - ActionGroupName = $"{this.GetDisplayName()}_KernelFunctions", + ActionGroupName = this.GetKernelFunctionActionGroupSignature(), ActionGroupState = AmazonBedrockAgent.ActionGroupState.ENABLED, ActionGroupExecutor = new() { @@ -274,4 +287,8 @@ protected override Task RestoreChannelAsync(string channelState, C internal AmazonBedrockAgent.AmazonBedrockAgentClient GetClient() => this._client; internal AmazonBedrockAgentRuntimeClient GetRuntimeClient() => this._runtimeClient; internal AmazonBedrockAgentModel.Agent GetAgentModel() => this._agentModel; + + internal string GetCodeInterpreterActionGroupSignature() => $"{this.GetDisplayName()}_CodeInterpreter"; + internal string GetKernelFunctionActionGroupSignature() => $"{this.GetDisplayName()}_KernelFunctions"; + } diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs similarity index 100% rename from dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs rename to dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs index 6e927cee2a23..b56dd60bee65 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs @@ -10,6 +10,7 @@ using Amazon.Runtime.EventStreams.Internal; using Microsoft.SemanticKernel.Agents.Extensions; using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.FunctionCalling; namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; @@ -24,68 +25,58 @@ public static async IAsyncEnumerable InternalInvokeAsync( KernelArguments? arguments, [EnumeratorCancellation] CancellationToken cancellationToken) { - var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); - - if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new HttpOperationException($"Failed to invoke agent. Status code: {invokeAgentResponse.HttpStatusCode}"); - } - - List functionCallContents = []; - await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) + // Session state is used to store the results of function calls to be passed back to the agent. + // https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/BedrockAgentRuntime/TSessionState.html + SessionState? sessionState = null; + for (var requestIndex = 0; ; requestIndex++) { - // TODO: Handle exception events - var chatMessageContent = - HandleChunkEvent(agent, responseEvent) ?? - HandleFilesEvent(agent, responseEvent) ?? - HandleReturnControlEvent(agent, responseEvent, arguments) ?? - HandleTraceEvent(agent, responseEvent) ?? - throw new KernelException($"Failed to handle Bedrock Agent stream event: {responseEvent}"); - if (chatMessageContent.Items.Count > 0 && chatMessageContent.Items[0] is FunctionCallContent functionCallContent) + if (sessionState != null) { - functionCallContents.AddRange(chatMessageContent.Items.Where(item => item is FunctionCallContent).Cast()); + invokeAgentRequest.SessionState = sessionState; + sessionState = null; } - else + var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); + + if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) { - yield return chatMessageContent; + throw new HttpOperationException($"Failed to invoke agent. Status code: {invokeAgentResponse.HttpStatusCode}"); } - } - - if (functionCallContents.Count > 0) - { - var functionResults = await InvokeFunctionCallsAsync(agent, functionCallContents, cancellationToken).ConfigureAwait(false); - var sessionState = CreateSessionStateWithFunctionResults(functionResults); - } - } - public static async IAsyncEnumerable InternalInvokeStreamingAsync( - this BedrockAgent agent, - InvokeAgentRequest invokeAgentRequest, - KernelArguments? arguments, - [EnumeratorCancellation] CancellationToken cancellationToken) - { - var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); + List functionCallContents = []; + await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) + { + // TODO: Handle exception events + var chatMessageContent = + HandleChunkEvent(agent, responseEvent) ?? + HandleFilesEvent(agent, responseEvent) ?? + HandleReturnControlEvent(agent, responseEvent, arguments) ?? + HandleTraceEvent(agent, responseEvent) ?? + throw new KernelException($"Failed to handle Bedrock Agent stream event: {responseEvent}"); + if (chatMessageContent.Items.Count > 0 && chatMessageContent.Items[0] is FunctionCallContent functionCallContent) + { + functionCallContents.AddRange(chatMessageContent.Items.Where(item => item is FunctionCallContent).Cast()); + } + else + { + yield return chatMessageContent; + } + } - if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new HttpOperationException($"Failed to invoke agent. Status code: {invokeAgentResponse.HttpStatusCode}"); - } + // This is used to cap the auto function invocation loop to prevent infinite loops. + // It doesn't use the the `FunctionCallsProcessor` to process the functions because we do not need + // many of the features it offers and we want to keep the code simple. + var functionChoiceBehaviorConfiguration = new FunctionCallsProcessor().GetConfiguration( + FunctionChoiceBehavior.Auto(), [], requestIndex, agent.Kernel); - await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) - { - // TODO: Handle exception events - var chatMessageContent = - HandleChunkEvent(agent, responseEvent) ?? - HandleFilesEvent(agent, responseEvent) ?? - HandleReturnControlEvent(agent, responseEvent, arguments) ?? - HandleTraceEvent(agent, responseEvent) ?? - throw new KernelException($"Failed to handle Bedrock Agent stream event: {responseEvent}"); - yield return new(chatMessageContent.Role, chatMessageContent.Content) + if (functionCallContents.Count > 0 && functionChoiceBehaviorConfiguration!.AutoInvoke) { - AuthorName = chatMessageContent.AuthorName, - ModelId = chatMessageContent.ModelId, - InnerContent = chatMessageContent.InnerContent, - }; + var functionResults = await InvokeFunctionCallsAsync(agent, functionCallContents, cancellationToken).ConfigureAwait(false); + sessionState = CreateSessionStateWithFunctionResults(functionResults, agent); + } + else + { + break; + } } } @@ -202,30 +193,28 @@ private static async Task> InvokeFunctionCallsAsync( return [.. functionResults]; } - private static SessionState CreateSessionStateWithFunctionResults(List functionResults) + private static SessionState CreateSessionStateWithFunctionResults(List functionResults, BedrockAgent agent) { - return new SessionState() - { - ReturnControlInvocationResults = [.. functionResults.Select(functionResult => + return functionResults.Count == 0 + ? throw new KernelException("No function results were returned.") + : new() + { + InvocationId = functionResults[0].CallId, + ReturnControlInvocationResults = [.. functionResults.Select(functionResult => { return new InvocationResultMember() { - FunctionResult = new() + FunctionResult = new Amazon.BedrockAgentRuntime.Model.FunctionResult { - ActionGroup = functionResult.Metadata!["ActionGroup"] as string, + ActionGroup = agent.GetKernelFunctionActionGroupSignature(), Function = functionResult.FunctionName, - ResponseBody = { - { - "TEXT", - new() - { - Body = functionResult.Result as string, - } - }, - }, - }, + ResponseBody = new Dictionary + { + { "TEXT", new ContentBody() { Body = functionResult.Result as string } } + } + } }; })], - }; + }; } } \ No newline at end of file diff --git a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs new file mode 100644 index 000000000000..23332f33df22 --- /dev/null +++ b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Amazon.BedrockAgent.Model; +using Microsoft.SemanticKernel.Agents.Bedrock; + +/// +/// Base class for samples that demonstrate the usage of agents. +/// +public abstract class BaseBedrockAgentTest(ITestOutputHelper output) : BaseTest(output, redirectSystemConsoleOutput: true) +{ + protected const string AgentDescription = "A helpful assistant who helps users find information."; + protected const string AgentInstruction = "You're a helpful assistant who helps users find information."; + + protected CreateAgentRequest GetCreateAgentRequest(string agentName) => new() + { + AgentName = agentName, + Description = AgentDescription, + Instruction = AgentInstruction, + AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, + FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, + }; + + /// + /// Create a new instance. + /// Override this method to create an agent with desired settings. + /// + /// The name of the agent to create. Must be unique. + protected abstract Task CreateAgentAsync(string agentName); +} \ No newline at end of file From 5f1279d118a154f1f06259aaed758e76824d6f9c Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 11 Feb 2025 14:53:14 -0800 Subject: [PATCH 05/27] Formatting --- .../BedrockAgent/Step01_BedrockAgent.cs | 2 +- .../BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs | 2 +- .../BedrockAgent/Step03_BedrockAgent_Functions.cs | 2 +- .../samples/AgentUtilities/BaseBedrockAgentTest.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs index 8ffba1ed8b54..f8a0fb9987d1 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using Microsoft.SemanticKernel.Agents.Bedrock; diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs index ff981fb824ee..26a8c7b40f28 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System.Reflection; using Microsoft.SemanticKernel; diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs index ea56174010a1..ab4fda6b5855 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System.ComponentModel; using Microsoft.SemanticKernel; diff --git a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs index 23332f33df22..b33cf32e8baa 100644 --- a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs +++ b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using Amazon.BedrockAgent.Model; using Microsoft.SemanticKernel.Agents.Bedrock; @@ -26,4 +26,4 @@ public abstract class BaseBedrockAgentTest(ITestOutputHelper output) : BaseTest( /// /// The name of the agent to create. Must be unique. protected abstract Task CreateAgentAsync(string agentName); -} \ No newline at end of file +} From bac84869672417b646e7c5affffbe38ae40282ba Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 11 Feb 2025 15:00:36 -0800 Subject: [PATCH 06/27] Formatting 2 --- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 3 +-- dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs | 1 - .../src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs | 5 ++--- .../Bedrock/Extensions/BedrockAgentInvokeExtensions.cs | 4 ++-- .../Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs | 4 ++-- dotnet/src/Agents/Bedrock/Properties/AssemblyInfo.cs | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index e74bcfc8aa02..1ec683d01bce 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using AmazonBedrockAgent = Amazon.BedrockAgent; @@ -11,7 +12,6 @@ using Microsoft.SemanticKernel.Agents.Extensions; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; -using System.Runtime.CompilerServices; namespace Microsoft.SemanticKernel.Agents.Bedrock; @@ -290,5 +290,4 @@ protected override Task RestoreChannelAsync(string channelState, C internal string GetCodeInterpreterActionGroupSignature() => $"{this.GetDisplayName()}_CodeInterpreter"; internal string GetKernelFunctionActionGroupSignature() => $"{this.GetDisplayName()}_KernelFunctions"; - } diff --git a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs index 83fbf2b22b6a..cd794ca99e59 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.SemanticKernel.Agents; namespace Microsoft.SemanticKernel.Agents.Bedrock; diff --git a/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs index c6c2b34b84c3..a0ec7a389f66 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs @@ -1,11 +1,10 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Threading; using System.Threading.Tasks; using AmazonBedrockAgent = Amazon.BedrockAgent; - namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; /// @@ -42,4 +41,4 @@ public static async Task WaitForAgentStatusAsync( throw new TimeoutException($"Agent did not reach status {status} within the specified time."); } -} \ No newline at end of file +} diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs index b56dd60bee65..0c8c126ea130 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; using System.Linq; @@ -217,4 +217,4 @@ private static SessionState CreateSessionStateWithFunctionResults(List throw new ArgumentException($"Unsupported parameter type: {typeString}"), }; } -} \ No newline at end of file +} diff --git a/dotnet/src/Agents/Bedrock/Properties/AssemblyInfo.cs b/dotnet/src/Agents/Bedrock/Properties/AssemblyInfo.cs index c4c5cac19736..bd1c0f58314e 100644 --- a/dotnet/src/Agents/Bedrock/Properties/AssemblyInfo.cs +++ b/dotnet/src/Agents/Bedrock/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System.Diagnostics.CodeAnalysis; From 2771168e0a2c973769c2b6c4fdeb12a3481cebaa Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 11 Feb 2025 15:18:35 -0800 Subject: [PATCH 07/27] Reformat usings --- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 48 +++++++++---------- .../Extensions/AgentStatusExtensions.cs | 4 +- .../BedrockFunctionSchemaExtensions.cs | 20 ++++---- .../AgentUtilities/BaseBedrockAgentTest.cs | 2 +- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index 1ec683d01bce..c20cc65cf1fc 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -5,10 +5,10 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using AmazonBedrockAgent = Amazon.BedrockAgent; +using Amazon.BedrockAgent; using Amazon.BedrockAgentRuntime; -using AmazonBedrockAgentModel = Amazon.BedrockAgent.Model; -using AmazonBedrockAgentRuntimeModel = Amazon.BedrockAgentRuntime.Model; +using Amazon.BedrockAgent.Model; +using Amazon.BedrockAgentRuntime.Model; using Microsoft.SemanticKernel.Agents.Extensions; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; @@ -20,11 +20,11 @@ namespace Microsoft.SemanticKernel.Agents.Bedrock; /// public class BedrockAgent : KernelAgent { - private readonly AmazonBedrockAgent.AmazonBedrockAgentClient _client; + private readonly AmazonBedrockAgentClient _client; private readonly AmazonBedrockAgentRuntimeClient _runtimeClient; - private readonly AmazonBedrockAgentModel.Agent _agentModel; + private readonly Amazon.BedrockAgent.Model.Agent _agentModel; // There is a default alias created by Bedrock for the working draft version of the agent. // https://docs.aws.amazon.com/bedrock/latest/userguide/agents-deploy.html @@ -37,8 +37,8 @@ public class BedrockAgent : KernelAgent /// A client used to interact with the Bedrock Agent service. /// A client used to interact with the Bedrock Agent runtime service. public BedrockAgent( - AmazonBedrockAgentModel.Agent agentModel, - AmazonBedrockAgent.AmazonBedrockAgentClient client, + Amazon.BedrockAgent.Model.Agent agentModel, + AmazonBedrockAgentClient client, AmazonBedrockAgentRuntimeClient runtimeClient) { this._agentModel = agentModel; @@ -64,16 +64,16 @@ public BedrockAgent( /// The to monitor for cancellation requests. The default is . /// An instance of the . public static async Task CreateAsync( - AmazonBedrockAgentModel.CreateAgentRequest request, + CreateAgentRequest request, bool enableCodeInterpreter = false, bool enableKernelFunctions = false, - AmazonBedrockAgent.AmazonBedrockAgentClient? client = null, + AmazonBedrockAgentClient? client = null, AmazonBedrockAgentRuntimeClient? runtimeClient = null, Kernel? kernel = null, KernelArguments? defaultArguments = null, CancellationToken cancellationToken = default) { - client ??= new AmazonBedrockAgent.AmazonBedrockAgentClient(); + client ??= new AmazonBedrockAgentClient(); runtimeClient ??= new AmazonBedrockAgentRuntimeClient(); var createAgentResponse = await client.CreateAgentAsync(request, cancellationToken).ConfigureAwait(false); @@ -85,7 +85,7 @@ public static async Task CreateAsync( // The agent will first enter the CREATING status. // When the agent is created, it will enter the NOT_PREPARED status. - await agent.WaitForAgentStatusAsync(AmazonBedrockAgent.AgentStatus.NOT_PREPARED, cancellationToken).ConfigureAwait(false); + await agent.WaitForAgentStatusAsync(AgentStatus.NOT_PREPARED, cancellationToken).ConfigureAwait(false); if (enableCodeInterpreter) { @@ -114,13 +114,13 @@ public static async Task CreateAsync( /// An instance of the . public static async Task RetrieveAsync( string id, - AmazonBedrockAgent.AmazonBedrockAgentClient? client = null, + AmazonBedrockAgentClient? client = null, AmazonBedrockAgentRuntimeClient? runtimeClient = null, Kernel? kernel = null, KernelArguments? defaultArguments = null, CancellationToken cancellationToken = default) { - client ??= new AmazonBedrockAgent.AmazonBedrockAgentClient(); + client ??= new AmazonBedrockAgentClient(); runtimeClient ??= new AmazonBedrockAgentRuntimeClient(); var getAgentResponse = await client.GetAgentAsync(new() { AgentId = id }, cancellationToken).ConfigureAwait(false); @@ -154,7 +154,7 @@ public async Task DeleteAsync(CancellationToken cancellationToken) public async Task PrepareAsync(CancellationToken cancellationToken) { await this._client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); - await this.WaitForAgentStatusAsync(AmazonBedrockAgent.AgentStatus.PREPARED, cancellationToken).ConfigureAwait(false); + await this.WaitForAgentStatusAsync(AgentStatus.PREPARED, cancellationToken).ConfigureAwait(false); } /// @@ -171,7 +171,7 @@ public IAsyncEnumerable InvokeAsync( KernelArguments? arguments, CancellationToken cancellationToken) { - var invokeAgentRequest = new AmazonBedrockAgentRuntimeModel.InvokeAgentRequest + var invokeAgentRequest = new InvokeAgentRequest { AgentAliasId = WORKING_DRAFT_AGENT_ALIAS, AgentId = this.Id, @@ -198,7 +198,7 @@ public async IAsyncEnumerable InvokeStreamingAsync( KernelArguments? arguments, [EnumeratorCancellation] CancellationToken cancellationToken) { - var invokeAgentRequest = new AmazonBedrockAgentRuntimeModel.InvokeAgentRequest + var invokeAgentRequest = new InvokeAgentRequest { AgentAliasId = WORKING_DRAFT_AGENT_ALIAS, AgentId = this.Id, @@ -233,13 +233,13 @@ public async IAsyncEnumerable InvokeStreamingAsync( /// private async Task CreateCodeInterpreterActionGroupAsync(CancellationToken cancellationToken) { - var createAgentActionGroupRequest = new AmazonBedrockAgentModel.CreateAgentActionGroupRequest + var createAgentActionGroupRequest = new CreateAgentActionGroupRequest { AgentId = this.Id, AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", ActionGroupName = this.GetCodeInterpreterActionGroupSignature(), - ActionGroupState = AmazonBedrockAgent.ActionGroupState.ENABLED, - ParentActionGroupSignature = new(AmazonBedrockAgent.ActionGroupSignature.AMAZONCodeInterpreter), + ActionGroupState = ActionGroupState.ENABLED, + ParentActionGroupSignature = new(Amazon.BedrockAgent.ActionGroupSignature.AMAZONCodeInterpreter), }; await this._client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); @@ -250,15 +250,15 @@ private async Task CreateCodeInterpreterActionGroupAsync(CancellationToken cance /// private async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancellationToken) { - var createAgentActionGroupRequest = new AmazonBedrockAgentModel.CreateAgentActionGroupRequest + var createAgentActionGroupRequest = new CreateAgentActionGroupRequest { AgentId = this.Id, AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", ActionGroupName = this.GetKernelFunctionActionGroupSignature(), - ActionGroupState = AmazonBedrockAgent.ActionGroupState.ENABLED, + ActionGroupState = ActionGroupState.ENABLED, ActionGroupExecutor = new() { - CustomControl = AmazonBedrockAgent.CustomControlMethod.RETURN_CONTROL, + CustomControl = Amazon.BedrockAgent.CustomControlMethod.RETURN_CONTROL, }, FunctionSchema = this.Kernel.ToFunctionSchema(), }; @@ -284,9 +284,9 @@ protected override Task RestoreChannelAsync(string channelState, C return Task.FromResult(new BedrockAgentChannel()); } - internal AmazonBedrockAgent.AmazonBedrockAgentClient GetClient() => this._client; + internal AmazonBedrockAgentClient GetClient() => this._client; internal AmazonBedrockAgentRuntimeClient GetRuntimeClient() => this._runtimeClient; - internal AmazonBedrockAgentModel.Agent GetAgentModel() => this._agentModel; + internal Amazon.BedrockAgent.Model.Agent GetAgentModel() => this._agentModel; internal string GetCodeInterpreterActionGroupSignature() => $"{this.GetDisplayName()}_CodeInterpreter"; internal string GetKernelFunctionActionGroupSignature() => $"{this.GetDisplayName()}_KernelFunctions"; diff --git a/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs index a0ec7a389f66..f564f614bcc6 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs @@ -3,7 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; -using AmazonBedrockAgent = Amazon.BedrockAgent; +using Amazon.BedrockAgent; namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; @@ -22,7 +22,7 @@ internal static class BedrockAgentStatusExtensions /// The maximum number of attempts to make. The default is 5 attempts. public static async Task WaitForAgentStatusAsync( this BedrockAgent agent, - AmazonBedrockAgent.AgentStatus status, + AgentStatus status, CancellationToken cancellationToken, int interval = 2, int maxAttempts = 5) diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs index fc0cd6c64860..95effc839664 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs @@ -2,8 +2,8 @@ using System; using System.Collections.Generic; -using AmazonBedrockAgentModel = Amazon.BedrockAgent.Model; -using AmazonBedrockAgentRuntimeModel = Amazon.BedrockAgentRuntime.Model; +using Amazon.BedrockAgent.Model; +using Amazon.BedrockAgentRuntime.Model; namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; @@ -12,7 +12,7 @@ namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; /// internal static class BedrockFunctionSchemaExtensions { - public static KernelArguments FromFunctionParameters(this List parameters, KernelArguments? arguments) + public static KernelArguments FromFunctionParameters(this List parameters, KernelArguments? arguments) { KernelArguments kernelArguments = arguments ?? []; foreach (var parameter in parameters) @@ -23,15 +23,15 @@ public static KernelArguments FromFunctionParameters(this List functions = []; + List functions = []; foreach (var plugin in plugins) { foreach (KernelFunction function in plugin) { - functions.Add(new AmazonBedrockAgentModel.Function + functions.Add(new Function { Name = function.Name, Description = function.Description, @@ -46,19 +46,19 @@ public static AmazonBedrockAgentModel.FunctionSchema ToFunctionSchema(this Kerne } } - return new AmazonBedrockAgentModel.FunctionSchema + return new Amazon.BedrockAgent.Model.FunctionSchema { Functions = functions, }; } - private static Dictionary CreateParameterSpec( + private static Dictionary CreateParameterSpec( this IReadOnlyList parameters) { - Dictionary parameterSpec = []; + Dictionary parameterSpec = []; foreach (var parameter in parameters) { - parameterSpec.Add(parameter.Name, new AmazonBedrockAgentModel.ParameterDetail + parameterSpec.Add(parameter.Name, new Amazon.BedrockAgent.Model.ParameterDetail { Description = parameter.Description, Required = parameter.IsRequired, diff --git a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs index b33cf32e8baa..65b2695e64b5 100644 --- a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs +++ b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs @@ -4,7 +4,7 @@ using Microsoft.SemanticKernel.Agents.Bedrock; /// -/// Base class for samples that demonstrate the usage of agents. +/// Base class for samples that demonstrate the usage of AWS Bedrock agents. /// public abstract class BaseBedrockAgentTest(ITestOutputHelper output) : BaseTest(output, redirectSystemConsoleOutput: true) { From 1476281a430798ecaef53efaff9076d8c7003473 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 11 Feb 2025 16:52:42 -0800 Subject: [PATCH 08/27] Bedrock agent trace --- .../BedrockAgent/Step01_BedrockAgent.cs | 8 ++ .../Step02_BedrockAgent_CodeInterpreter.cs | 5 ++ .../Step03_BedrockAgent_Functions.cs | 14 ++- .../BedrockAgent/Step04_BedrockAgent_Trace.cs | 85 +++++++++++++++++++ dotnet/src/Agents/Bedrock/BedrockAgent.cs | 68 ++++++++++++--- .../BedrockAgentInvokeExtensions.cs | 9 +- 6 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs index f8a0fb9987d1..f89844ac6adf 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs @@ -11,6 +11,10 @@ public class Step01_BedrockAgent(ITestOutputHelper output) : BaseBedrockAgentTes { private const string UserQuery = "Why is the sky blue in one sentence?"; + /// + /// Demonstrates how to create a new and interact with it. + /// The agent will respond to the user query. + /// [Fact] public async Task UseNewAgentAsync() { @@ -32,6 +36,10 @@ public async Task UseNewAgentAsync() } } + /// + /// Demonstrates how to create a new and interact with it using streaming. + /// The agent will respond to the user query. + /// [Fact] public async Task UseNewAgentStreamingAsync() { diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs index 26a8c7b40f28..69cc8aa6e530 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs @@ -18,6 +18,11 @@ Lion 3 Monkey 6 Dolphin 2"; + /// + /// Demonstrates how to create a new with code interpreter enabled and interact with it. + /// The agent will respond to the user query by creating a Python code that will be executed by the code interpreter. + /// The output of the code interpreter will be a file containing the bar chart, which will be returned to the user. + /// [Fact] public async Task UseAgentWithCodeInterpreterAsync() { diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs index ab4fda6b5855..b8cc04ff56d7 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs @@ -9,8 +9,12 @@ namespace GettingStarted.BedrockAgents; /// /// This example demonstrates how to interact with a with kernel functions. /// -public class Step02_BedrockAgent_Functions(ITestOutputHelper output) : BaseBedrockAgentTest(output) +public class Step03_BedrockAgent_Functions(ITestOutputHelper output) : BaseBedrockAgentTest(output) { + /// + /// Demonstrates how to create a new with kernel functions enabled and interact with it. + /// The agent will respond to the user query by calling kernel functions to provide weather information. + /// [Fact] public async Task UseAgentWithFunctionsAsync() { @@ -39,6 +43,10 @@ public async Task UseAgentWithFunctionsAsync() } } + /// + /// Demonstrates how to create a new with kernel functions enabled and interact with it using streaming. + /// The agent will respond to the user query by calling kernel functions to provide weather information. + /// [Fact] public async Task UseAgentStreamingWithFunctionsAsync() { @@ -67,6 +75,10 @@ public async Task UseAgentStreamingWithFunctionsAsync() } } + /// + /// Demonstrates how to create a new with kernel functions enabled and interact with it. + /// The agent will respond to the user query by calling multiple kernel functions in parallel to provide weather information. + /// [Fact] public async Task UseAgentWithParallelFunctionsAsync() { diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs new file mode 100644 index 000000000000..e59290a193f5 --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Amazon.BedrockAgentRuntime.Model; +using Microsoft.SemanticKernel.Agents.Bedrock; + +namespace GettingStarted.BedrockAgents; + +/// +/// This example demonstrates how to interact with a and inspect the agent's thought process. +/// To learn more about different traces available, see: +/// https://docs.aws.amazon.com/bedrock/latest/userguide/trace-events.html +/// +public class Step04_BedrockAgent_Trace(ITestOutputHelper output) : Step03_BedrockAgent_Functions(output) +{ + /// + /// Demonstrates how to inspect the thought process of a by enabling trace. + /// + [Fact] + public async Task UseAgentWithTraceAsync() + { + // Create the agent + var bedrock_agent = await this.CreateAgentAsync("Step04_BedrockAgent_Trace"); + + // Respond to user input + var userQuery = "What is the current weather in Seattle and what is the weather forecast in Seattle?"; + try + { + // Customize the request for advanced scenarios + InvokeAgentRequest invokeAgentRequest = new() + { + AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, + AgentId = bedrock_agent.Id, + SessionId = BedrockAgent.CreateSessionId(), + InputText = userQuery, + // Enable trace to inspect the agent's thought process + EnableTrace = true, + }; + + var responses = bedrock_agent.InvokeAsync(invokeAgentRequest, null, CancellationToken.None); + await foreach (var response in responses) + { + if (response.Content != null) + { + this.Output.WriteLine(response.Content); + } + else if (response.InnerContent is TracePart tracePart) + { + this.OutputTrace(tracePart.Trace); + } + } + } + finally + { + await bedrock_agent.DeleteAsync(CancellationToken.None); + } + } + + /// + /// Outputs the trace information to the console. + /// This only outputs the orchestration trace for demonstration purposes. + /// To learn more about different traces available, see: + /// https://docs.aws.amazon.com/bedrock/latest/userguide/trace-events.html + /// + private void OutputTrace(Trace trace) + { + if (trace.OrchestrationTrace is not null) + { + if (trace.OrchestrationTrace.ModelInvocationInput is not null) + { + this.Output.WriteLine("========== Orchestration trace =========="); + this.Output.WriteLine("Orchestration input:"); + this.Output.WriteLine(trace.OrchestrationTrace.ModelInvocationInput.Text); + } + if (trace.OrchestrationTrace.ModelInvocationOutput is not null) + { + this.Output.WriteLine("========== Orchestration trace =========="); + this.Output.WriteLine("Orchestration output:"); + this.Output.WriteLine(trace.OrchestrationTrace.ModelInvocationOutput.RawResponse.Content); + this.Output.WriteLine("Usage:"); + this.Output.WriteLine($"Input token: {trace.OrchestrationTrace.ModelInvocationOutput.Metadata.Usage.InputTokens}"); + this.Output.WriteLine($"Output token: {trace.OrchestrationTrace.ModelInvocationOutput.Metadata.Usage.OutputTokens}"); + } + } + } +} diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index c20cc65cf1fc..9f5bfc84ee6c 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -26,9 +26,11 @@ public class BedrockAgent : KernelAgent private readonly Amazon.BedrockAgent.Model.Agent _agentModel; - // There is a default alias created by Bedrock for the working draft version of the agent. - // https://docs.aws.amazon.com/bedrock/latest/userguide/agents-deploy.html - private const string WORKING_DRAFT_AGENT_ALIAS = "TSTALIASID"; + /// + /// There is a default alias created by Bedrock for the working draft version of the agent. + /// https://docs.aws.amazon.com/bedrock/latest/userguide/agents-deploy.html + /// + public static readonly string WorkingDraftAgentAlias = "TSTALIASID"; /// /// Initializes a new instance of the class. @@ -164,22 +166,40 @@ public async Task PrepareAsync(CancellationToken cancellationToken) /// The message to send to the agent. /// The arguments to use when invoking the agent. /// The to monitor for cancellation requests. The default is . + /// The alias id of the agent to use. The default is the working draft alias id. /// An of . public IAsyncEnumerable InvokeAsync( string sessionId, string message, KernelArguments? arguments, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + string? agentAliasId = null) { var invokeAgentRequest = new InvokeAgentRequest { - AgentAliasId = WORKING_DRAFT_AGENT_ALIAS, + AgentAliasId = agentAliasId ?? WorkingDraftAgentAlias, AgentId = this.Id, SessionId = sessionId, InputText = message, }; - return ActivityExtensions.RunWithActivityAsync( + return this.InvokeAsync(invokeAgentRequest, arguments, cancellationToken); + } + + /// + /// Invoke the Bedrock agent with the given request. Use this method when you want to customize the request. + /// + /// The request to send to the agent. + /// The arguments to use when invoking the agent. + /// The to monitor for cancellation requests. The default is . + public IAsyncEnumerable InvokeAsync( + InvokeAgentRequest invokeAgentRequest, + KernelArguments? arguments, + CancellationToken cancellationToken) + { + return invokeAgentRequest.StreamingConfigurations != null && invokeAgentRequest.StreamingConfigurations.StreamFinalResponse + ? throw new ArgumentException("The streaming configuration must be null for non-streaming responses.") + : ActivityExtensions.RunWithActivityAsync( () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), () => this.InternalInvokeAsync(invokeAgentRequest, arguments, cancellationToken), cancellationToken); @@ -191,16 +211,18 @@ public IAsyncEnumerable InvokeAsync( /// The session id. /// The message to send to the agent. /// The arguments to use when invoking the agent. /// The to monitor for cancellation requests. The default is . + /// The alias id of the agent to use. The default is the working draft alias id. /// An of . - public async IAsyncEnumerable InvokeStreamingAsync( + public IAsyncEnumerable InvokeStreamingAsync( string sessionId, string message, KernelArguments? arguments, - [EnumeratorCancellation] CancellationToken cancellationToken) + CancellationToken cancellationToken, + string? agentAliasId = null) { var invokeAgentRequest = new InvokeAgentRequest { - AgentAliasId = WORKING_DRAFT_AGENT_ALIAS, + AgentAliasId = agentAliasId ?? WorkingDraftAgentAlias, AgentId = this.Id, SessionId = sessionId, InputText = message, @@ -210,6 +232,33 @@ public async IAsyncEnumerable InvokeStreamingAsync( }, }; + return this.InvokeStreamingAsync(invokeAgentRequest, arguments, cancellationToken); + } + + /// + /// Invoke the Bedrock agent with the given request and streaming response. Use this method when you want to customize the request. + /// + /// The request to send to the agent. + /// The arguments to use when invoking the agent. + /// The to monitor for cancellation requests. The default is . + /// An of . + public async IAsyncEnumerable InvokeStreamingAsync( + InvokeAgentRequest invokeAgentRequest, + KernelArguments? arguments, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + if (invokeAgentRequest.StreamingConfigurations == null) + { + invokeAgentRequest.StreamingConfigurations = new() + { + StreamFinalResponse = true, + }; + } + else if (!invokeAgentRequest.StreamingConfigurations.StreamFinalResponse) + { + throw new ArgumentException("The streaming configuration must have StreamFinalResponse set to true."); + } + // The Bedrock agent service has the same API for both streaming and non-streaming responses. // We are invoking the same method as the non-streaming response with the streaming configuration set, // and converting the chat message content to streaming chat message content. @@ -287,7 +336,6 @@ protected override Task RestoreChannelAsync(string channelState, C internal AmazonBedrockAgentClient GetClient() => this._client; internal AmazonBedrockAgentRuntimeClient GetRuntimeClient() => this._runtimeClient; internal Amazon.BedrockAgent.Model.Agent GetAgentModel() => this._agentModel; - internal string GetCodeInterpreterActionGroupSignature() => $"{this.GetDisplayName()}_CodeInterpreter"; internal string GetKernelFunctionActionGroupSignature() => $"{this.GetDisplayName()}_KernelFunctions"; } diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs index 0c8c126ea130..1680440354df 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs @@ -92,7 +92,7 @@ public static async IAsyncEnumerable InternalInvokeAsync( AuthorName = agent.GetDisplayName(), Content = Encoding.UTF8.GetString(payload.Bytes.ToArray()), ModelId = agent.GetAgentModel().FoundationModel, - InnerContent = responseEvent, + InnerContent = payload, }; } @@ -123,7 +123,7 @@ public static async IAsyncEnumerable InternalInvokeAsync( AuthorName = agent.GetDisplayName(), Items = binaryContents, ModelId = agent.GetAgentModel().FoundationModel, - InnerContent = responseEvent, + InnerContent = files, }; } @@ -160,7 +160,7 @@ public static async IAsyncEnumerable InternalInvokeAsync( AuthorName = agent.GetDisplayName(), Items = functionCallContents, ModelId = agent.GetAgentModel().FoundationModel, - InnerContent = responseEvent, + InnerContent = returnControlPayload, }; } @@ -174,9 +174,8 @@ public static async IAsyncEnumerable InternalInvokeAsync( { Role = AuthorRole.Assistant, AuthorName = agent.GetDisplayName(), - Content = "Trace received", ModelId = agent.GetAgentModel().FoundationModel, - InnerContent = responseEvent, + InnerContent = trace, }; } From 2695ddf98dbeabe7a9ed94bb7a8ab62c62e38a28 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 11 Feb 2025 16:55:57 -0800 Subject: [PATCH 09/27] Example output --- .../BedrockAgent/Step04_BedrockAgent_Trace.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs index e59290a193f5..80de8eade70a 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs @@ -81,5 +81,52 @@ private void OutputTrace(Trace trace) this.Output.WriteLine($"Output token: {trace.OrchestrationTrace.ModelInvocationOutput.Metadata.Usage.OutputTokens}"); } } + // Example output: + // ========== Orchestration trace ========== + // Orchestration input: + // {"system":"You're a helpful assistant who helps users find information.You have been provided with a set of functions to answer ... + // ========== Orchestration trace ========== + // Orchestration output: + // + // To answer this question, I will need to call the following functions: + // 1. Step04_BedrockAgent_Trace_KernelFunctions::Current to get the current weather in Seattle + // 2. Step04_BedrockAgent_Trace_KernelFunctions::Forecast to get the weather forecast in Seattle + // + // + // + // + // Step04_BedrockAgent_Trace_KernelFunctions::Current + // + // Seattle + // + // Usage: + // Input token: 617 + // Output token: 144 + // ========== Orchestration trace ========== + // Orchestration input: + // {"system":"You're a helpful assistant who helps users find information.You have been provided with a set of functions to answer ... + // ========== Orchestration trace ========== + // Orchestration output: + // Now that I have the current weather in Seattle, I will call the forecast function to get the weather forecast. + // + // + // + // Step04_BedrockAgent_Trace_KernelFunctions::Forecast + // + // Seattle + // + // Usage: + // Input token: 834 + // Output token: 87 + // ========== Orchestration trace ========== + // Orchestration input: + // {"system":"You're a helpful assistant who helps users find information.You have been provided with a set of functions to answer ... + // ========== Orchestration trace ========== + // Orchestration output: + // + // The current weather in Seattle is 72 degrees. The weather forecast for Seattle is 75 degrees tomorrow. + // Usage: + // Input token: 1003 + // Output token: 31 } } From 47993213357c79d0f04a84018ccb534ff062c7fd Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 12 Feb 2025 11:26:19 -0800 Subject: [PATCH 10/27] Knowledge base --- .../Step04_AzureAIAgent_FileSearch.cs | 2 +- .../Step05_BedrockAgent_FileSearch.cs | 74 ++++++++++++++++ .../Step04_AssistantTool_FileSearch.cs | 2 +- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 86 +++++++++++++++++++ 4 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs diff --git a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step04_AzureAIAgent_FileSearch.cs b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step04_AzureAIAgent_FileSearch.cs index 3aa44e5dab3a..b0fe78b54968 100644 --- a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step04_AzureAIAgent_FileSearch.cs +++ b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step04_AzureAIAgent_FileSearch.cs @@ -9,7 +9,7 @@ namespace GettingStarted.AzureAgents; /// -/// Demonstrate using code-interpreter on . +/// Demonstrate using with file search. /// public class Step04_AzureAIAgent_FileSearch(ITestOutputHelper output) : BaseAgentsTest(output) { diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs new file mode 100644 index 000000000000..8a3c5049ac0e --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Amazon.BedrockAgentRuntime.Model; +using Microsoft.SemanticKernel.Agents.Bedrock; + +namespace GettingStarted.BedrockAgents; + +/// +/// This example demonstrates how to interact with a that is associated with a knowledge base. +/// A Bedrock Knowledge Base is a collection of documents that the agent uses to answer user queries. +/// To learn more about Bedrock Knowledge Base, see: +/// https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html +/// +public class Step05_BedrockAgent_FileSearch(ITestOutputHelper output) : BaseBedrockAgentTest(output) +{ + // Replace the KnowledgeBaseId with a valid KnowledgeBaseId + // To learn how to create a Knowledge Base, see: + // https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-create.html + private const string KnowledgeBaseId = "[KnowledgeBaseId]"; + + protected override async Task CreateAgentAsync(string agentName) + { + var bedrockAgent = await BedrockAgent.CreateAsync(this.GetCreateAgentRequest(agentName)); + // Associate the agent with a knowledge base + await bedrockAgent.AssociateAgentKnowledgeBaseAsync( + KnowledgeBaseId, + "You will find information here.", + CancellationToken.None); + + return bedrockAgent; + } + + /// + /// Demonstrates how to inspect the thought process of a by enabling trace. + /// + // [Fact(Skip = "This test is skipped because it requires a valid KnowledgeBaseId.")] + [Fact] + public async Task UseAgentWithFileSearchAsync() + { + // Create the agent + var bedrock_agent = await this.CreateAgentAsync("Step05_BedrockAgent_FileSearch"); + + // Respond to user input + // Assuming the knowledge base contains information about Semantic Kernel. + // Feel free to modify the user query according to the information in your knowledge base. + var userQuery = "What is Semantic Kernel?"; + try + { + // Customize the request for advanced scenarios + InvokeAgentRequest invokeAgentRequest = new() + { + AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, + AgentId = bedrock_agent.Id, + SessionId = BedrockAgent.CreateSessionId(), + InputText = userQuery, + // Enable trace to inspect the agent's thought process + EnableTrace = true, + }; + + var responses = bedrock_agent.InvokeAsync(invokeAgentRequest, null, CancellationToken.None); + await foreach (var response in responses) + { + if (response.Content != null) + { + this.Output.WriteLine(response.Content); + } + } + } + finally + { + await bedrock_agent.DeleteAsync(CancellationToken.None); + } + } +} diff --git a/dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step04_AssistantTool_FileSearch.cs b/dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step04_AssistantTool_FileSearch.cs index 7eb85de97ce4..b3638be05d52 100644 --- a/dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step04_AssistantTool_FileSearch.cs +++ b/dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step04_AssistantTool_FileSearch.cs @@ -10,7 +10,7 @@ namespace GettingStarted.OpenAIAssistants; /// -/// Demonstrate using code-interpreter on . +/// Demonstrate using with file search. /// public class Step04_AssistantTool_FileSearch(ITestOutputHelper output) : BaseAgentsTest(output) { diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index 9f5bfc84ee6c..17a1f0001639 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -53,12 +53,15 @@ public BedrockAgent( this.Instructions = agentModel.Instruction; } + #region static methods + /// /// Create a Bedrock agent on the service. /// /// The request to create the agent. /// Whether to enable the code interpreter for the agent. /// Whether to enable kernel functions for the agent. + /// Whether to enable user input for the agent. /// The client to use. /// The runtime client to use. /// A kernel instance for the agent to use. @@ -69,6 +72,7 @@ public static async Task CreateAsync( CreateAgentRequest request, bool enableCodeInterpreter = false, bool enableKernelFunctions = false, + bool enableUserInput = false, AmazonBedrockAgentClient? client = null, AmazonBedrockAgentRuntimeClient? runtimeClient = null, Kernel? kernel = null, @@ -97,6 +101,10 @@ public static async Task CreateAsync( { await agent.CreateKernelFunctionActionGroupAsync(cancellationToken).ConfigureAwait(false); } + if (enableUserInput) + { + await agent.EnableUserInputActionGroupAsync(cancellationToken).ConfigureAwait(false); + } // Need to prepare the agent before it can be invoked. await agent.PrepareAsync(cancellationToken).ConfigureAwait(false); @@ -141,6 +149,10 @@ public static string CreateSessionId() return Guid.NewGuid().ToString(); } + #endregion + + # region public methods + /// /// Delete the Bedrock agent from the service. /// @@ -277,6 +289,56 @@ public async IAsyncEnumerable InvokeStreamingAsync( } } + /// + /// Associate the agent with a knowledge base. + /// + /// The id of the knowledge base to associate with the agent. + /// A description of what the agent should use the knowledge base for. + /// The to monitor for cancellation requests. The default is . + public async Task AssociateAgentKnowledgeBaseAsync(string knowledgeBaseId, string description, CancellationToken cancellationToken) + { + await this._client.AssociateAgentKnowledgeBaseAsync(new() + { + AgentId = this.Id, + AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", + KnowledgeBaseId = knowledgeBaseId, + Description = description, + }, cancellationToken).ConfigureAwait(false); + } + + /// + /// Disassociate the agent with a knowledge base. + /// + /// The id of the knowledge base to disassociate with the agent. + /// The to monitor for cancellation requests. The default is . + public async Task DisassociateAgentKnowledgeBaseAsync(string knowledgeBaseId, CancellationToken cancellationToken) + { + await this._client.DisassociateAgentKnowledgeBaseAsync(new() + { + AgentId = this.Id, + AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", + KnowledgeBaseId = knowledgeBaseId, + }, cancellationToken).ConfigureAwait(false); + } + + /// + /// List the knowledge bases associated with the agent. + /// + /// The to monitor for cancellation requests. The default is . + /// A containing the knowledge bases associated with the agent. + public async Task ListAssociatedKnowledgeBasesAsync(CancellationToken cancellationToken) + { + return await this._client.ListAgentKnowledgeBasesAsync(new() + { + AgentId = this.Id, + AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", + }, cancellationToken).ConfigureAwait(false); + } + + #endregion + + #region private methods + /// /// Create a code interpreter action group for the agent. /// @@ -315,6 +377,25 @@ private async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancel await this._client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); } + /// + /// Enable user input for the agent. + /// + private async Task EnableUserInputActionGroupAsync(CancellationToken cancellationToken) + { + var createAgentActionGroupRequest = new CreateAgentActionGroupRequest + { + AgentId = this.Id, + AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", + ActionGroupName = this.GetUseInputActionGroupSignature(), + ActionGroupState = ActionGroupState.ENABLED, + ParentActionGroupSignature = new(Amazon.BedrockAgent.ActionGroupSignature.AMAZONUserInput), + }; + + await this._client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + } + + #endregion + protected override IEnumerable GetChannelKeys() { // Return the channel keys for the BedrockAgent @@ -333,9 +414,14 @@ protected override Task RestoreChannelAsync(string channelState, C return Task.FromResult(new BedrockAgentChannel()); } + #region internal methods + internal AmazonBedrockAgentClient GetClient() => this._client; internal AmazonBedrockAgentRuntimeClient GetRuntimeClient() => this._runtimeClient; internal Amazon.BedrockAgent.Model.Agent GetAgentModel() => this._agentModel; internal string GetCodeInterpreterActionGroupSignature() => $"{this.GetDisplayName()}_CodeInterpreter"; internal string GetKernelFunctionActionGroupSignature() => $"{this.GetDisplayName()}_KernelFunctions"; + internal string GetUseInputActionGroupSignature() => $"{this.GetDisplayName()}_UserInput"; + + #endregion } From d15fc299bfea7f731839bbaed048f64e7c649cfc Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 12 Feb 2025 15:41:10 -0800 Subject: [PATCH 11/27] Bedrock agent channel --- .../BedrockAgent/Step01_BedrockAgent.cs | 12 +- .../Step02_BedrockAgent_CodeInterpreter.cs | 6 +- .../Step03_BedrockAgent_Functions.cs | 18 +- .../BedrockAgent/Step04_BedrockAgent_Trace.cs | 8 +- .../Step05_BedrockAgent_FileSearch.cs | 15 +- .../Step06_BedrockAgent_AgentChat.cs | 88 ++++++++ dotnet/src/Agents/Bedrock/BedrockAgent.cs | 3 + .../src/Agents/Bedrock/BedrockAgentChannel.cs | 210 ++++++++++++++++-- 8 files changed, 313 insertions(+), 47 deletions(-) create mode 100644 dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs index f89844ac6adf..5098f4af5411 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs @@ -19,12 +19,12 @@ public class Step01_BedrockAgent(ITestOutputHelper output) : BaseBedrockAgentTes public async Task UseNewAgentAsync() { // Create the agent - var bedrock_agent = await this.CreateAgentAsync("Step01_BedrockAgent"); + var bedrockAgent = await this.CreateAgentAsync("Step01_BedrockAgent"); // Respond to user input try { - var responses = bedrock_agent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + var responses = bedrockAgent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); await foreach (var response in responses) { this.Output.WriteLine(response.Content); @@ -32,7 +32,7 @@ public async Task UseNewAgentAsync() } finally { - await bedrock_agent.DeleteAsync(CancellationToken.None); + await bedrockAgent.DeleteAsync(CancellationToken.None); } } @@ -44,12 +44,12 @@ public async Task UseNewAgentAsync() public async Task UseNewAgentStreamingAsync() { // Create the agent - var bedrock_agent = await this.CreateAgentAsync("Step01_BedrockAgent_Streaming"); + var bedrockAgent = await this.CreateAgentAsync("Step01_BedrockAgent_Streaming"); // Respond to user input try { - var streamingResponses = bedrock_agent.InvokeStreamingAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + var streamingResponses = bedrockAgent.InvokeStreamingAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); await foreach (var response in streamingResponses) { this.Output.WriteLine(response.Content); @@ -57,7 +57,7 @@ public async Task UseNewAgentStreamingAsync() } finally { - await bedrock_agent.DeleteAsync(CancellationToken.None); + await bedrockAgent.DeleteAsync(CancellationToken.None); } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs index 69cc8aa6e530..03598f21f493 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs @@ -27,13 +27,13 @@ Monkey 6 public async Task UseAgentWithCodeInterpreterAsync() { // Create the agent - var bedrock_agent = await this.CreateAgentAsync("Step02_BedrockAgent_CodeInterpreter"); + var bedrockAgent = await this.CreateAgentAsync("Step02_BedrockAgent_CodeInterpreter"); // Respond to user input try { BinaryContent? binaryContent = null; - var responses = bedrock_agent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + var responses = bedrockAgent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); await foreach (var response in responses) { if (response.Content != null) @@ -70,7 +70,7 @@ public async Task UseAgentWithCodeInterpreterAsync() } finally { - await bedrock_agent.DeleteAsync(CancellationToken.None); + await bedrockAgent.DeleteAsync(CancellationToken.None); } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs index b8cc04ff56d7..7f750091d3be 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs @@ -19,12 +19,12 @@ public class Step03_BedrockAgent_Functions(ITestOutputHelper output) : BaseBedro public async Task UseAgentWithFunctionsAsync() { // Create the agent - var bedrock_agent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions"); + var bedrockAgent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions"); // Respond to user input try { - var responses = bedrock_agent.InvokeAsync( + var responses = bedrockAgent.InvokeAsync( BedrockAgent.CreateSessionId(), "What is the weather in Seattle?", null, @@ -39,7 +39,7 @@ public async Task UseAgentWithFunctionsAsync() } finally { - await bedrock_agent.DeleteAsync(CancellationToken.None); + await bedrockAgent.DeleteAsync(CancellationToken.None); } } @@ -51,12 +51,12 @@ public async Task UseAgentWithFunctionsAsync() public async Task UseAgentStreamingWithFunctionsAsync() { // Create the agent - var bedrock_agent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions_Streaming"); + var bedrockAgent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions_Streaming"); // Respond to user input try { - var streamingResponses = bedrock_agent.InvokeStreamingAsync( + var streamingResponses = bedrockAgent.InvokeStreamingAsync( BedrockAgent.CreateSessionId(), "What is the weather forecast in Seattle?", null, @@ -71,7 +71,7 @@ public async Task UseAgentStreamingWithFunctionsAsync() } finally { - await bedrock_agent.DeleteAsync(CancellationToken.None); + await bedrockAgent.DeleteAsync(CancellationToken.None); } } @@ -83,12 +83,12 @@ public async Task UseAgentStreamingWithFunctionsAsync() public async Task UseAgentWithParallelFunctionsAsync() { // Create the agent - var bedrock_agent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions_Parallel"); + var bedrockAgent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions_Parallel"); // Respond to user input try { - var responses = bedrock_agent.InvokeAsync( + var responses = bedrockAgent.InvokeAsync( BedrockAgent.CreateSessionId(), "What is the current weather in Seattle and what is the weather forecast in Seattle?", null, @@ -103,7 +103,7 @@ public async Task UseAgentWithParallelFunctionsAsync() } finally { - await bedrock_agent.DeleteAsync(CancellationToken.None); + await bedrockAgent.DeleteAsync(CancellationToken.None); } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs index 80de8eade70a..156fc85b113b 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs @@ -19,7 +19,7 @@ public class Step04_BedrockAgent_Trace(ITestOutputHelper output) : Step03_Bedroc public async Task UseAgentWithTraceAsync() { // Create the agent - var bedrock_agent = await this.CreateAgentAsync("Step04_BedrockAgent_Trace"); + var bedrockAgent = await this.CreateAgentAsync("Step04_BedrockAgent_Trace"); // Respond to user input var userQuery = "What is the current weather in Seattle and what is the weather forecast in Seattle?"; @@ -29,14 +29,14 @@ public async Task UseAgentWithTraceAsync() InvokeAgentRequest invokeAgentRequest = new() { AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, - AgentId = bedrock_agent.Id, + AgentId = bedrockAgent.Id, SessionId = BedrockAgent.CreateSessionId(), InputText = userQuery, // Enable trace to inspect the agent's thought process EnableTrace = true, }; - var responses = bedrock_agent.InvokeAsync(invokeAgentRequest, null, CancellationToken.None); + var responses = bedrockAgent.InvokeAsync(invokeAgentRequest, null, CancellationToken.None); await foreach (var response in responses) { if (response.Content != null) @@ -51,7 +51,7 @@ public async Task UseAgentWithTraceAsync() } finally { - await bedrock_agent.DeleteAsync(CancellationToken.None); + await bedrockAgent.DeleteAsync(CancellationToken.None); } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs index 8a3c5049ac0e..f0c81adf3ccc 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs @@ -31,14 +31,13 @@ await bedrockAgent.AssociateAgentKnowledgeBaseAsync( } /// - /// Demonstrates how to inspect the thought process of a by enabling trace. + /// Demonstrates how to use a with file search. /// - // [Fact(Skip = "This test is skipped because it requires a valid KnowledgeBaseId.")] - [Fact] + [Fact(Skip = "This test is skipped because it requires a valid KnowledgeBaseId.")] public async Task UseAgentWithFileSearchAsync() { // Create the agent - var bedrock_agent = await this.CreateAgentAsync("Step05_BedrockAgent_FileSearch"); + var bedrockAgent = await this.CreateAgentAsync("Step05_BedrockAgent_FileSearch"); // Respond to user input // Assuming the knowledge base contains information about Semantic Kernel. @@ -50,14 +49,12 @@ public async Task UseAgentWithFileSearchAsync() InvokeAgentRequest invokeAgentRequest = new() { AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, - AgentId = bedrock_agent.Id, + AgentId = bedrockAgent.Id, SessionId = BedrockAgent.CreateSessionId(), InputText = userQuery, - // Enable trace to inspect the agent's thought process - EnableTrace = true, }; - var responses = bedrock_agent.InvokeAsync(invokeAgentRequest, null, CancellationToken.None); + var responses = bedrockAgent.InvokeAsync(invokeAgentRequest, null, CancellationToken.None); await foreach (var response in responses) { if (response.Content != null) @@ -68,7 +65,7 @@ public async Task UseAgentWithFileSearchAsync() } finally { - await bedrock_agent.DeleteAsync(CancellationToken.None); + await bedrockAgent.DeleteAsync(CancellationToken.None); } } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs new file mode 100644 index 000000000000..b4a29f942423 --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.Bedrock; +using Microsoft.SemanticKernel.Agents.Chat; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace GettingStarted.BedrockAgents; + +/// +/// This example demonstrates how two agents (one of which is a Bedrock agent) can chat with each other. +/// +public class Step06_BedrockAgent_AgentChat(ITestOutputHelper output) : BaseBedrockAgentTest(output) +{ + protected override async Task CreateAgentAsync(string agentName) + { + return await BedrockAgent.CreateAsync(this.GetCreateAgentRequest(agentName)); + } + + /// + /// Demonstrates how to put two instances in a chat. + /// + [Fact] + public async Task UseAgentWithAgentChatAsync() + { + // Create the agent + var bedrockAgent = await this.CreateAgentAsync("Step06_BedrockAgent_AgentChat"); + var chatCompletionAgent = new ChatCompletionAgent() + { + Instructions = "You're a translator who helps users understand the content in Spanish.", + Name = "Translator", + Kernel = this.CreateKernelWithChatCompletion(), + }; + + // Create a chat for agent interaction + var chat = new AgentGroupChat(bedrockAgent, chatCompletionAgent) + { + ExecutionSettings = new() + { + // Terminate after two turns: one from the bedrock agent and one from the chat completion agent. + // Note: each invoke will terminate after two turns, and we are invoking the group chat for each user query. + TerminationStrategy = new MultiTurnTerminationStrategy(2), + } + }; + + // Respond to user input + string[] userQueries = [ + "Why is the sky blue in one sentence?", + "Why do we have seasons in one sentence?" + ]; + try + { + foreach (var userQuery in userQueries) + { + chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, userQuery)); + await foreach (var response in chat.InvokeAsync()) + { + if (response.Content != null) + { + this.Output.WriteLine($"[{response.AuthorName}]: {response.Content}"); + } + } + } + } + finally + { + await bedrockAgent.DeleteAsync(CancellationToken.None); + } + } + + internal sealed class MultiTurnTerminationStrategy : TerminationStrategy + { + public MultiTurnTerminationStrategy(int turns) + { + this.MaximumIterations = turns; + } + + /// + protected override Task ShouldAgentTerminateAsync( + Agent agent, + IReadOnlyList history, + CancellationToken cancellationToken = default) + { + return Task.FromResult(false); + } + } +} diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index 17a1f0001639..06eac4ca081c 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -396,18 +396,21 @@ private async Task EnableUserInputActionGroupAsync(CancellationToken cancellatio #endregion + /// protected override IEnumerable GetChannelKeys() { // Return the channel keys for the BedrockAgent yield return typeof(BedrockAgentChannel).FullName!; } + /// protected override Task CreateChannelAsync(CancellationToken cancellationToken) { // Create and return a new BedrockAgentChannel return Task.FromResult(new BedrockAgentChannel()); } + /// protected override Task RestoreChannelAsync(string channelState, CancellationToken cancellationToken) { // Restore and return a BedrockAgentChannel from the given state diff --git a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs index cd794ca99e59..ce6c0ab4922e 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs @@ -1,52 +1,230 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Amazon.BedrockAgentRuntime.Model; +using Microsoft.SemanticKernel.Agents.Extensions; +using Microsoft.SemanticKernel.Agents.Serialization; +using Microsoft.SemanticKernel.ChatCompletion; namespace Microsoft.SemanticKernel.Agents.Bedrock; +/// +/// A specialization for use with . +/// public class BedrockAgentChannel : AgentChannel { - public BedrockAgentChannel() - { - // Initialize the BedrockAgentChannel - } + private readonly ChatHistory _history = []; + + private const string MessagePlaceholder = "[SILENCE]"; + /// + /// Receive messages from a group chat. + /// Bedrock requires the chat history to alternate between user and agent messages. + /// Thus, when receiving messages, the message sequence will be mutated by inserting + /// placeholder agent or user messages as needed. + /// + /// The history of messages to receive. + /// A token to monitor for cancellation requests. protected override Task ReceiveAsync(IEnumerable history, CancellationToken cancellationToken) { - // Implement the logic to receive messages from the Bedrock service + foreach (var incomingMessage in history) + { + if (incomingMessage.Content is null) + { + throw new InvalidOperationException("Message content cannot be null."); + } + + if (this._history.Count == 0 || this._history.Last().Role != incomingMessage.Role) + { + this._history.Add(incomingMessage); + } + else + { + this._history.Add + ( + new ChatMessageContent + ( + incomingMessage.Role == AuthorRole.Assistant ? AuthorRole.User : AuthorRole.Assistant, + MessagePlaceholder + ) + ); + this._history.Add(incomingMessage); + } + } + return Task.CompletedTask; } - protected override IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync(BedrockAgent agent, CancellationToken cancellationToken) + /// + protected override IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync( + BedrockAgent agent, + CancellationToken cancellationToken) { - // Implement the logic to invoke the Bedrock service - return AsyncEnumerable.Empty<(bool IsVisible, ChatMessageContent Message)>(); + return this._history.Count == 0 ? throw new InvalidOperationException("No messages to send.") : InvokeInternalAsync(); + + async IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeInternalAsync() + { + this.EnsureHistoryAlternates(); + this.EnsureLastMessageIsUser(); + InvokeAgentRequest invokeAgentRequest = new() + { + AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, + AgentId = agent.Id, + SessionId = BedrockAgent.CreateSessionId(), + InputText = this._history.Last().Content ?? throw new InvalidOperationException("Message content cannot be null."), + SessionState = this.ParseHistoryToSessionState(), + }; + await foreach (var message in agent.InvokeAsync(invokeAgentRequest, null, cancellationToken).ConfigureAwait(false)) + { + if (message.Content is not null) + { + this._history.Add(message); + // All messages from Bedrock agents are user facing, i.e., function calls are not returned as messages + yield return (true, message); + } + } + } } - protected override IAsyncEnumerable InvokeStreamingAsync(BedrockAgent agent, IList messages, CancellationToken cancellationToken) + /// + protected override IAsyncEnumerable InvokeStreamingAsync( + BedrockAgent agent, + IList messages, + CancellationToken cancellationToken) { - // Implement the logic to invoke the Bedrock service with streaming results - return AsyncEnumerable.Empty(); + return this._history.Count == 0 ? throw new InvalidOperationException("No messages to send.") : InvokeInternalAsync(); + + async IAsyncEnumerable InvokeInternalAsync() + { + this.EnsureHistoryAlternates(); + this.EnsureLastMessageIsUser(); + InvokeAgentRequest invokeAgentRequest = new() + { + AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, + AgentId = agent.Id, + SessionId = BedrockAgent.CreateSessionId(), + InputText = this._history.Last().Content ?? throw new InvalidOperationException("Message content cannot be null."), + SessionState = this.ParseHistoryToSessionState(), + }; + await foreach (var message in agent.InvokeStreamingAsync(invokeAgentRequest, null, cancellationToken).ConfigureAwait(false)) + { + if (message.Content is not null) + { + this._history.Add(new() + { + Role = AuthorRole.Assistant, + Content = message.Content, + AuthorName = message.AuthorName, + InnerContent = message.InnerContent, + ModelId = message.ModelId, + }); + // All messages from Bedrock agents are user facing, i.e., function calls are not returned as messages + yield return message; + } + } + } } + /// protected override IAsyncEnumerable GetHistoryAsync(CancellationToken cancellationToken) { - // Implement the logic to retrieve the message history from the Bedrock service - return AsyncEnumerable.Empty(); + return this._history.ToDescendingAsync(); } + /// protected override Task ResetAsync(CancellationToken cancellationToken) { - // Implement the logic to reset the BedrockAgentChannel + this._history.Clear(); + return Task.CompletedTask; } + /// protected override string Serialize() + => JsonSerializer.Serialize(ChatMessageReference.Prepare(this._history)); + + #region private methods + + private void EnsureHistoryAlternates() { - // Implement the logic to serialize the BedrockAgentChannel state - return string.Empty; + if (this._history.Count <= 1) + { + return; + } + + int currentIndex = 1; + while (currentIndex < this._history.Count) + { + if (this._history[currentIndex].Role == this._history[currentIndex - 1].Role) + { + this._history.Insert( + currentIndex, + new ChatMessageContent( + this._history[currentIndex].Role == AuthorRole.Assistant ? AuthorRole.User : AuthorRole.Assistant, + MessagePlaceholder + ) + ); + currentIndex += 2; + } + else + { + currentIndex++; + } + } + } + + private void EnsureLastMessageIsUser() + { + if (this._history.Count > 0 && this._history.Last().Role != AuthorRole.User) + { + this._history.Add(new ChatMessageContent(AuthorRole.User, MessagePlaceholder)); + } + } + + private SessionState ParseHistoryToSessionState() + { + SessionState sessionState = new(); + + // We don't take the last message as it needs to be sent separately in another parameter. + if (this._history.Count > 1) + { + sessionState.ConversationHistory = new() + { + Messages = [] + }; + + foreach (var message in this._history.Take(this._history.Count - 1)) + { + if (message.Content is null) + { + throw new InvalidOperationException("Message content cannot be null."); + } + if (message.Role != AuthorRole.Assistant && message.Role != AuthorRole.User) + { + throw new InvalidOperationException("Message role must be either Assistant or User."); + } + + sessionState.ConversationHistory.Messages.Add(new() + { + Role = message.Role == AuthorRole.Assistant + ? Amazon.BedrockAgentRuntime.ConversationRole.Assistant + : Amazon.BedrockAgentRuntime.ConversationRole.User, + Content = [ + new Amazon.BedrockAgentRuntime.Model.ContentBlock() + { + Text = message.Content, + }, + ], + }); + } + } + + return sessionState; } + #endregion } From 9f848379ef47f4a016b10a2306ff4bcb7f20c9aa Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 12 Feb 2025 15:56:20 -0800 Subject: [PATCH 12/27] Remove unit test files --- .../BedrockAgent/Step04_BedrockAgent_Trace.cs | 2 +- .../BedrockAgent/Step06_BedrockAgent_AgentChat.cs | 2 +- dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj | 2 +- dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj | 1 - .../Agents/UnitTests/Bedrock/BedrockAgentTests.cs | 12 ------------ 5 files changed, 3 insertions(+), 16 deletions(-) delete mode 100644 dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs index 156fc85b113b..71a916c43908 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using Amazon.BedrockAgentRuntime.Model; using Microsoft.SemanticKernel.Agents.Bedrock; diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs index b4a29f942423..fd09c3a471c7 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; diff --git a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj index 085d2726c26b..6a74513ece7e 100644 --- a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj +++ b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj @@ -5,7 +5,7 @@ Microsoft.SemanticKernel.Agents.Bedrock Microsoft.SemanticKernel.Agents.Bedrock net8.0;netstandard2.0 - $(NoWarn);SKEXP0110 + $(NoWarn);SKEXP0110;CA1724 false alpha diff --git a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj index e28d01551ba7..7ac69a6c0a14 100644 --- a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj +++ b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj @@ -34,7 +34,6 @@ - diff --git a/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs b/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs deleted file mode 100644 index 4041a765994b..000000000000 --- a/dotnet/src/Agents/UnitTests/Bedrock/BedrockAgentTests.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Agents.Bedrock; -using Xunit; - -namespace SemanticKernel.Agents.UnitTests.Bedrock -{ - public class BedrockAgentTests - { - - } -} From 2a58212fffb8ea406dee72e1a8f3d8674b1fbabc Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 12 Feb 2025 16:01:41 -0800 Subject: [PATCH 13/27] Fix input ordering --- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index 06eac4ca081c..c44a2410f195 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -6,12 +6,12 @@ using System.Threading; using System.Threading.Tasks; using Amazon.BedrockAgent; -using Amazon.BedrockAgentRuntime; using Amazon.BedrockAgent.Model; +using Amazon.BedrockAgentRuntime; using Amazon.BedrockAgentRuntime.Model; +using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; using Microsoft.SemanticKernel.Agents.Extensions; using Microsoft.SemanticKernel.Diagnostics; -using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; namespace Microsoft.SemanticKernel.Agents.Bedrock; From be3be0fb939a74008e643c5a876c4982f4b49a0d Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 12 Feb 2025 16:10:14 -0800 Subject: [PATCH 14/27] Remove import --- dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs b/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs index 9dfe17744192..c7b1de285754 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs @@ -6,7 +6,6 @@ using System.IO; using System.Text; using System.Text.Json.Serialization; -using System.Threading.Tasks; using Microsoft.SemanticKernel.Text; #pragma warning disable CA1056 // URI-like properties should not be strings From dc114eb0e22bad3b2a983e284743c5e280dfe181 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 12 Feb 2025 16:22:52 -0800 Subject: [PATCH 15/27] Add project reference to fix build --- dotnet/samples/Concepts/Concepts.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index 17335b615a19..1b9957e54b61 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -56,6 +56,7 @@ + From 204fa907a091cfcf415cefcbd49afc4a7632751e Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 12 Feb 2025 17:02:18 -0800 Subject: [PATCH 16/27] READMEs --- .../BedrockAgent/README.md | 38 +++++++++++++++++++ .../Step03_BedrockAgent_Functions.cs | 1 - .../GettingStartedWithAgents/README.md | 27 +++++++++++-- .../BedrockAgentInvokeExtensions.cs | 23 +++++------ dotnet/src/Agents/Bedrock/README.md | 27 +++++++++++++ .../concepts/agents/bedrock_agent/README.md | 4 +- 6 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 dotnet/samples/GettingStartedWithAgents/BedrockAgent/README.md create mode 100644 dotnet/src/Agents/Bedrock/README.md diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/README.md b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/README.md new file mode 100644 index 000000000000..815bcf23f845 --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/README.md @@ -0,0 +1,38 @@ +# Concept samples on how to use AWS Bedrock agents + +## Pre-requisites + +1. You need to have an AWS account and [access to the foundation models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-permissions.html) +2. [AWS CLI installed](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) and [configured](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html#configuration) + +## Before running the samples + +You need to set up some user secrets run the samples. + +### `BedrockAgent:AgentResourceRoleArn` + +On your AWS console, go to the IAM service and go to **Roles**. Find the role you want to use and click on it. You will find the ARN in the summary section. + +``` +dotnet user-secrets set "BedrockAgent:AgentResourceRoleArn" "arn:aws:iam::...:role/..." +``` + +### `BedrockAgent:FoundationModel` + +You need to make sure you have permission to access the foundation model. You can find the model ID in the [AWS documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html). To see the models you have access to, find the policy attached to your role you should see a list of models you have access to under the `Resource` section. + +``` +dotnet user-secrets set "BedrockAgent:FoundationModel" "..." +``` + +### How to add the `bedrock:InvokeModelWithResponseStream` action to an IAM policy + +1. Open the [IAM console](https://console.aws.amazon.com/iam/). +2. On the left navigation pane, choose `Roles` under `Access management`. +3. Find the role you want to edit and click on it. +4. Under the `Permissions policies` tab, click on the policy you want to edit. +5. Under the `Permissions defined in this policy` section, click on the service. You should see **Bedrock** if you already have access to the Bedrock agent service. +6. Click on the service, and then click `Edit`. +7. On the right, you will be able to add an action. Find the service and search for `InvokeModelWithResponseStream`. +8. Check the box next to the action and then scroll all the way down and click `Next`. +9. Follow the prompts to save the changes. diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs index 7f750091d3be..a47716b654bf 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs @@ -121,7 +121,6 @@ protected override async Task CreateAgentAsync(string agentName) private sealed class WeatherPlugin { [KernelFunction, Description("Provides realtime weather information.")] - // [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")] public string Current([Description("The location to get the weather for.")] string location) { return $"The current weather in {location} is 72 degrees."; diff --git a/dotnet/samples/GettingStartedWithAgents/README.md b/dotnet/samples/GettingStartedWithAgents/README.md index d30a0c6d7917..278f0513a85e 100644 --- a/dotnet/samples/GettingStartedWithAgents/README.md +++ b/dotnet/samples/GettingStartedWithAgents/README.md @@ -2,13 +2,14 @@ This project contains a step by step guide to get started with _Semantic Kernel Agents_. +## NuGet -#### NuGet: - [Microsoft.SemanticKernel.Agents.Abstractions](https://www.nuget.org/packages/Microsoft.SemanticKernel.Agents.Abstractions) - [Microsoft.SemanticKernel.Agents.Core](https://www.nuget.org/packages/Microsoft.SemanticKernel.Agents.Core) - [Microsoft.SemanticKernel.Agents.OpenAI](https://www.nuget.org/packages/Microsoft.SemanticKernel.Agents.OpenAI) -#### Source +## Source + - [Semantic Kernel Agent Framework](https://github.com/microsoft/semantic-kernel/tree/main/dotnet/src/Agents) The examples can be run as integration tests but their code can also be copied to stand-alone programs. @@ -48,6 +49,17 @@ Example|Description [Step04_AzureAIAgent_FileSearch](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step04_AzureAIAgent_FileSearch.cs)|How to use the file-search tool for an Azure AI agent. [Step05_AzureAIAgent_OpenAPI](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step05_AzureAIAgent_OpenAPI.cs)|How to use the Open API tool for an Azure AI agent. +### Bedrock Agent + +Example|Description +---|--- +[Step01_BedrockAgent](./BedrockAgent/Step01_BedrockAgent.cs)|How to create a Bedrock agent and interact with it in the most basic way. +[Step02_BedrockAgent_CodeInterpreter](./BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs)|How to use the code-interpreter tool with a Bedrock agent. +[Step03_BedrockAgent_Functions](./BedrockAgent/Step02_BedrockAgent_Functions.cs)|How to use kernel functions with a Bedrock agent. +[Step04_BedrockAgent_Trace](./BedrockAgent/Step02_BedrockAgent_Trace.cs)|How to enable tracing for a Bedrock agent to inspect the chain of thoughts. +[Step05_BedrockAgent_FileSearch](./BedrockAgent/Step02_BedrockAgent_FileSearch.cs)|How to use file search with a Bedrock agent (i.e. Bedrock knowledge base). +[Step06_BedrockAgent_AgentChat](./BedrockAgent/Step02_BedrockAgent_AgentChat.cs)|How to create a conversation between two agents and one of them in a Bedrock agent. + ## Legacy Agents Support for the OpenAI Assistant API was originally published in `Microsoft.SemanticKernel.Experimental.Agents` package: @@ -55,8 +67,8 @@ Support for the OpenAI Assistant API was originally published in `Microsoft.Sema This package has been superseded by _Semantic Kernel Agents_, which includes support for Open AI Assistant agents. - ## Running Examples with Filters + Examples may be explored and ran within _Visual Studio_ using _Test Explorer_. You can also run specific examples via the command-line by using test filters (`dotnet test --filter`). Type `dotnet test --help` at the command line for more details. @@ -108,13 +120,20 @@ To set your secrets with .NET Secret Manager: dotnet user-secrets set "AzureOpenAI:ApiKey" "..." ``` -5. Or Azure AI: +6. Or Azure AI: ``` dotnet user-secrets set "AzureAI:ConnectionString" "..." dotnet user-secrets set "AzureAI:ChatModelId" "gpt-4o" ``` +7. Or Bedrock: + + ``` + dotnet user-secrets set "BedrockAgent:AgentResourceRoleArn" "arn:aws:iam::...:role/..." + dotnet user-secrets set "BedrockAgent:FoundationModel" "..." + ``` + > NOTE: Azure secrets will take precedence, if both Open AI and Azure Open AI secrets are defined, unless `ForceOpenAI` is set: ``` diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs index 1680440354df..1cd22f0ea71c 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs @@ -200,20 +200,21 @@ private static SessionState CreateSessionStateWithFunctionResults(List - { - return new InvocationResultMember() - { - FunctionResult = new Amazon.BedrockAgentRuntime.Model.FunctionResult { - ActionGroup = agent.GetKernelFunctionActionGroupSignature(), - Function = functionResult.FunctionName, - ResponseBody = new Dictionary + return new InvocationResultMember() { - { "TEXT", new ContentBody() { Body = functionResult.Result as string } } - } + FunctionResult = new Amazon.BedrockAgentRuntime.Model.FunctionResult + { + ActionGroup = agent.GetKernelFunctionActionGroupSignature(), + Function = functionResult.FunctionName, + ResponseBody = new Dictionary + { + { "TEXT", new ContentBody() { Body = functionResult.Result as string } } + } + } + }; } - }; - })], + )], }; } } diff --git a/dotnet/src/Agents/Bedrock/README.md b/dotnet/src/Agents/Bedrock/README.md new file mode 100644 index 000000000000..d480985fc667 --- /dev/null +++ b/dotnet/src/Agents/Bedrock/README.md @@ -0,0 +1,27 @@ +# Amazon Bedrock AI Agents in Semantic Kernel + +## Overview + +AWS Bedrock Agents is a managed service that allows users to stand up and run AI agents in the AWS cloud quickly. + +## Tools/Functions + +Bedrock Agents allow the use of tools via [action groups](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-action-create.html). + +The integration of Bedrock Agents with Semantic Kernel allows users to register kernel functions as tools in Bedrock Agents. + +## Enable code interpretation + +Bedrock Agents can write and execute code via a feature known as [code interpretation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-code-interpretation.html) similar to what OpenAI also offers. + +## Enable user input + +Bedrock Agents can [request user input](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-user-input.html) in case of missing information to invoke a tool. When this is enabled, the agent will prompt the user for the missing information. When this is disabled, the agent will guess the missing information. + +## Knowledge base + +Bedrock Agents can leverage data saved on AWS to perform RAG tasks, this is referred to as the [knowledge base](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-kb-add.html) in AWS. + +## Multi-agent + +Bedrock Agents support [multi-agent workflows](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-multi-agent-collaboration.html) for more complex tasks. However, it employs a different pattern than what we have in Semantic Kernel, thus this is not supported in the current integration. diff --git a/python/samples/concepts/agents/bedrock_agent/README.md b/python/samples/concepts/agents/bedrock_agent/README.md index 0255aa561291..2e759a18f919 100644 --- a/python/samples/concepts/agents/bedrock_agent/README.md +++ b/python/samples/concepts/agents/bedrock_agent/README.md @@ -41,5 +41,5 @@ You need to make sure you have permission to access the foundation model. You ca 5. Under the `Permissions defined in this policy` section, click on the service. You should see **Bedrock** if you already have access to the Bedrock agent service. 6. Click on the service, and then click `Edit`. 7. On the right, you will be able to add an action. Find the service and search for `InvokeModelWithResponseStream`. -8. Check the box next to the action and then sroll all the way down and click `Next`. -9. Follow the prompts to save the changes. \ No newline at end of file +8. Check the box next to the action and then scroll all the way down and click `Next`. +9. Follow the prompts to save the changes. From 0d1b6e84bb11c4f72dbc755ddb197bcb30bcc79e Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Thu, 13 Feb 2025 11:36:16 -0800 Subject: [PATCH 17/27] Fix non-streaming invoke --- .../Step02_BedrockAgent_CodeInterpreter.cs | 4 +- .../BedrockAgent/Step04_BedrockAgent_Trace.cs | 41 ++++++++++++++-- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 47 +++++++++++++++++-- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs index 03598f21f493..97c5fe24cbec 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs @@ -40,9 +40,9 @@ public async Task UseAgentWithCodeInterpreterAsync() { this.Output.WriteLine(response.Content); } - if (binaryContent == null && response.Items.Count > 0 && response.Items[0] is BinaryContent binary) + if (binaryContent == null && response.Items.Count > 0) { - binaryContent = binary; + binaryContent = response.Items.OfType().FirstOrDefault(); } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs index 71a916c43908..e5d649d0b267 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. +using System.ComponentModel; using Amazon.BedrockAgentRuntime.Model; +using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.Bedrock; namespace GettingStarted.BedrockAgents; @@ -10,7 +12,7 @@ namespace GettingStarted.BedrockAgents; /// To learn more about different traces available, see: /// https://docs.aws.amazon.com/bedrock/latest/userguide/trace-events.html /// -public class Step04_BedrockAgent_Trace(ITestOutputHelper output) : Step03_BedrockAgent_Functions(output) +public class Step04_BedrockAgent_Trace(ITestOutputHelper output) : BaseBedrockAgentTest(output) { /// /// Demonstrates how to inspect the thought process of a by enabling trace. @@ -43,9 +45,17 @@ public async Task UseAgentWithTraceAsync() { this.Output.WriteLine(response.Content); } - else if (response.InnerContent is TracePart tracePart) + if (response.InnerContent is List innerContents) { - this.OutputTrace(tracePart.Trace); + // There could be multiple traces and they are stored in the InnerContent property + var traceParts = innerContents.OfType().ToList(); + if (traceParts is not null) + { + foreach (var tracePart in traceParts) + { + this.OutputTrace(tracePart.Trace); + } + } } } } @@ -129,4 +139,29 @@ private void OutputTrace(Trace trace) // Input token: 1003 // Output token: 31 } + protected override async Task CreateAgentAsync(string agentName) + { + Kernel kernel = new(); + kernel.Plugins.Add(KernelPluginFactory.CreateFromType()); + + return await BedrockAgent.CreateAsync( + this.GetCreateAgentRequest(agentName), + kernel: kernel, + enableKernelFunctions: true); + } + + private sealed class WeatherPlugin + { + [KernelFunction, Description("Provides realtime weather information.")] + public string Current([Description("The location to get the weather for.")] string location) + { + return $"The current weather in {location} is 72 degrees."; + } + + [KernelFunction, Description("Forecast weather information.")] + public string Forecast([Description("The location to get the weather for.")] string location) + { + return $"The forecast for {location} is 75 degrees tomorrow."; + } + } } diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index c44a2410f195..bb4ef1e3d29b 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -11,6 +11,7 @@ using Amazon.BedrockAgentRuntime.Model; using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; using Microsoft.SemanticKernel.Agents.Extensions; +using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Diagnostics; namespace Microsoft.SemanticKernel.Agents.Bedrock; @@ -211,10 +212,48 @@ public IAsyncEnumerable InvokeAsync( { return invokeAgentRequest.StreamingConfigurations != null && invokeAgentRequest.StreamingConfigurations.StreamFinalResponse ? throw new ArgumentException("The streaming configuration must be null for non-streaming responses.") - : ActivityExtensions.RunWithActivityAsync( - () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), - () => this.InternalInvokeAsync(invokeAgentRequest, arguments, cancellationToken), - cancellationToken); + : InvokeInternal(); + + // Collect all responses from the agent and return them as a single chat message content since this + // is a non-streaming API. + // The Bedrock Agent API streams beck different types of responses, i.e. text, files, metadata, etc. + // The Bedrock Agent API also won't stream back any content when it needs to call a function. It will + // only start streaming back content after the function has been called and the response is ready. + async IAsyncEnumerable InvokeInternal() + { + ChatMessageContentItemCollection items = []; + string content = ""; + Dictionary metadata = []; + List innerContents = []; + + await foreach (var message in ActivityExtensions.RunWithActivityAsync( + () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), + () => this.InternalInvokeAsync(invokeAgentRequest, arguments, cancellationToken), + cancellationToken).ConfigureAwait(false)) + { + items.AddRange(message.Items); + content += message.Content ?? ""; + if (message.Metadata != null) + { + foreach (var key in message.Metadata.Keys) + { + metadata[key] = message.Metadata[key]; + } + } + innerContents.Add(message.InnerContent); + } + + yield return content.Length == 0 + ? throw new KernelException("No content was returned from the agent.") + : new ChatMessageContent(AuthorRole.Assistant, content) + { + AuthorName = this.GetDisplayName(), + Items = items, + ModelId = this._agentModel.FoundationModel, + Metadata = metadata, + InnerContent = innerContents, + }; + } } /// From 250a27bbb9fb7b3bd8c42094690fe7336796f1a5 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Thu, 13 Feb 2025 15:53:47 -0800 Subject: [PATCH 18/27] Minor updates --- .../BedrockAgent/Step01_BedrockAgent.cs | 12 ------------ .../Extensions/BedrockAgentInvokeExtensions.cs | 9 +++++++-- ...Extensions.cs => BedrockAgentStatusExtensions.cs} | 0 3 files changed, 7 insertions(+), 14 deletions(-) rename dotnet/src/Agents/Bedrock/Extensions/{AgentStatusExtensions.cs => BedrockAgentStatusExtensions.cs} (100%) diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs index 5098f4af5411..dd23394e69c8 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs @@ -61,18 +61,6 @@ public async Task UseNewAgentStreamingAsync() } } - [Fact] - public async Task UseTemplateForAssistantAgentAsync() - { - // Define the agent - - // Instructions, Name and Description properties defined via the config. - - // Create a thread for the agent conversation. - - // Local function to invoke agent and display the response. - } - protected override async Task CreateAgentAsync(string agentName) { return await BedrockAgent.CreateAsync(this.GetCreateAgentRequest(agentName)); diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs index 1cd22f0ea71c..20371c54ce62 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Amazon.BedrockAgentRuntime; using Amazon.BedrockAgentRuntime.Model; using Amazon.Runtime.EventStreams.Internal; using Microsoft.SemanticKernel.Agents.Extensions; @@ -25,7 +26,7 @@ public static async IAsyncEnumerable InternalInvokeAsync( KernelArguments? arguments, [EnumeratorCancellation] CancellationToken cancellationToken) { - // Session state is used to store the results of function calls to be passed back to the agent. + // This session state is used to store the results of function calls to be passed back to the agent. // https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/BedrockAgentRuntime/TSessionState.html SessionState? sessionState = null; for (var requestIndex = 0; ; requestIndex++) @@ -45,7 +46,11 @@ public static async IAsyncEnumerable InternalInvokeAsync( List functionCallContents = []; await foreach (var responseEvent in invokeAgentResponse.Completion.ToAsyncEnumerable().ConfigureAwait(false)) { - // TODO: Handle exception events + if (responseEvent is BedrockAgentRuntimeEventStreamException bedrockAgentRuntimeEventStreamException) + { + throw new KernelException("Failed to handle Bedrock Agent stream event.", bedrockAgentRuntimeEventStreamException); + } + var chatMessageContent = HandleChunkEvent(agent, responseEvent) ?? HandleFilesEvent(agent, responseEvent) ?? diff --git a/dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs similarity index 100% rename from dotnet/src/Agents/Bedrock/Extensions/AgentStatusExtensions.cs rename to dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs From 48e499a0ceea64069e369379687c24667af950bd Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Thu, 13 Feb 2025 16:04:29 -0800 Subject: [PATCH 19/27] Add array type support --- .../Extensions/BedrockFunctionSchemaExtensions.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs index 95effc839664..c890638484a2 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockFunctionSchemaExtensions.cs @@ -85,7 +85,17 @@ private static Amazon.BedrockAgent.Type ToAmazonType(this System.Type? parameter "Single" => Amazon.BedrockAgent.Type.Number, "Double" => Amazon.BedrockAgent.Type.Number, "Decimal" => Amazon.BedrockAgent.Type.Number, - // TODO: Add support for array type. + "String[]" => Amazon.BedrockAgent.Type.Array, + "Boolean[]" => Amazon.BedrockAgent.Type.Array, + "Int16[]" => Amazon.BedrockAgent.Type.Array, + "UInt16[]" => Amazon.BedrockAgent.Type.Array, + "Int32[]" => Amazon.BedrockAgent.Type.Array, + "UInt32[]" => Amazon.BedrockAgent.Type.Array, + "Int64[]" => Amazon.BedrockAgent.Type.Array, + "UInt64[]" => Amazon.BedrockAgent.Type.Array, + "Single[]" => Amazon.BedrockAgent.Type.Array, + "Double[]" => Amazon.BedrockAgent.Type.Array, + "Decimal[]" => Amazon.BedrockAgent.Type.Array, _ => throw new ArgumentException($"Unsupported parameter type: {typeString}"), }; } From 0377d076b905be7feffdd54cce2087217a2fac73 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Thu, 13 Feb 2025 16:29:20 -0800 Subject: [PATCH 20/27] Fix pipeline --- dotnet/samples/GettingStartedWithAgents/README.md | 8 ++++---- dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj | 1 + dotnet/src/Agents/Bedrock/BedrockAgent.cs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/README.md b/dotnet/samples/GettingStartedWithAgents/README.md index b8648600bb6a..6c54a26c0d90 100644 --- a/dotnet/samples/GettingStartedWithAgents/README.md +++ b/dotnet/samples/GettingStartedWithAgents/README.md @@ -57,10 +57,10 @@ Example|Description ---|--- [Step01_BedrockAgent](./BedrockAgent/Step01_BedrockAgent.cs)|How to create a Bedrock agent and interact with it in the most basic way. [Step02_BedrockAgent_CodeInterpreter](./BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs)|How to use the code-interpreter tool with a Bedrock agent. -[Step03_BedrockAgent_Functions](./BedrockAgent/Step02_BedrockAgent_Functions.cs)|How to use kernel functions with a Bedrock agent. -[Step04_BedrockAgent_Trace](./BedrockAgent/Step02_BedrockAgent_Trace.cs)|How to enable tracing for a Bedrock agent to inspect the chain of thoughts. -[Step05_BedrockAgent_FileSearch](./BedrockAgent/Step02_BedrockAgent_FileSearch.cs)|How to use file search with a Bedrock agent (i.e. Bedrock knowledge base). -[Step06_BedrockAgent_AgentChat](./BedrockAgent/Step02_BedrockAgent_AgentChat.cs)|How to create a conversation between two agents and one of them in a Bedrock agent. +[Step03_BedrockAgent_Functions](./BedrockAgent/Step03_BedrockAgent_Functions.cs)|How to use kernel functions with a Bedrock agent. +[Step04_BedrockAgent_Trace](./BedrockAgent/Step04_BedrockAgent_Trace.cs)|How to enable tracing for a Bedrock agent to inspect the chain of thoughts. +[Step05_BedrockAgent_FileSearch](./BedrockAgent/Step05_BedrockAgent_FileSearch.cs)|How to use file search with a Bedrock agent (i.e. Bedrock knowledge base). +[Step06_BedrockAgent_AgentChat](./BedrockAgent/Step06_BedrockAgent_AgentChat.cs)|How to create a conversation between two agents and one of them in a Bedrock agent. ## Legacy Agents diff --git a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj index 6a74513ece7e..e17d43f63fcc 100644 --- a/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj +++ b/dotnet/src/Agents/Bedrock/Agents.Bedrock.csproj @@ -19,6 +19,7 @@ + diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index bb4ef1e3d29b..fcb7b74b3434 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -87,7 +87,7 @@ public static async Task CreateAsync( BedrockAgent agent = new(createAgentResponse.Agent, client, runtimeClient) { Kernel = kernel ?? new(), - Arguments = defaultArguments, + Arguments = defaultArguments ?? [], }; // The agent will first enter the CREATING status. @@ -138,7 +138,7 @@ public static async Task RetrieveAsync( return new(getAgentResponse.Agent, client, runtimeClient) { Kernel = kernel ?? new(), - Arguments = defaultArguments, + Arguments = defaultArguments ?? [], }; } From 9c20a80b4bf53d807fd75ef20b0879049c3e0de5 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 18 Feb 2025 13:50:11 -0800 Subject: [PATCH 21/27] Address comments 1st batch --- .../BedrockAgent/README.md | 2 +- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 133 +++++++----------- .../src/Agents/Bedrock/BedrockAgentChannel.cs | 6 +- .../BedrockAgentInvokeExtensions.cs | 12 +- .../BedrockAgentStatusExtensions.cs | 2 +- 5 files changed, 66 insertions(+), 89 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/README.md b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/README.md index 815bcf23f845..083a1c71a156 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/README.md +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/README.md @@ -7,7 +7,7 @@ ## Before running the samples -You need to set up some user secrets run the samples. +You need to set up some user secrets to run the samples. ### `BedrockAgent:AgentResourceRoleArn` diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index fcb7b74b3434..eb29f3cab8b8 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -21,11 +21,11 @@ namespace Microsoft.SemanticKernel.Agents.Bedrock; /// public class BedrockAgent : KernelAgent { - private readonly AmazonBedrockAgentClient _client; + internal readonly AmazonBedrockAgentClient Client; - private readonly AmazonBedrockAgentRuntimeClient _runtimeClient; + internal readonly AmazonBedrockAgentRuntimeClient RuntimeClient; - private readonly Amazon.BedrockAgent.Model.Agent _agentModel; + internal readonly Amazon.BedrockAgent.Model.Agent AgentModel; /// /// There is a default alias created by Bedrock for the working draft version of the agent. @@ -35,6 +35,8 @@ public class BedrockAgent : KernelAgent /// /// Initializes a new instance of the class. + /// Unlike other types of agents in Semantic Kernel, prompt templates are not supported for Bedrock agents, + /// since Bedrock agents don't support using an alternative instruction in runtime. /// /// The agent model of an agent that exists on the Bedrock Agent service. /// A client used to interact with the Bedrock Agent service. @@ -44,9 +46,9 @@ public BedrockAgent( AmazonBedrockAgentClient client, AmazonBedrockAgentRuntimeClient runtimeClient) { - this._agentModel = agentModel; - this._client = client; - this._runtimeClient = runtimeClient; + this.AgentModel = agentModel; + this.Client = client; + this.RuntimeClient = runtimeClient; this.Id = agentModel.AgentId; this.Name = agentModel.AgentName; @@ -113,35 +115,6 @@ public static async Task CreateAsync( return agent; } - /// - /// Retrieve a Bedrock agent from the service by id. - /// - /// The id of the agent that exists on the Bedrock Agent service. - /// The client to use. - /// The runtime client to use. - /// A kernel instance for the agent to use. - /// Optional default arguments. - /// The to monitor for cancellation requests. The default is . - /// An instance of the . - public static async Task RetrieveAsync( - string id, - AmazonBedrockAgentClient? client = null, - AmazonBedrockAgentRuntimeClient? runtimeClient = null, - Kernel? kernel = null, - KernelArguments? defaultArguments = null, - CancellationToken cancellationToken = default) - { - client ??= new AmazonBedrockAgentClient(); - runtimeClient ??= new AmazonBedrockAgentRuntimeClient(); - var getAgentResponse = await client.GetAgentAsync(new() { AgentId = id }, cancellationToken).ConfigureAwait(false); - - return new(getAgentResponse.Agent, client, runtimeClient) - { - Kernel = kernel ?? new(), - Arguments = defaultArguments ?? [], - }; - } - /// /// Convenient method to create an unique session id. /// @@ -160,15 +133,15 @@ public static string CreateSessionId() /// The to monitor for cancellation requests. The default is . public async Task DeleteAsync(CancellationToken cancellationToken) { - await this._client.DeleteAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); + await this.Client.DeleteAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); } /// /// Prepare the Bedrock agent for use. /// - public async Task PrepareAsync(CancellationToken cancellationToken) + private async Task PrepareAsync(CancellationToken cancellationToken) { - await this._client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); + await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); await this.WaitForAgentStatusAsync(AgentStatus.PREPARED, cancellationToken).ConfigureAwait(false); } @@ -212,7 +185,10 @@ public IAsyncEnumerable InvokeAsync( { return invokeAgentRequest.StreamingConfigurations != null && invokeAgentRequest.StreamingConfigurations.StreamFinalResponse ? throw new ArgumentException("The streaming configuration must be null for non-streaming responses.") - : InvokeInternal(); + : ActivityExtensions.RunWithActivityAsync( + () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), + InvokeInternal, + cancellationToken); // Collect all responses from the agent and return them as a single chat message content since this // is a non-streaming API. @@ -226,10 +202,7 @@ async IAsyncEnumerable InvokeInternal() Dictionary metadata = []; List innerContents = []; - await foreach (var message in ActivityExtensions.RunWithActivityAsync( - () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), - () => this.InternalInvokeAsync(invokeAgentRequest, arguments, cancellationToken), - cancellationToken).ConfigureAwait(false)) + await foreach (var message in this.InternalInvokeAsync(invokeAgentRequest, arguments, cancellationToken).ConfigureAwait(false)) { items.AddRange(message.Items); content += message.Content ?? ""; @@ -249,7 +222,7 @@ async IAsyncEnumerable InvokeInternal() { AuthorName = this.GetDisplayName(), Items = items, - ModelId = this._agentModel.FoundationModel, + ModelId = this.AgentModel.FoundationModel, Metadata = metadata, InnerContent = innerContents, }; @@ -293,10 +266,10 @@ public IAsyncEnumerable InvokeStreamingAsync( /// The arguments to use when invoking the agent. /// The to monitor for cancellation requests. The default is . /// An of . - public async IAsyncEnumerable InvokeStreamingAsync( + public IAsyncEnumerable InvokeStreamingAsync( InvokeAgentRequest invokeAgentRequest, KernelArguments? arguments, - [EnumeratorCancellation] CancellationToken cancellationToken) + CancellationToken cancellationToken) { if (invokeAgentRequest.StreamingConfigurations == null) { @@ -310,21 +283,26 @@ public async IAsyncEnumerable InvokeStreamingAsync( throw new ArgumentException("The streaming configuration must have StreamFinalResponse set to true."); } - // The Bedrock agent service has the same API for both streaming and non-streaming responses. - // We are invoking the same method as the non-streaming response with the streaming configuration set, - // and converting the chat message content to streaming chat message content. - await foreach (var chatMessageContent in ActivityExtensions.RunWithActivityAsync( + return ActivityExtensions.RunWithActivityAsync( () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), - () => this.InternalInvokeAsync(invokeAgentRequest, arguments, cancellationToken), - cancellationToken).ConfigureAwait(false)) + InvokeInternal, + cancellationToken); + + async IAsyncEnumerable InvokeInternal() { - yield return new StreamingChatMessageContent(chatMessageContent.Role, chatMessageContent.Content) + // The Bedrock agent service has the same API for both streaming and non-streaming responses. + // We are invoking the same method as the non-streaming response with the streaming configuration set, + // and converting the chat message content to streaming chat message content. + await foreach (var chatMessageContent in this.InternalInvokeAsync(invokeAgentRequest, arguments, cancellationToken).ConfigureAwait(false)) { - AuthorName = chatMessageContent.AuthorName, - ModelId = chatMessageContent.ModelId, - InnerContent = chatMessageContent.InnerContent, - Metadata = chatMessageContent.Metadata, - }; + yield return new StreamingChatMessageContent(chatMessageContent.Role, chatMessageContent.Content) + { + AuthorName = chatMessageContent.AuthorName, + ModelId = chatMessageContent.ModelId, + InnerContent = chatMessageContent.InnerContent, + Metadata = chatMessageContent.Metadata, + }; + } } } @@ -336,10 +314,10 @@ public async IAsyncEnumerable InvokeStreamingAsync( /// The to monitor for cancellation requests. The default is . public async Task AssociateAgentKnowledgeBaseAsync(string knowledgeBaseId, string description, CancellationToken cancellationToken) { - await this._client.AssociateAgentKnowledgeBaseAsync(new() + await this.Client.AssociateAgentKnowledgeBaseAsync(new() { AgentId = this.Id, - AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", + AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", KnowledgeBaseId = knowledgeBaseId, Description = description, }, cancellationToken).ConfigureAwait(false); @@ -352,10 +330,10 @@ await this._client.AssociateAgentKnowledgeBaseAsync(new() /// The to monitor for cancellation requests. The default is . public async Task DisassociateAgentKnowledgeBaseAsync(string knowledgeBaseId, CancellationToken cancellationToken) { - await this._client.DisassociateAgentKnowledgeBaseAsync(new() + await this.Client.DisassociateAgentKnowledgeBaseAsync(new() { AgentId = this.Id, - AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", + AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", KnowledgeBaseId = knowledgeBaseId, }, cancellationToken).ConfigureAwait(false); } @@ -367,10 +345,10 @@ await this._client.DisassociateAgentKnowledgeBaseAsync(new() /// A containing the knowledge bases associated with the agent. public async Task ListAssociatedKnowledgeBasesAsync(CancellationToken cancellationToken) { - return await this._client.ListAgentKnowledgeBasesAsync(new() + return await this.Client.ListAgentKnowledgeBasesAsync(new() { AgentId = this.Id, - AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", + AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", }, cancellationToken).ConfigureAwait(false); } @@ -386,13 +364,13 @@ private async Task CreateCodeInterpreterActionGroupAsync(CancellationToken cance var createAgentActionGroupRequest = new CreateAgentActionGroupRequest { AgentId = this.Id, - AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", - ActionGroupName = this.GetCodeInterpreterActionGroupSignature(), + AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", + ActionGroupName = this.CodeInterpreterActionGroupSignature, ActionGroupState = ActionGroupState.ENABLED, ParentActionGroupSignature = new(Amazon.BedrockAgent.ActionGroupSignature.AMAZONCodeInterpreter), }; - await this._client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + await this.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); } /// @@ -403,8 +381,8 @@ private async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancel var createAgentActionGroupRequest = new CreateAgentActionGroupRequest { AgentId = this.Id, - AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", - ActionGroupName = this.GetKernelFunctionActionGroupSignature(), + AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", + ActionGroupName = this.KernelFunctionActionGroupSignature, ActionGroupState = ActionGroupState.ENABLED, ActionGroupExecutor = new() { @@ -413,7 +391,7 @@ private async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancel FunctionSchema = this.Kernel.ToFunctionSchema(), }; - await this._client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + await this.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); } /// @@ -424,13 +402,13 @@ private async Task EnableUserInputActionGroupAsync(CancellationToken cancellatio var createAgentActionGroupRequest = new CreateAgentActionGroupRequest { AgentId = this.Id, - AgentVersion = this._agentModel.AgentVersion ?? "DRAFT", - ActionGroupName = this.GetUseInputActionGroupSignature(), + AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", + ActionGroupName = this.UseInputActionGroupSignature, ActionGroupState = ActionGroupState.ENABLED, ParentActionGroupSignature = new(Amazon.BedrockAgent.ActionGroupSignature.AMAZONUserInput), }; - await this._client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + await this.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); } #endregion @@ -458,12 +436,9 @@ protected override Task RestoreChannelAsync(string channelState, C #region internal methods - internal AmazonBedrockAgentClient GetClient() => this._client; - internal AmazonBedrockAgentRuntimeClient GetRuntimeClient() => this._runtimeClient; - internal Amazon.BedrockAgent.Model.Agent GetAgentModel() => this._agentModel; - internal string GetCodeInterpreterActionGroupSignature() => $"{this.GetDisplayName()}_CodeInterpreter"; - internal string GetKernelFunctionActionGroupSignature() => $"{this.GetDisplayName()}_KernelFunctions"; - internal string GetUseInputActionGroupSignature() => $"{this.GetDisplayName()}_UserInput"; + internal string CodeInterpreterActionGroupSignature { get => $"{this.GetDisplayName()}_CodeInterpreter"; } + internal string KernelFunctionActionGroupSignature { get => $"{this.GetDisplayName()}_KernelFunctions"; } + internal string UseInputActionGroupSignature { get => $"{this.GetDisplayName()}_UserInput"; } #endregion } diff --git a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs index ce6c0ab4922e..840da11c9862 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Amazon.BedrockAgentRuntime.Model; +using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.Agents.Extensions; using Microsoft.SemanticKernel.Agents.Serialization; using Microsoft.SemanticKernel.ChatCompletion; @@ -34,9 +35,10 @@ protected override Task ReceiveAsync(IEnumerable history, Ca { foreach (var incomingMessage in history) { - if (incomingMessage.Content is null) + if (string.IsNullOrEmpty(incomingMessage.Content)) { - throw new InvalidOperationException("Message content cannot be null."); + this.Logger.LogWarning("Received a message with no content. Skipping."); + continue; } if (this._history.Count == 0 || this._history.Last().Role != incomingMessage.Role) diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs index 20371c54ce62..5e67aacaf04a 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentInvokeExtensions.cs @@ -36,7 +36,7 @@ public static async IAsyncEnumerable InternalInvokeAsync( invokeAgentRequest.SessionState = sessionState; sessionState = null; } - var invokeAgentResponse = await agent.GetRuntimeClient().InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); + var invokeAgentResponse = await agent.RuntimeClient.InvokeAgentAsync(invokeAgentRequest, cancellationToken).ConfigureAwait(false); if (invokeAgentResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) { @@ -96,7 +96,7 @@ public static async IAsyncEnumerable InternalInvokeAsync( Role = AuthorRole.Assistant, AuthorName = agent.GetDisplayName(), Content = Encoding.UTF8.GetString(payload.Bytes.ToArray()), - ModelId = agent.GetAgentModel().FoundationModel, + ModelId = agent.AgentModel.FoundationModel, InnerContent = payload, }; } @@ -127,7 +127,7 @@ public static async IAsyncEnumerable InternalInvokeAsync( Role = AuthorRole.Assistant, AuthorName = agent.GetDisplayName(), Items = binaryContents, - ModelId = agent.GetAgentModel().FoundationModel, + ModelId = agent.AgentModel.FoundationModel, InnerContent = files, }; } @@ -164,7 +164,7 @@ public static async IAsyncEnumerable InternalInvokeAsync( Role = AuthorRole.Assistant, AuthorName = agent.GetDisplayName(), Items = functionCallContents, - ModelId = agent.GetAgentModel().FoundationModel, + ModelId = agent.AgentModel.FoundationModel, InnerContent = returnControlPayload, }; } @@ -179,7 +179,7 @@ public static async IAsyncEnumerable InternalInvokeAsync( { Role = AuthorRole.Assistant, AuthorName = agent.GetDisplayName(), - ModelId = agent.GetAgentModel().FoundationModel, + ModelId = agent.AgentModel.FoundationModel, InnerContent = trace, }; } @@ -210,7 +210,7 @@ private static SessionState CreateSessionStateWithFunctionResults(List { diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs index f564f614bcc6..b91b88baa438 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs @@ -29,7 +29,7 @@ public static async Task WaitForAgentStatusAsync( { for (var i = 0; i < maxAttempts; i++) { - var getAgentResponse = await agent.GetClient().GetAgentAsync(new() { AgentId = agent.Id }, cancellationToken).ConfigureAwait(false); + var getAgentResponse = await agent.Client.GetAgentAsync(new() { AgentId = agent.Id }, cancellationToken).ConfigureAwait(false); if (getAgentResponse.Agent.AgentStatus == status) { From c5488b688200a7ea69357423849daab4946ad8cd Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 18 Feb 2025 17:22:25 -0800 Subject: [PATCH 22/27] Address comments 2nd batch --- .../BedrockAgent/Step01_BedrockAgent.cs | 15 +- .../Step02_BedrockAgent_CodeInterpreter.cs | 15 +- .../Step03_BedrockAgent_Functions.cs | 32 +++-- .../BedrockAgent/Step04_BedrockAgent_Trace.cs | 21 ++- .../Step05_BedrockAgent_FileSearch.cs | 14 +- .../Step06_BedrockAgent_AgentChat.cs | 9 +- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 133 ++++-------------- .../Extensions/BedrockAgentExtensions.cs | 79 +++++++++++ .../BedrockAgentStatusExtensions.cs | 44 ------ .../AgentUtilities/BaseBedrockAgentTest.cs | 16 ++- .../Contents/BinaryContent.cs | 26 ---- .../Contents/BinaryContentExtenstions.cs | 38 +++++ 12 files changed, 234 insertions(+), 208 deletions(-) create mode 100644 dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs delete mode 100644 dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs create mode 100644 dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtenstions.cs diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs index dd23394e69c8..6791ede89406 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using Microsoft.SemanticKernel.Agents.Bedrock; +using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; namespace GettingStarted.BedrockAgents; @@ -24,7 +25,7 @@ public async Task UseNewAgentAsync() // Respond to user input try { - var responses = bedrockAgent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + var responses = bedrockAgent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null); await foreach (var response in responses) { this.Output.WriteLine(response.Content); @@ -32,7 +33,7 @@ public async Task UseNewAgentAsync() } finally { - await bedrockAgent.DeleteAsync(CancellationToken.None); + // await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } @@ -49,7 +50,7 @@ public async Task UseNewAgentStreamingAsync() // Respond to user input try { - var streamingResponses = bedrockAgent.InvokeStreamingAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + var streamingResponses = bedrockAgent.InvokeStreamingAsync(BedrockAgent.CreateSessionId(), UserQuery, null); await foreach (var response in streamingResponses) { this.Output.WriteLine(response.Content); @@ -57,12 +58,16 @@ public async Task UseNewAgentStreamingAsync() } finally { - await bedrockAgent.DeleteAsync(CancellationToken.None); + await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } protected override async Task CreateAgentAsync(string agentName) { - return await BedrockAgent.CreateAsync(this.GetCreateAgentRequest(agentName)); + // Create a new agent on the Bedrock Agent service and prepare it for use + var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName)); + // Create a new BedrockAgent instance with the agent model and the client + // so that we can interact with the agent using Semantic Kernel contents. + return new BedrockAgent(agentModel, this.Client); } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs index 97c5fe24cbec..70bde61a9aab 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs @@ -3,6 +3,7 @@ using System.Reflection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.Bedrock; +using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; namespace GettingStarted.BedrockAgents; @@ -33,7 +34,7 @@ public async Task UseAgentWithCodeInterpreterAsync() try { BinaryContent? binaryContent = null; - var responses = bedrockAgent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null, CancellationToken.None); + var responses = bedrockAgent.InvokeAsync(BedrockAgent.CreateSessionId(), UserQuery, null); await foreach (var response in responses) { if (response.Content != null) @@ -70,12 +71,20 @@ public async Task UseAgentWithCodeInterpreterAsync() } finally { - await bedrockAgent.DeleteAsync(CancellationToken.None); + await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } protected override async Task CreateAgentAsync(string agentName) { - return await BedrockAgent.CreateAsync(this.GetCreateAgentRequest(agentName), enableCodeInterpreter: true); + // Create a new agent on the Bedrock Agent service and prepare it for use + var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName)); + // Create a new BedrockAgent instance with the agent model and the client + // so that we can interact with the agent using Semantic Kernel contents. + var bedrockAgent = new BedrockAgent(agentModel, this.Client); + // Create the code interpreter action group and prepare the agent for interaction + await bedrockAgent.CreateCodeInterpreterActionGroupAsync(); + + return bedrockAgent; } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs index a47716b654bf..ab23b4be0128 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step03_BedrockAgent_Functions.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.Bedrock; +using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; namespace GettingStarted.BedrockAgents; @@ -27,8 +28,7 @@ public async Task UseAgentWithFunctionsAsync() var responses = bedrockAgent.InvokeAsync( BedrockAgent.CreateSessionId(), "What is the weather in Seattle?", - null, - CancellationToken.None); + null); await foreach (var response in responses) { if (response.Content != null) @@ -39,7 +39,7 @@ public async Task UseAgentWithFunctionsAsync() } finally { - await bedrockAgent.DeleteAsync(CancellationToken.None); + await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } @@ -59,8 +59,7 @@ public async Task UseAgentStreamingWithFunctionsAsync() var streamingResponses = bedrockAgent.InvokeStreamingAsync( BedrockAgent.CreateSessionId(), "What is the weather forecast in Seattle?", - null, - CancellationToken.None); + null); await foreach (var response in streamingResponses) { if (response.Content != null) @@ -71,7 +70,7 @@ public async Task UseAgentStreamingWithFunctionsAsync() } finally { - await bedrockAgent.DeleteAsync(CancellationToken.None); + await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } @@ -91,8 +90,7 @@ public async Task UseAgentWithParallelFunctionsAsync() var responses = bedrockAgent.InvokeAsync( BedrockAgent.CreateSessionId(), "What is the current weather in Seattle and what is the weather forecast in Seattle?", - null, - CancellationToken.None); + null); await foreach (var response in responses) { if (response.Content != null) @@ -103,19 +101,27 @@ public async Task UseAgentWithParallelFunctionsAsync() } finally { - await bedrockAgent.DeleteAsync(CancellationToken.None); + await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } protected override async Task CreateAgentAsync(string agentName) { + // Create a new agent on the Bedrock Agent service and prepare it for use + var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName)); + // Create a new kernel with plugins Kernel kernel = new(); kernel.Plugins.Add(KernelPluginFactory.CreateFromType()); + // Create a new BedrockAgent instance with the agent model and the client + // so that we can interact with the agent using Semantic Kernel contents. + var bedrockAgent = new BedrockAgent(agentModel, this.Client) + { + Kernel = kernel, + }; + // Create the kernel function action group and prepare the agent for interaction + await bedrockAgent.CreateKernelFunctionActionGroupAsync(); - return await BedrockAgent.CreateAsync( - this.GetCreateAgentRequest(agentName), - kernel: kernel, - enableKernelFunctions: true); + return bedrockAgent; } private sealed class WeatherPlugin diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs index e5d649d0b267..3e1400a5115d 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step04_BedrockAgent_Trace.cs @@ -4,6 +4,7 @@ using Amazon.BedrockAgentRuntime.Model; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.Bedrock; +using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; namespace GettingStarted.BedrockAgents; @@ -38,7 +39,7 @@ public async Task UseAgentWithTraceAsync() EnableTrace = true, }; - var responses = bedrockAgent.InvokeAsync(invokeAgentRequest, null, CancellationToken.None); + var responses = bedrockAgent.InvokeAsync(invokeAgentRequest, null); await foreach (var response in responses) { if (response.Content != null) @@ -61,7 +62,7 @@ public async Task UseAgentWithTraceAsync() } finally { - await bedrockAgent.DeleteAsync(CancellationToken.None); + await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } @@ -141,13 +142,21 @@ private void OutputTrace(Trace trace) } protected override async Task CreateAgentAsync(string agentName) { + // Create a new agent on the Bedrock Agent service and prepare it for use + var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName)); + // Create a new kernel with plugins Kernel kernel = new(); kernel.Plugins.Add(KernelPluginFactory.CreateFromType()); + // Create a new BedrockAgent instance with the agent model and the client + // so that we can interact with the agent using Semantic Kernel contents. + var bedrockAgent = new BedrockAgent(agentModel, this.Client) + { + Kernel = kernel, + }; + // Create the kernel function action group and prepare the agent for interaction + await bedrockAgent.CreateKernelFunctionActionGroupAsync(); - return await BedrockAgent.CreateAsync( - this.GetCreateAgentRequest(agentName), - kernel: kernel, - enableKernelFunctions: true); + return bedrockAgent; } private sealed class WeatherPlugin diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs index f0c81adf3ccc..9b7b4330af33 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step05_BedrockAgent_FileSearch.cs @@ -2,6 +2,7 @@ using Amazon.BedrockAgentRuntime.Model; using Microsoft.SemanticKernel.Agents.Bedrock; +using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; namespace GettingStarted.BedrockAgents; @@ -20,12 +21,15 @@ public class Step05_BedrockAgent_FileSearch(ITestOutputHelper output) : BaseBedr protected override async Task CreateAgentAsync(string agentName) { - var bedrockAgent = await BedrockAgent.CreateAsync(this.GetCreateAgentRequest(agentName)); - // Associate the agent with a knowledge base + // Create a new agent on the Bedrock Agent service and prepare it for use + var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName)); + // Create a new BedrockAgent instance with the agent model and the client + // so that we can interact with the agent using Semantic Kernel contents. + var bedrockAgent = new BedrockAgent(agentModel, this.Client); + // Associate the agent with a knowledge base and prepare the agent await bedrockAgent.AssociateAgentKnowledgeBaseAsync( KnowledgeBaseId, - "You will find information here.", - CancellationToken.None); + "You will find information here."); return bedrockAgent; } @@ -65,7 +69,7 @@ public async Task UseAgentWithFileSearchAsync() } finally { - await bedrockAgent.DeleteAsync(CancellationToken.None); + await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } } diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs index fd09c3a471c7..b7aee9d06c7e 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step06_BedrockAgent_AgentChat.cs @@ -3,6 +3,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Bedrock; +using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; using Microsoft.SemanticKernel.Agents.Chat; using Microsoft.SemanticKernel.ChatCompletion; @@ -15,7 +16,11 @@ public class Step06_BedrockAgent_AgentChat(ITestOutputHelper output) : BaseBedro { protected override async Task CreateAgentAsync(string agentName) { - return await BedrockAgent.CreateAsync(this.GetCreateAgentRequest(agentName)); + // Create a new agent on the Bedrock Agent service and prepare it for use + var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName)); + // Create a new BedrockAgent instance with the agent model and the client + // so that we can interact with the agent using Semantic Kernel contents. + return new BedrockAgent(agentModel, this.Client); } /// @@ -65,7 +70,7 @@ public async Task UseAgentWithAgentChatAsync() } finally { - await bedrockAgent.DeleteAsync(CancellationToken.None); + await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index eb29f3cab8b8..427c52f44066 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -43,12 +43,12 @@ public class BedrockAgent : KernelAgent /// A client used to interact with the Bedrock Agent runtime service. public BedrockAgent( Amazon.BedrockAgent.Model.Agent agentModel, - AmazonBedrockAgentClient client, - AmazonBedrockAgentRuntimeClient runtimeClient) + AmazonBedrockAgentClient? client = null, + AmazonBedrockAgentRuntimeClient? runtimeClient = null) { this.AgentModel = agentModel; - this.Client = client; - this.RuntimeClient = runtimeClient; + this.Client ??= new AmazonBedrockAgentClient(); + this.RuntimeClient ??= new AmazonBedrockAgentRuntimeClient(); this.Id = agentModel.AgentId; this.Name = agentModel.AgentName; @@ -58,63 +58,6 @@ public BedrockAgent( #region static methods - /// - /// Create a Bedrock agent on the service. - /// - /// The request to create the agent. - /// Whether to enable the code interpreter for the agent. - /// Whether to enable kernel functions for the agent. - /// Whether to enable user input for the agent. - /// The client to use. - /// The runtime client to use. - /// A kernel instance for the agent to use. - /// Optional default arguments. - /// The to monitor for cancellation requests. The default is . - /// An instance of the . - public static async Task CreateAsync( - CreateAgentRequest request, - bool enableCodeInterpreter = false, - bool enableKernelFunctions = false, - bool enableUserInput = false, - AmazonBedrockAgentClient? client = null, - AmazonBedrockAgentRuntimeClient? runtimeClient = null, - Kernel? kernel = null, - KernelArguments? defaultArguments = null, - CancellationToken cancellationToken = default) - { - client ??= new AmazonBedrockAgentClient(); - runtimeClient ??= new AmazonBedrockAgentRuntimeClient(); - var createAgentResponse = await client.CreateAgentAsync(request, cancellationToken).ConfigureAwait(false); - - BedrockAgent agent = new(createAgentResponse.Agent, client, runtimeClient) - { - Kernel = kernel ?? new(), - Arguments = defaultArguments ?? [], - }; - - // The agent will first enter the CREATING status. - // When the agent is created, it will enter the NOT_PREPARED status. - await agent.WaitForAgentStatusAsync(AgentStatus.NOT_PREPARED, cancellationToken).ConfigureAwait(false); - - if (enableCodeInterpreter) - { - await agent.CreateCodeInterpreterActionGroupAsync(cancellationToken).ConfigureAwait(false); - } - if (enableKernelFunctions) - { - await agent.CreateKernelFunctionActionGroupAsync(cancellationToken).ConfigureAwait(false); - } - if (enableUserInput) - { - await agent.EnableUserInputActionGroupAsync(cancellationToken).ConfigureAwait(false); - } - - // Need to prepare the agent before it can be invoked. - await agent.PrepareAsync(cancellationToken).ConfigureAwait(false); - - return agent; - } - /// /// Convenient method to create an unique session id. /// @@ -125,25 +68,7 @@ public static string CreateSessionId() #endregion - # region public methods - - /// - /// Delete the Bedrock agent from the service. - /// - /// The to monitor for cancellation requests. The default is . - public async Task DeleteAsync(CancellationToken cancellationToken) - { - await this.Client.DeleteAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Prepare the Bedrock agent for use. - /// - private async Task PrepareAsync(CancellationToken cancellationToken) - { - await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); - await this.WaitForAgentStatusAsync(AgentStatus.PREPARED, cancellationToken).ConfigureAwait(false); - } + #region public methods /// /// Invoke the Bedrock agent with the given message. @@ -151,15 +76,15 @@ private async Task PrepareAsync(CancellationToken cancellationToken) /// The session id. /// The message to send to the agent. /// The arguments to use when invoking the agent. - /// The to monitor for cancellation requests. The default is . /// The alias id of the agent to use. The default is the working draft alias id. + /// The to monitor for cancellation requests. The default is . /// An of . public IAsyncEnumerable InvokeAsync( string sessionId, string message, KernelArguments? arguments, - CancellationToken cancellationToken, - string? agentAliasId = null) + string? agentAliasId = null, + CancellationToken cancellationToken = default) { var invokeAgentRequest = new InvokeAgentRequest { @@ -181,7 +106,7 @@ public IAsyncEnumerable InvokeAsync( public IAsyncEnumerable InvokeAsync( InvokeAgentRequest invokeAgentRequest, KernelArguments? arguments, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { return invokeAgentRequest.StreamingConfigurations != null && invokeAgentRequest.StreamingConfigurations.StreamFinalResponse ? throw new ArgumentException("The streaming configuration must be null for non-streaming responses.") @@ -233,16 +158,17 @@ async IAsyncEnumerable InvokeInternal() /// Invoke the Bedrock agent with the given request and streaming response. /// /// The session id. - /// The message to send to the agent. /// The arguments to use when invoking the agent. - /// The to monitor for cancellation requests. The default is . + /// The message to send to the agent. + /// The arguments to use when invoking the agent. /// The alias id of the agent to use. The default is the working draft alias id. + /// The to monitor for cancellation requests. The default is . /// An of . public IAsyncEnumerable InvokeStreamingAsync( string sessionId, string message, KernelArguments? arguments, - CancellationToken cancellationToken, - string? agentAliasId = null) + string? agentAliasId = null, + CancellationToken cancellationToken = default) { var invokeAgentRequest = new InvokeAgentRequest { @@ -269,7 +195,7 @@ public IAsyncEnumerable InvokeStreamingAsync( public IAsyncEnumerable InvokeStreamingAsync( InvokeAgentRequest invokeAgentRequest, KernelArguments? arguments, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { if (invokeAgentRequest.StreamingConfigurations == null) { @@ -312,7 +238,7 @@ async IAsyncEnumerable InvokeInternal() /// The id of the knowledge base to associate with the agent. /// A description of what the agent should use the knowledge base for. /// The to monitor for cancellation requests. The default is . - public async Task AssociateAgentKnowledgeBaseAsync(string knowledgeBaseId, string description, CancellationToken cancellationToken) + public async Task AssociateAgentKnowledgeBaseAsync(string knowledgeBaseId, string description, CancellationToken cancellationToken = default) { await this.Client.AssociateAgentKnowledgeBaseAsync(new() { @@ -321,6 +247,8 @@ await this.Client.AssociateAgentKnowledgeBaseAsync(new() KnowledgeBaseId = knowledgeBaseId, Description = description, }, cancellationToken).ConfigureAwait(false); + + await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); } /// @@ -328,7 +256,7 @@ await this.Client.AssociateAgentKnowledgeBaseAsync(new() /// /// The id of the knowledge base to disassociate with the agent. /// The to monitor for cancellation requests. The default is . - public async Task DisassociateAgentKnowledgeBaseAsync(string knowledgeBaseId, CancellationToken cancellationToken) + public async Task DisassociateAgentKnowledgeBaseAsync(string knowledgeBaseId, CancellationToken cancellationToken = default) { await this.Client.DisassociateAgentKnowledgeBaseAsync(new() { @@ -336,6 +264,8 @@ await this.Client.DisassociateAgentKnowledgeBaseAsync(new() AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", KnowledgeBaseId = knowledgeBaseId, }, cancellationToken).ConfigureAwait(false); + + await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); } /// @@ -343,7 +273,7 @@ await this.Client.DisassociateAgentKnowledgeBaseAsync(new() /// /// The to monitor for cancellation requests. The default is . /// A containing the knowledge bases associated with the agent. - public async Task ListAssociatedKnowledgeBasesAsync(CancellationToken cancellationToken) + public async Task ListAssociatedKnowledgeBasesAsync(CancellationToken cancellationToken = default) { return await this.Client.ListAgentKnowledgeBasesAsync(new() { @@ -352,14 +282,10 @@ public async Task ListAssociatedKnowledgeBasesA }, cancellationToken).ConfigureAwait(false); } - #endregion - - #region private methods - /// - /// Create a code interpreter action group for the agent. + /// Create a code interpreter action group for the agent and prepare the agent. /// - private async Task CreateCodeInterpreterActionGroupAsync(CancellationToken cancellationToken) + public async Task CreateCodeInterpreterActionGroupAsync(CancellationToken cancellationToken = default) { var createAgentActionGroupRequest = new CreateAgentActionGroupRequest { @@ -371,12 +297,13 @@ private async Task CreateCodeInterpreterActionGroupAsync(CancellationToken cance }; await this.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); } /// - /// Create a kernel function action group for the agent. + /// Create a kernel function action group for the agent and prepare the agent. /// - private async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancellationToken) + public async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancellationToken = default) { var createAgentActionGroupRequest = new CreateAgentActionGroupRequest { @@ -392,12 +319,13 @@ private async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancel }; await this.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); } /// - /// Enable user input for the agent. + /// Enable user input for the agent and prepare the agent. /// - private async Task EnableUserInputActionGroupAsync(CancellationToken cancellationToken) + public async Task EnableUserInputActionGroupAsync(CancellationToken cancellationToken = default) { var createAgentActionGroupRequest = new CreateAgentActionGroupRequest { @@ -409,6 +337,7 @@ private async Task EnableUserInputActionGroupAsync(CancellationToken cancellatio }; await this.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); } #endregion diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs new file mode 100644 index 000000000000..b014d11d49fa --- /dev/null +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Amazon.BedrockAgent; +using Amazon.BedrockAgent.Model; + +namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; + +/// +/// Extensions associated with +/// +public static class BedrockAgentExtensions +{ + /// + /// Creates an agent. + /// + /// The instance. + /// The instance. + /// The instance. + public static async Task CreateAndPrepareAgentAsync( + this AmazonBedrockAgentClient client, + CreateAgentRequest request, + CancellationToken cancellationToken = default) + { + var createAgentResponse = await client.CreateAgentAsync(request, cancellationToken).ConfigureAwait(false); + // The agent will first enter the CREATING status. + // When the operation finishes, it will enter the NOT_PREPARED status. + // We need to wait for the agent to reach the NOT_PREPARED status before we can prepare it. + await client.WaitForAgentStatusAsync(createAgentResponse.Agent, AgentStatus.NOT_PREPARED, cancellationToken: cancellationToken).ConfigureAwait(false); + return await client.PrepareAgentAsync(createAgentResponse.Agent, cancellationToken).ConfigureAwait(false); + } + + private static async Task PrepareAgentAsync( + this AmazonBedrockAgentClient client, + Amazon.BedrockAgent.Model.Agent agent, + CancellationToken cancellationToken = default) + { + var prepareAgentResponse = await client.PrepareAgentAsync(new() { AgentId = agent.AgentId }, cancellationToken).ConfigureAwait(false); + + // The agent will enter the PREPARING status. + // When the agent is prepared, it will enter the PREPARED status. + return await client.WaitForAgentStatusAsync(agent, AgentStatus.PREPARED, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// Wait for the agent to reach the specified status. + /// + /// The instance. + /// The to monitor. + /// The status to wait for. + /// The interval in seconds to wait between attempts. The default is 2 seconds. + /// The maximum number of attempts to make. The default is 5 attempts. + /// The to monitor for cancellation requests. + /// The instance. + private static async Task WaitForAgentStatusAsync( + this AmazonBedrockAgentClient client, + Amazon.BedrockAgent.Model.Agent agent, + AgentStatus status, + int interval = 2, + int maxAttempts = 5, + CancellationToken cancellationToken = default) + { + for (var i = 0; i < maxAttempts; i++) + { + var getAgentResponse = await client.GetAgentAsync(new() { AgentId = agent.AgentId }, cancellationToken).ConfigureAwait(false); + + if (getAgentResponse.Agent.AgentStatus == status) + { + return getAgentResponse.Agent; + } + + await Task.Delay(interval * 1000, cancellationToken).ConfigureAwait(false); + } + + throw new TimeoutException($"Agent did not reach status {status} within the specified time."); + } +} diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs deleted file mode 100644 index b91b88baa438..000000000000 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentStatusExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Amazon.BedrockAgent; - -namespace Microsoft.SemanticKernel.Agents.Bedrock.Extensions; - -/// -/// Extensions associated with the status of a . -/// -internal static class BedrockAgentStatusExtensions -{ - /// - /// Wait for the agent to reach the specified status. - /// - /// The to monitor. - /// The status to wait for. - /// The to monitor for cancellation requests. - /// The interval in seconds to wait between attempts. The default is 2 seconds. - /// The maximum number of attempts to make. The default is 5 attempts. - public static async Task WaitForAgentStatusAsync( - this BedrockAgent agent, - AgentStatus status, - CancellationToken cancellationToken, - int interval = 2, - int maxAttempts = 5) - { - for (var i = 0; i < maxAttempts; i++) - { - var getAgentResponse = await agent.Client.GetAgentAsync(new() { AgentId = agent.Id }, cancellationToken).ConfigureAwait(false); - - if (getAgentResponse.Agent.AgentStatus == status) - { - return; - } - - await Task.Delay(interval * 1000, cancellationToken).ConfigureAwait(false); - } - - throw new TimeoutException($"Agent did not reach status {status} within the specified time."); - } -} diff --git a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs index 65b2695e64b5..0a41c9c5778c 100644 --- a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs +++ b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseBedrockAgentTest.cs @@ -1,15 +1,22 @@ // Copyright (c) Microsoft. All rights reserved. +using Amazon.BedrockAgent; using Amazon.BedrockAgent.Model; using Microsoft.SemanticKernel.Agents.Bedrock; /// /// Base class for samples that demonstrate the usage of AWS Bedrock agents. /// -public abstract class BaseBedrockAgentTest(ITestOutputHelper output) : BaseTest(output, redirectSystemConsoleOutput: true) +public abstract class BaseBedrockAgentTest : BaseTest { protected const string AgentDescription = "A helpful assistant who helps users find information."; protected const string AgentInstruction = "You're a helpful assistant who helps users find information."; + protected readonly AmazonBedrockAgentClient Client; + + protected BaseBedrockAgentTest(ITestOutputHelper output) : base(output, redirectSystemConsoleOutput: true) + { + Client = new AmazonBedrockAgentClient(); + } protected CreateAgentRequest GetCreateAgentRequest(string agentName) => new() { @@ -20,8 +27,13 @@ public abstract class BaseBedrockAgentTest(ITestOutputHelper output) : BaseTest( FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, }; + protected override void Dispose(bool disposing) + { + Client?.Dispose(); + base.Dispose(disposing); + } + /// - /// Create a new instance. /// Override this method to create an agent with desired settings. /// /// The name of the agent to create. Must be unique. diff --git a/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs b/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs index c7b1de285754..d7424eca571a 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Contents/BinaryContent.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Text; using System.Text.Json.Serialization; using Microsoft.SemanticKernel.Text; @@ -119,31 +118,6 @@ public BinaryContent( this.Data = data; } - /// - /// Write the binary content to a file. - /// - /// The path to the file to write the content to. - /// Whether to overwrite the file if it already exists. - public void WriteToFile(string filePath, bool overwrite = false) - { - if (string.IsNullOrWhiteSpace(filePath)) - { - throw new ArgumentException("File path cannot be null or empty", nameof(filePath)); - } - - if (!overwrite && File.Exists(filePath)) - { - throw new InvalidOperationException("File already exists."); - } - - if (!this.CanRead) - { - throw new InvalidOperationException("No content to write to file."); - } - - File.WriteAllBytes(filePath, this.Data!.Value.ToArray()); - } - #region Private /// /// Sets the Uri of the content. diff --git a/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtenstions.cs b/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtenstions.cs new file mode 100644 index 000000000000..c753057ca53a --- /dev/null +++ b/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtenstions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.IO; + +namespace Microsoft.SemanticKernel; + +/// +/// Provides extension methods for interacting with . +/// +public static class BinaryContentExtensions +{ + /// + /// Writes the content to a file. + /// + /// The content to write. + /// The path to the file to write to. + /// Whether to overwrite the file if it already exists. + public static void WriteToFile(this BinaryContent content, string filePath, bool overwrite = false) + { + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new ArgumentException("File path cannot be null or empty", nameof(filePath)); + } + + if (!overwrite && File.Exists(filePath)) + { + throw new InvalidOperationException("File already exists."); + } + + if (!content.CanRead) + { + throw new InvalidOperationException("No content to write to file."); + } + + File.WriteAllBytes(filePath, content.Data!.Value.ToArray()); + } +} \ No newline at end of file From 1914b7a05f7adfb8bff278fa6cd785815b4819b5 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 18 Feb 2025 17:28:12 -0800 Subject: [PATCH 23/27] Fix pipeline --- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 1 - .../{BinaryContentExtenstions.cs => BinaryContentExtensions.cs} | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) rename dotnet/src/SemanticKernel.Core/Contents/{BinaryContentExtenstions.cs => BinaryContentExtensions.cs} (95%) diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index 427c52f44066..e90e9723816a 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Amazon.BedrockAgent; diff --git a/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtenstions.cs b/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtensions.cs similarity index 95% rename from dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtenstions.cs rename to dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtensions.cs index c753057ca53a..941adf41ba83 100644 --- a/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtenstions.cs +++ b/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System; using System.IO; From ab212e50fcd7a410e4487d929a1beeb797494034 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 18 Feb 2025 17:34:58 -0800 Subject: [PATCH 24/27] Fix pipeline --- dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs index b014d11d49fa..2a86ea523bdc 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Threading; From 7c560bf21ecadc453f84584e24504952c7674cfc Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 18 Feb 2025 17:40:57 -0800 Subject: [PATCH 25/27] Fix pipeline --- .../src/SemanticKernel.Core/Contents/BinaryContentExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtensions.cs b/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtensions.cs index 941adf41ba83..f0d8b29ae280 100644 --- a/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtensions.cs +++ b/dotnet/src/SemanticKernel.Core/Contents/BinaryContentExtensions.cs @@ -35,4 +35,4 @@ public static void WriteToFile(this BinaryContent content, string filePath, bool File.WriteAllBytes(filePath, content.Data!.Value.ToArray()); } -} \ No newline at end of file +} From dd90d28dc94f8e4ac1c5cecbc6f00e2fa767e350 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 19 Feb 2025 09:51:42 -0800 Subject: [PATCH 26/27] Uncomment test code --- .../BedrockAgent/Step01_BedrockAgent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs index 6791ede89406..2c4aa4355097 100644 --- a/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs +++ b/dotnet/samples/GettingStartedWithAgents/BedrockAgent/Step01_BedrockAgent.cs @@ -33,7 +33,7 @@ public async Task UseNewAgentAsync() } finally { - // await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); + await this.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id }); } } From 4bc6141f28a58614cec2d014c6152ce8c3c38a3f Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 19 Feb 2025 12:12:22 -0800 Subject: [PATCH 27/27] Address comments 3rd batch --- dotnet/src/Agents/Bedrock/BedrockAgent.cs | 109 -------------- .../src/Agents/Bedrock/BedrockAgentChannel.cs | 112 ++++++++------ .../Extensions/BedrockAgentExtensions.cs | 141 +++++++++++++++++- 3 files changed, 202 insertions(+), 160 deletions(-) diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index e90e9723816a..31f199541c6a 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -5,7 +5,6 @@ using System.Threading; using System.Threading.Tasks; using Amazon.BedrockAgent; -using Amazon.BedrockAgent.Model; using Amazon.BedrockAgentRuntime; using Amazon.BedrockAgentRuntime.Model; using Microsoft.SemanticKernel.Agents.Bedrock.Extensions; @@ -231,114 +230,6 @@ async IAsyncEnumerable InvokeInternal() } } - /// - /// Associate the agent with a knowledge base. - /// - /// The id of the knowledge base to associate with the agent. - /// A description of what the agent should use the knowledge base for. - /// The to monitor for cancellation requests. The default is . - public async Task AssociateAgentKnowledgeBaseAsync(string knowledgeBaseId, string description, CancellationToken cancellationToken = default) - { - await this.Client.AssociateAgentKnowledgeBaseAsync(new() - { - AgentId = this.Id, - AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", - KnowledgeBaseId = knowledgeBaseId, - Description = description, - }, cancellationToken).ConfigureAwait(false); - - await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Disassociate the agent with a knowledge base. - /// - /// The id of the knowledge base to disassociate with the agent. - /// The to monitor for cancellation requests. The default is . - public async Task DisassociateAgentKnowledgeBaseAsync(string knowledgeBaseId, CancellationToken cancellationToken = default) - { - await this.Client.DisassociateAgentKnowledgeBaseAsync(new() - { - AgentId = this.Id, - AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", - KnowledgeBaseId = knowledgeBaseId, - }, cancellationToken).ConfigureAwait(false); - - await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); - } - - /// - /// List the knowledge bases associated with the agent. - /// - /// The to monitor for cancellation requests. The default is . - /// A containing the knowledge bases associated with the agent. - public async Task ListAssociatedKnowledgeBasesAsync(CancellationToken cancellationToken = default) - { - return await this.Client.ListAgentKnowledgeBasesAsync(new() - { - AgentId = this.Id, - AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", - }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Create a code interpreter action group for the agent and prepare the agent. - /// - public async Task CreateCodeInterpreterActionGroupAsync(CancellationToken cancellationToken = default) - { - var createAgentActionGroupRequest = new CreateAgentActionGroupRequest - { - AgentId = this.Id, - AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", - ActionGroupName = this.CodeInterpreterActionGroupSignature, - ActionGroupState = ActionGroupState.ENABLED, - ParentActionGroupSignature = new(Amazon.BedrockAgent.ActionGroupSignature.AMAZONCodeInterpreter), - }; - - await this.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); - await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Create a kernel function action group for the agent and prepare the agent. - /// - public async Task CreateKernelFunctionActionGroupAsync(CancellationToken cancellationToken = default) - { - var createAgentActionGroupRequest = new CreateAgentActionGroupRequest - { - AgentId = this.Id, - AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", - ActionGroupName = this.KernelFunctionActionGroupSignature, - ActionGroupState = ActionGroupState.ENABLED, - ActionGroupExecutor = new() - { - CustomControl = Amazon.BedrockAgent.CustomControlMethod.RETURN_CONTROL, - }, - FunctionSchema = this.Kernel.ToFunctionSchema(), - }; - - await this.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); - await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Enable user input for the agent and prepare the agent. - /// - public async Task EnableUserInputActionGroupAsync(CancellationToken cancellationToken = default) - { - var createAgentActionGroupRequest = new CreateAgentActionGroupRequest - { - AgentId = this.Id, - AgentVersion = this.AgentModel.AgentVersion ?? "DRAFT", - ActionGroupName = this.UseInputActionGroupSignature, - ActionGroupState = ActionGroupState.ENABLED, - ParentActionGroupSignature = new(Amazon.BedrockAgent.ActionGroupSignature.AMAZONUserInput), - }; - - await this.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); - await this.Client.PrepareAgentAsync(new() { AgentId = this.Id }, cancellationToken).ConfigureAwait(false); - } - #endregion /// diff --git a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs index 840da11c9862..1e0d40d91188 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -63,71 +64,67 @@ protected override Task ReceiveAsync(IEnumerable history, Ca } /// - protected override IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync( + protected override async IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync( BedrockAgent agent, - CancellationToken cancellationToken) + [EnumeratorCancellation] CancellationToken cancellationToken) { - return this._history.Count == 0 ? throw new InvalidOperationException("No messages to send.") : InvokeInternalAsync(); + if (!this.PrepareAndValidateHistory()) + { + yield break; + } - async IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeInternalAsync() + InvokeAgentRequest invokeAgentRequest = new() { - this.EnsureHistoryAlternates(); - this.EnsureLastMessageIsUser(); - InvokeAgentRequest invokeAgentRequest = new() - { - AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, - AgentId = agent.Id, - SessionId = BedrockAgent.CreateSessionId(), - InputText = this._history.Last().Content ?? throw new InvalidOperationException("Message content cannot be null."), - SessionState = this.ParseHistoryToSessionState(), - }; - await foreach (var message in agent.InvokeAsync(invokeAgentRequest, null, cancellationToken).ConfigureAwait(false)) + AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, + AgentId = agent.Id, + SessionId = BedrockAgent.CreateSessionId(), + InputText = this._history.Last().Content, + SessionState = this.ParseHistoryToSessionState(), + }; + await foreach (var message in agent.InvokeAsync(invokeAgentRequest, null, cancellationToken).ConfigureAwait(false)) + { + if (message.Content is not null) { - if (message.Content is not null) - { - this._history.Add(message); - // All messages from Bedrock agents are user facing, i.e., function calls are not returned as messages - yield return (true, message); - } + this._history.Add(message); + // All messages from Bedrock agents are user facing, i.e., function calls are not returned as messages + yield return (true, message); } } } /// - protected override IAsyncEnumerable InvokeStreamingAsync( + protected override async IAsyncEnumerable InvokeStreamingAsync( BedrockAgent agent, IList messages, - CancellationToken cancellationToken) + [EnumeratorCancellation] CancellationToken cancellationToken) { - return this._history.Count == 0 ? throw new InvalidOperationException("No messages to send.") : InvokeInternalAsync(); + if (!this.PrepareAndValidateHistory()) + { + yield break; + } - async IAsyncEnumerable InvokeInternalAsync() + InvokeAgentRequest invokeAgentRequest = new() { - this.EnsureHistoryAlternates(); - this.EnsureLastMessageIsUser(); - InvokeAgentRequest invokeAgentRequest = new() - { - AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, - AgentId = agent.Id, - SessionId = BedrockAgent.CreateSessionId(), - InputText = this._history.Last().Content ?? throw new InvalidOperationException("Message content cannot be null."), - SessionState = this.ParseHistoryToSessionState(), - }; - await foreach (var message in agent.InvokeStreamingAsync(invokeAgentRequest, null, cancellationToken).ConfigureAwait(false)) + AgentAliasId = BedrockAgent.WorkingDraftAgentAlias, + AgentId = agent.Id, + SessionId = BedrockAgent.CreateSessionId(), + InputText = this._history.Last().Content, + SessionState = this.ParseHistoryToSessionState(), + }; + await foreach (var message in agent.InvokeStreamingAsync(invokeAgentRequest, null, cancellationToken).ConfigureAwait(false)) + { + if (message.Content is not null) { - if (message.Content is not null) + this._history.Add(new() { - this._history.Add(new() - { - Role = AuthorRole.Assistant, - Content = message.Content, - AuthorName = message.AuthorName, - InnerContent = message.InnerContent, - ModelId = message.ModelId, - }); - // All messages from Bedrock agents are user facing, i.e., function calls are not returned as messages - yield return message; - } + Role = AuthorRole.Assistant, + Content = message.Content, + AuthorName = message.AuthorName, + InnerContent = message.InnerContent, + ModelId = message.ModelId, + }); + // All messages from Bedrock agents are user facing, i.e., function calls are not returned as messages + yield return message; } } } @@ -152,6 +149,25 @@ protected override string Serialize() #region private methods + private bool PrepareAndValidateHistory() + { + if (this._history.Count == 0) + { + this.Logger.LogWarning("No messages to send. Bedrock requires at least one message to start a conversation."); + return false; + } + + this.EnsureHistoryAlternates(); + this.EnsureLastMessageIsUser(); + if (string.IsNullOrEmpty(this._history.Last().Content)) + { + this.Logger.LogWarning("Last message has no content. Bedrock doesn't support empty messages."); + return false; + } + + return true; + } + private void EnsureHistoryAlternates() { if (this._history.Count <= 1) diff --git a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs index 2a86ea523bdc..c2e6bdd358bb 100644 --- a/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs +++ b/dotnet/src/Agents/Bedrock/Extensions/BedrockAgentExtensions.cs @@ -29,17 +29,152 @@ public static class BedrockAgentExtensions // When the operation finishes, it will enter the NOT_PREPARED status. // We need to wait for the agent to reach the NOT_PREPARED status before we can prepare it. await client.WaitForAgentStatusAsync(createAgentResponse.Agent, AgentStatus.NOT_PREPARED, cancellationToken: cancellationToken).ConfigureAwait(false); - return await client.PrepareAgentAsync(createAgentResponse.Agent, cancellationToken).ConfigureAwait(false); + return await client.PrepareAgentAndWaitUntilPreparedAsync(createAgentResponse.Agent, cancellationToken).ConfigureAwait(false); } - private static async Task PrepareAgentAsync( + /// + /// Associates an agent with a knowledge base. + /// + /// The instance. + /// The knowledge base ID. + /// The description of the association. + /// The instance. + public static async Task AssociateAgentKnowledgeBaseAsync( + this BedrockAgent agent, + string knowledgeBaseId, + string description, + CancellationToken cancellationToken = default) + { + await agent.Client.AssociateAgentKnowledgeBaseAsync(new() + { + AgentId = agent.Id, + AgentVersion = agent.AgentModel.AgentVersion ?? "DRAFT", + KnowledgeBaseId = knowledgeBaseId, + Description = description, + }, cancellationToken).ConfigureAwait(false); + + await agent.Client.PrepareAgentAndWaitUntilPreparedAsync(agent.AgentModel, cancellationToken).ConfigureAwait(false); + } + + /// + /// Disassociate the agent with a knowledge base. + /// + /// The instance. + /// The id of the knowledge base to disassociate with the agent. + /// The to monitor for cancellation requests. The default is . + public static async Task DisassociateAgentKnowledgeBaseAsync( + this BedrockAgent agent, + string knowledgeBaseId, + CancellationToken cancellationToken = default) + { + await agent.Client.DisassociateAgentKnowledgeBaseAsync(new() + { + AgentId = agent.Id, + AgentVersion = agent.AgentModel.AgentVersion ?? "DRAFT", + KnowledgeBaseId = knowledgeBaseId, + }, cancellationToken).ConfigureAwait(false); + + await agent.Client.PrepareAgentAndWaitUntilPreparedAsync(agent.AgentModel, cancellationToken).ConfigureAwait(false); + } + + /// + /// List the knowledge bases associated with the agent. + /// + /// The instance. + /// The to monitor for cancellation requests. The default is . + /// A containing the knowledge bases associated with the agent. + public static async Task ListAssociatedKnowledgeBasesAsync( + this BedrockAgent agent, + CancellationToken cancellationToken = default) + { + return await agent.Client.ListAgentKnowledgeBasesAsync(new() + { + AgentId = agent.Id, + AgentVersion = agent.AgentModel.AgentVersion ?? "DRAFT", + }, cancellationToken).ConfigureAwait(false); + } + + /// + /// Create a code interpreter action group for the agent and prepare the agent. + /// + /// The instance. + /// The to monitor for cancellation requests. The default is . + public static async Task CreateCodeInterpreterActionGroupAsync( + this BedrockAgent agent, + CancellationToken cancellationToken = default) + { + var createAgentActionGroupRequest = new CreateAgentActionGroupRequest + { + AgentId = agent.Id, + AgentVersion = agent.AgentModel.AgentVersion ?? "DRAFT", + ActionGroupName = agent.CodeInterpreterActionGroupSignature, + ActionGroupState = ActionGroupState.ENABLED, + ParentActionGroupSignature = new(Amazon.BedrockAgent.ActionGroupSignature.AMAZONCodeInterpreter), + }; + + await agent.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + await agent.Client.PrepareAgentAndWaitUntilPreparedAsync(agent.AgentModel, cancellationToken).ConfigureAwait(false); + } + + /// + /// Create a kernel function action group for the agent and prepare the agent. + /// + /// The instance. + /// The to monitor for cancellation requests. The default is . + public static async Task CreateKernelFunctionActionGroupAsync( + this BedrockAgent agent, + CancellationToken cancellationToken = default) + { + var createAgentActionGroupRequest = new CreateAgentActionGroupRequest + { + AgentId = agent.Id, + AgentVersion = agent.AgentModel.AgentVersion ?? "DRAFT", + ActionGroupName = agent.KernelFunctionActionGroupSignature, + ActionGroupState = ActionGroupState.ENABLED, + ActionGroupExecutor = new() + { + CustomControl = Amazon.BedrockAgent.CustomControlMethod.RETURN_CONTROL, + }, + FunctionSchema = agent.Kernel.ToFunctionSchema(), + }; + + await agent.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + await agent.Client.PrepareAgentAndWaitUntilPreparedAsync(agent.AgentModel, cancellationToken).ConfigureAwait(false); + } + + /// + /// Enable user input for the agent and prepare the agent. + /// + /// The instance. + /// The to monitor for cancellation requests. The default is . + public static async Task EnableUserInputActionGroupAsync( + this BedrockAgent agent, + CancellationToken cancellationToken = default) + { + var createAgentActionGroupRequest = new CreateAgentActionGroupRequest + { + AgentId = agent.Id, + AgentVersion = agent.AgentModel.AgentVersion ?? "DRAFT", + ActionGroupName = agent.UseInputActionGroupSignature, + ActionGroupState = ActionGroupState.ENABLED, + ParentActionGroupSignature = new(Amazon.BedrockAgent.ActionGroupSignature.AMAZONUserInput), + }; + + await agent.Client.CreateAgentActionGroupAsync(createAgentActionGroupRequest, cancellationToken).ConfigureAwait(false); + await agent.Client.PrepareAgentAndWaitUntilPreparedAsync(agent.AgentModel, cancellationToken).ConfigureAwait(false); + } + + private static async Task PrepareAgentAndWaitUntilPreparedAsync( this AmazonBedrockAgentClient client, Amazon.BedrockAgent.Model.Agent agent, CancellationToken cancellationToken = default) { var prepareAgentResponse = await client.PrepareAgentAsync(new() { AgentId = agent.AgentId }, cancellationToken).ConfigureAwait(false); - // The agent will enter the PREPARING status. + // The agent will take some time to enter the PREPARING status after the prepare operation is called. + // We need to wait for the agent to reach the PREPARING status before we can proceed, otherwise we + // will return immediately if the agent is already in PREPARED status. + await client.WaitForAgentStatusAsync(agent, AgentStatus.PREPARING, cancellationToken: cancellationToken).ConfigureAwait(false); // When the agent is prepared, it will enter the PREPARED status. return await client.WaitForAgentStatusAsync(agent, AgentStatus.PREPARED, cancellationToken: cancellationToken).ConfigureAwait(false); }