diff --git a/dotnet/src/Agents/Abstractions/Agent.cs b/dotnet/src/Agents/Abstractions/Agent.cs index 833bea6b4987..5f21f83bb6c0 100644 --- a/dotnet/src/Agents/Abstractions/Agent.cs +++ b/dotnet/src/Agents/Abstractions/Agent.cs @@ -384,4 +384,38 @@ protected Task NotifyThreadOfNewMessage(AgentThread thread, ChatMessageContent m { return thread.OnNewMessageAsync(message, cancellationToken); } + + /// + /// Default formatting for additional instructions for the AI agent based on the provided context and invocation options. + /// + /// The context containing relevant information for the AI agent's operation. + /// Optional parameters that influence the invocation behavior. Can be . + /// A formatted string representing the additional instructions for the AI agent. +#pragma warning disable SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + protected static string FormatAdditionalInstructions(AIContext context, AgentInvokeOptions? options) +#pragma warning restore SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + { + return string.Concat(ProcessInstructions()); + + IEnumerable ProcessInstructions() + { + bool hasInstructions = false; + if (options?.AdditionalInstructions is not null) + { + yield return options!.AdditionalInstructions; + hasInstructions = true; + } + + if (!string.IsNullOrWhiteSpace(context.Instructions)) + { + if (hasInstructions) + { + yield return Environment.NewLine; + yield return Environment.NewLine; + } + + yield return context.Instructions!; + } + } + } } diff --git a/dotnet/src/Agents/AzureAI/AzureAIAgent.cs b/dotnet/src/Agents/AzureAI/AzureAIAgent.cs index eeab8434a682..a942eae8ba73 100644 --- a/dotnet/src/Agents/AzureAI/AzureAIAgent.cs +++ b/dotnet/src/Agents/AzureAI/AzureAIAgent.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; @@ -131,21 +130,21 @@ public async IAsyncEnumerable> InvokeAsync { Verify.NotNull(messages); - var azureAIAgentThread = await this.EnsureThreadExistsWithMessagesAsync( + AzureAIAgentThread azureAIAgentThread = await this.EnsureThreadExistsWithMessagesAsync( messages, thread, () => new AzureAIAgentThread(this.Client), cancellationToken).ConfigureAwait(false); - var kernel = (options?.Kernel ?? this.Kernel).Clone(); + Kernel kernel = (options?.Kernel ?? this.Kernel).Clone(); // Get the context contributions from the AIContextProviders. -#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var providersContext = await azureAIAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); +#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + AIContext providersContext = await azureAIAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); kernel.Plugins.AddFromAIContext(providersContext, "Tools"); -#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var mergedAdditionalInstructions = MergeAdditionalInstructions(options?.AdditionalInstructions, providersContext.Instructions); + string mergedAdditionalInstructions = FormatAdditionalInstructions(providersContext, options); var extensionsContextOptions = options is null ? new AzureAIAgentInvokeOptions() { AdditionalInstructions = mergedAdditionalInstructions } : new AzureAIAgentInvokeOptions(options) { AdditionalInstructions = mergedAdditionalInstructions }; @@ -223,7 +222,7 @@ public async IAsyncEnumerable> In { Verify.NotNull(messages); - var azureAIAgentThread = await this.EnsureThreadExistsWithMessagesAsync( + AzureAIAgentThread azureAIAgentThread = await this.EnsureThreadExistsWithMessagesAsync( messages, thread, () => new AzureAIAgentThread(this.Client), @@ -232,19 +231,19 @@ public async IAsyncEnumerable> In var kernel = (options?.Kernel ?? this.Kernel).Clone(); // Get the context contributions from the AIContextProviders. -#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var providersContext = await azureAIAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); +#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + AIContext providersContext = await azureAIAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); kernel.Plugins.AddFromAIContext(providersContext, "Tools"); -#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var mergedAdditionalInstructions = MergeAdditionalInstructions(options?.AdditionalInstructions, providersContext.Instructions); + string mergedAdditionalInstructions = FormatAdditionalInstructions(providersContext, options); var extensionsContextOptions = options is null ? new AzureAIAgentInvokeOptions() { AdditionalInstructions = mergedAdditionalInstructions } : new AzureAIAgentInvokeOptions(options) { AdditionalInstructions = mergedAdditionalInstructions }; // Invoke the Agent with the thread that we already added our message to, and with // a chat history to receive complete messages. - var newMessagesReceiver = new ChatHistory(); + ChatHistory newMessagesReceiver = []; var invokeResults = ActivityExtensions.RunWithActivityAsync( () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), () => AgentThreadActions.InvokeStreamingAsync( @@ -324,19 +323,4 @@ protected override async Task RestoreChannelAsync(string channelSt return new AzureAIChannel(this.Client, thread.Id); } - - private static string MergeAdditionalInstructions(string? optionsAdditionalInstructions, string? extensionsContext) => - (optionsAdditionalInstructions, extensionsContext) switch - { - (string ai, string ec) when !string.IsNullOrWhiteSpace(ai) && !string.IsNullOrWhiteSpace(ec) => string.Concat( - ai, - Environment.NewLine, - Environment.NewLine, - ec), - (string ai, string ec) when string.IsNullOrWhiteSpace(ai) => ec, - (string ai, string ec) when string.IsNullOrWhiteSpace(ec) => ai, - (null, string ec) => ec, - (string ai, null) => ai, - _ => string.Empty - }; } diff --git a/dotnet/src/Agents/Bedrock/BedrockAgent.cs b/dotnet/src/Agents/Bedrock/BedrockAgent.cs index 2a34e5c0503f..815ec1a859f3 100644 --- a/dotnet/src/Agents/Bedrock/BedrockAgent.cs +++ b/dotnet/src/Agents/Bedrock/BedrockAgent.cs @@ -121,16 +121,16 @@ public override async IAsyncEnumerable> In cancellationToken).ConfigureAwait(false); // Get the context contributions from the AIContextProviders. -#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var providersContext = await bedrockThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); -#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + AIContext providersContext = await bedrockThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); +#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. // Ensure that the last message provided is a user message string message = this.ExtractUserMessage(messages.Last()); // Build session state with conversation history and override instructions if needed SessionState sessionState = this.ExtractSessionState(messages); - var mergedAdditionalInstructions = MergeAdditionalInstructions(options?.AdditionalInstructions, providersContext.Instructions); + string mergedAdditionalInstructions = FormatAdditionalInstructions(providersContext, options); sessionState.PromptSessionAttributes = new() { [AdditionalInstructionsSessionAttributeName] = mergedAdditionalInstructions }; // Configure the agent request with the provided options @@ -196,7 +196,7 @@ public async IAsyncEnumerable> InvokeAsync thread = new BedrockAgentThread(this.RuntimeClient, invokeAgentRequest.SessionId); } - var bedrockThread = await this.EnsureThreadExistsWithMessagesAsync( + BedrockAgentThread bedrockThread = await this.EnsureThreadExistsWithMessagesAsync( [], thread, () => new BedrockAgentThread(this.RuntimeClient), @@ -255,23 +255,23 @@ public override async IAsyncEnumerable new BedrockAgentThread(this.RuntimeClient), cancellationToken).ConfigureAwait(false); // Get the context contributions from the AIContextProviders. -#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var providersContext = await bedrockThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); -#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + AIContext providersContext = await bedrockThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); +#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. // Ensure that the last message provided is a user message string? message = this.ExtractUserMessage(messages.Last()); // Build session state with conversation history and override instructions if needed SessionState sessionState = this.ExtractSessionState(messages); - var mergedAdditionalInstructions = MergeAdditionalInstructions(options?.AdditionalInstructions, providersContext.Instructions); + string mergedAdditionalInstructions = FormatAdditionalInstructions(providersContext, options); sessionState.PromptSessionAttributes = new() { [AdditionalInstructionsSessionAttributeName] = mergedAdditionalInstructions }; // Configure the agent request with the provided options @@ -579,20 +579,5 @@ private Amazon.BedrockAgentRuntime.ConversationRole MapBedrockAgentUser(AuthorRo throw new ArgumentOutOfRangeException($"Invalid role: {authorRole}"); } - private static string MergeAdditionalInstructions(string? optionsAdditionalInstructions, string? extensionsContext) => - (optionsAdditionalInstructions, extensionsContext) switch - { - (string ai, string ec) when !string.IsNullOrWhiteSpace(ai) && !string.IsNullOrWhiteSpace(ec) => string.Concat( - ai, - Environment.NewLine, - Environment.NewLine, - ec), - (string ai, string ec) when string.IsNullOrWhiteSpace(ai) => ec, - (string ai, string ec) when string.IsNullOrWhiteSpace(ec) => ai, - (null, string ec) => ec, - (string ai, null) => ai, - _ => string.Empty - }; - #endregion } diff --git a/dotnet/src/Agents/Core/ChatCompletionAgent.cs b/dotnet/src/Agents/Core/ChatCompletionAgent.cs index 85cebf8fa4c0..56c8712ab50f 100644 --- a/dotnet/src/Agents/Core/ChatCompletionAgent.cs +++ b/dotnet/src/Agents/Core/ChatCompletionAgent.cs @@ -67,22 +67,22 @@ public override async IAsyncEnumerable> In { Verify.NotNull(messages); - var chatHistoryAgentThread = await this.EnsureThreadExistsWithMessagesAsync( + ChatHistoryAgentThread chatHistoryAgentThread = await this.EnsureThreadExistsWithMessagesAsync( messages, thread, () => new ChatHistoryAgentThread(), cancellationToken).ConfigureAwait(false); - var kernel = (options?.Kernel ?? this.Kernel).Clone(); + Kernel kernel = (options?.Kernel ?? this.Kernel).Clone(); // Get the context contributions from the AIContextProviders. -#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var providersContext = await chatHistoryAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); +#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + AIContext providersContext = await chatHistoryAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); kernel.Plugins.AddFromAIContext(providersContext, "Tools"); -#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. // Invoke Chat Completion with the updated chat history. - var chatHistory = new ChatHistory(); + ChatHistory chatHistory = []; await foreach (var existingMessage in chatHistoryAgentThread.GetMessagesAsync(cancellationToken).ConfigureAwait(false)) { chatHistory.Add(existingMessage); @@ -100,9 +100,7 @@ public override async IAsyncEnumerable> In }, options?.KernelArguments, kernel, - options?.AdditionalInstructions == null ? - providersContext.Instructions : - string.Concat(options.AdditionalInstructions, Environment.NewLine, Environment.NewLine, providersContext.Instructions), + FormatAdditionalInstructions(providersContext, options), cancellationToken); // Notify the thread of new messages and return them to the caller. @@ -164,22 +162,22 @@ public override async IAsyncEnumerable new ChatHistoryAgentThread(), cancellationToken).ConfigureAwait(false); - var kernel = (options?.Kernel ?? this.Kernel).Clone(); + Kernel kernel = (options?.Kernel ?? this.Kernel).Clone(); // Get the context contributions from the AIContextProviders. -#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var providersContext = await chatHistoryAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); +#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + AIContext providersContext = await chatHistoryAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); kernel.Plugins.AddFromAIContext(providersContext, "Tools"); -#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. // Invoke Chat Completion with the updated chat history. - var chatHistory = new ChatHistory(); + ChatHistory chatHistory = []; await foreach (var existingMessage in chatHistoryAgentThread.GetMessagesAsync(cancellationToken).ConfigureAwait(false)) { chatHistory.Add(existingMessage); @@ -198,9 +196,7 @@ public override async IAsyncEnumerable> InvokeAsync { Verify.NotNull(messages); - var openAIAssistantAgentThread = await this.EnsureThreadExistsWithMessagesAsync( + OpenAIAssistantAgentThread openAIAssistantAgentThread = await this.EnsureThreadExistsWithMessagesAsync( messages, thread, () => new OpenAIAssistantAgentThread(this.Client), @@ -138,13 +138,13 @@ public async IAsyncEnumerable> InvokeAsync AdditionalInstructions = options?.AdditionalInstructions, }); - var kernel = (options?.Kernel ?? this.Kernel).Clone(); + Kernel kernel = (options?.Kernel ?? this.Kernel).Clone(); // Get the context contributions from the AIContextProviders. -#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var providersContext = await openAIAssistantAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); +#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + AIContext providersContext = await openAIAssistantAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); kernel.Plugins.AddFromAIContext(providersContext, "Tools"); -#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var invokeResults = ActivityExtensions.RunWithActivityAsync( () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), @@ -220,19 +220,19 @@ public async IAsyncEnumerable> In { Verify.NotNull(messages); - var openAIAssistantAgentThread = await this.EnsureThreadExistsWithMessagesAsync( + OpenAIAssistantAgentThread openAIAssistantAgentThread = await this.EnsureThreadExistsWithMessagesAsync( messages, thread, () => new OpenAIAssistantAgentThread(this.Client), cancellationToken).ConfigureAwait(false); - var kernel = (options?.Kernel ?? this.Kernel).Clone(); + Kernel kernel = (options?.Kernel ?? this.Kernel).Clone(); // Get the context contributions from the AIContextProviders. -#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var providersContext = await openAIAssistantAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); +#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + AIContext providersContext = await openAIAssistantAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false); kernel.Plugins.AddFromAIContext(providersContext, "Tools"); -#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. // Create options that use the RunCreationOptions from the options param if provided or // falls back to creating a new RunCreationOptions if additional instructions is provided @@ -243,7 +243,7 @@ public async IAsyncEnumerable> In }); #pragma warning disable SKEXP0001 // ModelDiagnostics is marked experimental. - var newMessagesReceiver = new ChatHistory(); + ChatHistory newMessagesReceiver = []; var invokeResults = ActivityExtensions.RunWithActivityAsync( () => ModelDiagnostics.StartAgentInvocationActivity(this.Id, this.GetDisplayName(), this.Description), () => InternalInvokeStreamingAsync(),