From 2abaec8682bb17dd8d11029f38b68d857770ffa5 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 3 Jul 2024 10:51:03 -0700 Subject: [PATCH 001/121] Checkpoint --- dotnet/samples/Concepts/Concepts.csproj | 13 +- .../GettingStartedWithAgents.csproj | 2 +- .../GettingStartedWithAgents/Step2_Plugins.cs | 4 +- .../Step6_DependencyInjection.cs | 6 +- .../Step8_OpenAIAssistant.cs | 4 +- dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj | 3 +- .../OpenAI/Extensions/AuthorRoleExtensions.cs | 2 +- .../Extensions/KernelFunctionExtensions.cs | 2 +- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 192 ++++++++++-------- .../Agents/OpenAI/OpenAIAssistantChannel.cs | 180 ++++++++-------- .../OpenAI/OpenAIAssistantConfiguration.cs | 8 +- .../OpenAI/OpenAIAssistantDefinition.cs | 4 +- .../Extensions/AuthorRoleExtensionsTests.cs | 2 +- .../KernelFunctionExtensionsTests.cs | 6 +- .../OpenAI/OpenAIAssistantAgentTests.cs | 21 +- .../OpenAIAssistantConfigurationTests.cs | 6 +- .../OpenAI/OpenAIAssistantDefinitionTests.cs | 12 +- .../Agents/OpenAIAssistantAgentTests.cs | 2 +- .../samples/InternalUtilities/BaseTest.cs | 6 +- 19 files changed, 255 insertions(+), 220 deletions(-) diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index 5f81653e6dff..dfcddf2920c5 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -46,8 +46,8 @@ - - + + @@ -103,4 +103,13 @@ Always + + + + + + + + + diff --git a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj index ea4decbf86bb..b95bbd546d34 100644 --- a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj +++ b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj @@ -38,7 +38,7 @@ - + diff --git a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs b/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs index 708fab321f04..3ce8ade066e4 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs @@ -3,7 +3,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.OpenAI; +using Microsoft.SemanticKernel.Connectors.AzureOpenAI; namespace GettingStarted; @@ -26,7 +26,7 @@ public async Task RunAsync() Instructions = HostInstructions, Name = HostName, Kernel = this.CreateKernelWithChatCompletion(), - ExecutionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, + ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = AzureOpenAIToolCallBehavior.AutoInvokeKernelFunctions }, }; // Initialize plugin and add to the agent's Kernel (same as direct Kernel usage). diff --git a/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs b/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs index c759053dbe1c..6524f3ee39b2 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs @@ -38,9 +38,9 @@ public async Task RunAsync() if (this.UseOpenAIConfig) { - serviceContainer.AddOpenAIChatCompletion( - TestConfiguration.OpenAI.ChatModelId, - TestConfiguration.OpenAI.ApiKey); + //serviceContainer.AddOpenAIChatCompletion( %%% + // TestConfiguration.OpenAI.ChatModelId, + // TestConfiguration.OpenAI.ApiKey); } else { diff --git a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs index 32ce38da8b2f..f9e1b601e4c5 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs @@ -14,6 +14,8 @@ namespace GettingStarted; /// public class Step8_OpenAIAssistant(ITestOutputHelper output) : BaseTest(output) { + protected override bool ForceOpenAI => true; + private const string HostName = "Host"; private const string HostInstructions = "Answer questions about the menu."; @@ -29,7 +31,7 @@ await OpenAIAssistantAgent.CreateAsync( { Instructions = HostInstructions, Name = HostName, - ModelId = this.Model, + Model = this.Model, }); // Initialize plugin and add to the agent's Kernel (same as direct Kernel usage). diff --git a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj index 222ea5c5be88..628fdf2aa171 100644 --- a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj +++ b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj @@ -19,6 +19,7 @@ + @@ -32,7 +33,7 @@ - + diff --git a/dotnet/src/Agents/OpenAI/Extensions/AuthorRoleExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/AuthorRoleExtensions.cs index cd4e80c3abf1..895482927515 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/AuthorRoleExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/AuthorRoleExtensions.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -using Azure.AI.OpenAI.Assistants; using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs index 9665fb680498..63eec124f0ae 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Azure.AI.OpenAI.Assistants; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index ca016a5d97cb..78ff3bc470d9 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -1,17 +1,17 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Azure; -using Azure.AI.OpenAI.Assistants; -using Azure.Core; -using Azure.Core.Pipeline; +using Azure.AI.OpenAI; using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Agents.OpenAI.Azure; using Microsoft.SemanticKernel.Http; +using OpenAI; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -21,13 +21,13 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; public sealed partial class OpenAIAssistantAgent : KernelAgent { private readonly Assistant _assistant; - private readonly AssistantsClient _client; + private readonly AssistantClient _client; private readonly OpenAIAssistantConfiguration _config; - /// - /// A list of previously uploaded file IDs to attach to the assistant. - /// - public IReadOnlyList FileIds => this._assistant.FileIds; + ///// + ///// A list of previously uploaded file IDs to attach to the assistant. + ///// + //public IReadOnlyList FileIds => this._assistant.FileIds; %%% /// /// A set of up to 16 key/value pairs that can be attached to an agent, used for @@ -67,11 +67,11 @@ public static async Task CreateAsync( Verify.NotNull(definition, nameof(definition)); // Create the client - AssistantsClient client = CreateClient(config); + AssistantClient client = CreateClient(config); // Create the assistant AssistantCreationOptions assistantCreationOptions = CreateAssistantCreationOptions(definition); - Assistant model = await client.CreateAssistantAsync(assistantCreationOptions, cancellationToken).ConfigureAwait(false); + Assistant model = await client.CreateAssistantAsync(definition.Model, assistantCreationOptions, cancellationToken).ConfigureAwait(false); // Instantiate the agent return @@ -85,53 +85,31 @@ public static async Task CreateAsync( /// Retrieve a list of assistant definitions: . /// /// Configuration for accessing the Assistants API service, such as the api-key. - /// The maximum number of assistant definitions to retrieve - /// The identifier of the assistant beyond which to begin selection. /// The to monitor for cancellation requests. The default is . /// An list of objects. public static async IAsyncEnumerable ListDefinitionsAsync( OpenAIAssistantConfiguration config, - int maxResults = 100, - string? lastId = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { // Create the client - AssistantsClient client = CreateClient(config); - - // Retrieve the assistants - PageableList assistants; + AssistantClient client = CreateClient(config); - int resultCount = 0; - do + await foreach (Assistant assistant in client.GetAssistantsAsync(ListOrder.NewestFirst, cancellationToken).ConfigureAwait(false)) { - assistants = await client.GetAssistantsAsync(limit: Math.Min(maxResults, 100), ListSortOrder.Descending, after: lastId, cancellationToken: cancellationToken).ConfigureAwait(false); - foreach (Assistant assistant in assistants) - { - if (resultCount >= maxResults) + yield return + new() { - break; - } - - resultCount++; - - yield return - new() - { - Id = assistant.Id, - Name = assistant.Name, - Description = assistant.Description, - Instructions = assistant.Instructions, - EnableCodeInterpreter = assistant.Tools.Any(t => t is CodeInterpreterToolDefinition), - EnableRetrieval = assistant.Tools.Any(t => t is RetrievalToolDefinition), - FileIds = assistant.FileIds, - Metadata = assistant.Metadata, - ModelId = assistant.Model, - }; - - lastId = assistant.Id; - } + Id = assistant.Id, + Name = assistant.Name, + Description = assistant.Description, + Instructions = assistant.Instructions, + EnableCodeInterpreter = assistant.Tools.Any(t => t is CodeInterpreterToolDefinition), + EnableFileSearch = assistant.Tools.Any(t => t is FileSearchToolDefinition), + //FileIds = assistant.FileIds, %%% + Metadata = assistant.Metadata, + Model = assistant.Model, + }; } - while (assistants.HasMore && resultCount < maxResults); } /// @@ -149,10 +127,10 @@ public static async Task RetrieveAsync( CancellationToken cancellationToken = default) { // Create the client - AssistantsClient client = CreateClient(config); + AssistantClient client = CreateClient(config); // Retrieve the assistant - Assistant model = await client.GetAssistantAsync(id, cancellationToken).ConfigureAwait(false); + Assistant model = await client.GetAssistantAsync(id).ConfigureAwait(false); // %%% CANCEL TOKEN // Instantiate the agent return @@ -182,12 +160,6 @@ protected override IEnumerable GetChannelKeys() // Distinguish between different Azure OpenAI endpoints or OpenAI services. yield return this._config.Endpoint ?? "openai"; - // Distinguish between different API versioning. - if (this._config.Version.HasValue) - { - yield return this._config.Version.ToString()!; - } - // Custom client receives dedicated channel. if (this._config.HttpClient is not null) { @@ -208,7 +180,7 @@ protected override async Task CreateChannelAsync(ILogger logger, C { logger.LogDebug("[{MethodName}] Creating assistant thread", nameof(CreateChannelAsync)); - AssistantThread thread = await this._client.CreateThreadAsync(cancellationToken).ConfigureAwait(false); + AssistantThread thread = await this._client.CreateThreadAsync(options: null, cancellationToken).ConfigureAwait(false); logger.LogInformation("[{MethodName}] Created assistant thread: {ThreadId}", nameof(CreateChannelAsync), thread.Id); @@ -219,7 +191,7 @@ protected override async Task CreateChannelAsync(ILogger logger, C /// Initializes a new instance of the class. /// private OpenAIAssistantAgent( - AssistantsClient client, + AssistantClient client, Assistant model, OpenAIAssistantConfiguration config) { @@ -233,64 +205,124 @@ private OpenAIAssistantAgent( this.Instructions = this._assistant.Instructions; } + private static AzureOpenAIClientOptions GetAzureOpenAIClientOptions(HttpClient? httpClient) + { + AzureOpenAIClientOptions options = new() + { + ApplicationId = HttpHeaderConstant.Values.UserAgent, + }; + + options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall); + + if (httpClient is not null) + { + options.Transport = new HttpClientPipelineTransport(httpClient); + options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable Azure SDK retry policy if and only if a custom HttpClient is provided. + options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable Azure SDK default timeout + } + + return options; + } + private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAssistantDefinition definition) { AssistantCreationOptions assistantCreationOptions = - new(definition.ModelId) + new() { Description = definition.Description, Instructions = definition.Instructions, Name = definition.Name, - Metadata = definition.Metadata?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + // %%% ResponseFormat = + // %%% Temperature = + // %%% ToolResources + //assistantCreationOptions.FileIds.AddRange(definition.FileIds ?? []); %%% + // %%% NucleusSamplingFactor }; - assistantCreationOptions.FileIds.AddRange(definition.FileIds ?? []); + // %%% COPY METADATA + // Metadata = definition.Metadata?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), if (definition.EnableCodeInterpreter) { assistantCreationOptions.Tools.Add(new CodeInterpreterToolDefinition()); } - if (definition.EnableRetrieval) + if (definition.EnableFileSearch) { - assistantCreationOptions.Tools.Add(new RetrievalToolDefinition()); + assistantCreationOptions.Tools.Add(new FileSearchToolDefinition()); } return assistantCreationOptions; } - private static AssistantsClient CreateClient(OpenAIAssistantConfiguration config) + private static AssistantClient CreateClient(OpenAIAssistantConfiguration config) { - AssistantsClientOptions clientOptions = CreateClientOptions(config); + OpenAIClient client; // Inspect options - if (!string.IsNullOrWhiteSpace(config.Endpoint)) + if (!string.IsNullOrWhiteSpace(config.Endpoint)) // %%% INSUFFICENT (BOTH HAVE ENDPOINT OPTION) { // Create client configured for Azure OpenAI, if endpoint definition is present. - return new AssistantsClient(new Uri(config.Endpoint), new AzureKeyCredential(config.ApiKey), clientOptions); + AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(config.HttpClient); + client = new AzureOpenAIClient(new Uri(config.Endpoint), config.ApiKey, clientOptions); + } + else + { + // Otherwise, create client configured for OpenAI. + OpenAIClientOptions clientOptions = CreateClientOptions(config.HttpClient, config.Endpoint); + client = new OpenAIClient(config.ApiKey, clientOptions); + } + + return client.GetAssistantClient(); + } + + internal static AzureOpenAIClientOptions CreateAzureClientOptions(HttpClient? httpClient) + { + AzureOpenAIClientOptions options = new() + { + ApplicationId = HttpHeaderConstant.Values.UserAgent, + }; + + options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall); + + if (httpClient is not null) + { + options.Transport = new HttpClientPipelineTransport(httpClient); + options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable Azure SDK retry policy if and only if a custom HttpClient is provided. + options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable Azure SDK default timeout } - // Otherwise, create client configured for OpenAI. - return new AssistantsClient(config.ApiKey, clientOptions); + return options; } - private static AssistantsClientOptions CreateClientOptions(OpenAIAssistantConfiguration config) + private static OpenAIClientOptions CreateClientOptions(HttpClient? httpClient, string? endpoint) { - AssistantsClientOptions options = - config.Version.HasValue ? - new(config.Version.Value) : - new(); + OpenAIClientOptions options = new() + { + ApplicationId = HttpHeaderConstant.Values.UserAgent, + Endpoint = string.IsNullOrEmpty(endpoint) ? null : new Uri(endpoint) + }; - options.Diagnostics.ApplicationId = HttpHeaderConstant.Values.UserAgent; - options.AddPolicy(new AddHeaderRequestPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), HttpPipelinePosition.PerCall); + options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall); - if (config.HttpClient is not null) + if (httpClient is not null) { - options.Transport = new HttpClientTransport(config.HttpClient); - options.RetryPolicy = new RetryPolicy(maxRetries: 0); // Disable Azure SDK retry policy if and only if a custom HttpClient is provided. - options.Retry.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable Azure SDK default timeout + options.Transport = new HttpClientPipelineTransport(httpClient); + options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable retry policy if and only if a custom HttpClient is provided. + options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable default timeout } return options; } + + private static GenericActionPipelinePolicy CreateRequestHeaderPolicy(string headerName, string headerValue) + { + return new GenericActionPipelinePolicy((message) => + { + if (message?.Request?.Headers?.TryGetValue(headerName, out string? _) == false) + { + message.Request.Headers.Set(headerName, headerValue); + } + }); + } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs index 0d8b20b5b931..1c037ecab08c 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs @@ -1,4 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. +using System.ClientModel; using System.Collections.Generic; using System.Linq; using System.Net; @@ -7,16 +8,17 @@ using System.Threading; using System.Threading.Tasks; using Azure; -using Azure.AI.OpenAI.Assistants; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// A specialization for use with . /// -internal sealed class OpenAIAssistantChannel(AssistantsClient client, string threadId, OpenAIAssistantConfiguration.PollingConfiguration pollingConfiguration) +internal sealed class OpenAIAssistantChannel(AssistantClient client, string threadId, OpenAIAssistantConfiguration.PollingConfiguration pollingConfiguration) : AgentChannel { private const string FunctionDelimiter = "-"; @@ -35,7 +37,7 @@ internal sealed class OpenAIAssistantChannel(AssistantsClient client, string thr RunStatus.Cancelled, ]; - private readonly AssistantsClient _client = client; + private readonly AssistantClient _client = client; private readonly string _threadId = threadId; private readonly Dictionary _agentTools = []; private readonly Dictionary _agentNames = []; // Cache agent names by their identifier for GetHistoryAsync() @@ -50,11 +52,17 @@ protected override async Task ReceiveAsync(IReadOnlyList his continue; } + MessageCreationOptions options = + new() + { + //Role = message.Role.ToMessageRole(), // %%% BUG: ASSIGNABLE + }; + await this._client.CreateMessageAsync( this._threadId, - message.Role.ToMessageRole(), - message.Content, - cancellationToken: cancellationToken).ConfigureAwait(false); + [message.Content], // %%% + options, + cancellationToken).ConfigureAwait(false); } } @@ -81,15 +89,17 @@ protected override async IAsyncEnumerable InvokeAsync( this.Logger.LogDebug("[{MethodName}] Creating run for agent/thrad: {AgentId}/{ThreadId}", nameof(InvokeAsync), agent.Id, this._threadId); - CreateRunOptions options = - new(agent.Id) + RunCreationOptions options = + new() { - OverrideInstructions = agent.Instructions, - OverrideTools = tools, + //InstructionsOverride = agent.Instructions, + //ParallelToolCallsEnabled = true, // %%% + //ResponseFormat = %%% + //ToolsOverride = tools, %%% }; // Create run - ThreadRun run = await this._client.CreateRunAsync(this._threadId, options, cancellationToken).ConfigureAwait(false); + ThreadRun run = await this._client.CreateRunAsync(this._threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); this.Logger.LogInformation("[{MethodName}] Created run: {RunId}", nameof(InvokeAsync), run.Id); @@ -100,7 +110,7 @@ protected override async IAsyncEnumerable InvokeAsync( do { // Poll run and steps until actionable - PageableList steps = await PollRunStatusAsync().ConfigureAwait(false); + await PollRunStatusAsync().ConfigureAwait(false); // Is in terminal state? if (s_terminalStatuses.Contains(run.Status)) @@ -108,13 +118,15 @@ protected override async IAsyncEnumerable InvokeAsync( throw new KernelException($"Agent Failure - Run terminated: {run.Status} [{run.Id}]: {run.LastError?.Message ?? "Unknown"}"); } + RunStep[] steps = await this._client.GetRunStepsAsync(run).ToArrayAsync(cancellationToken).ConfigureAwait(false); + // Is tool action required? if (run.Status == RunStatus.RequiresAction) { this.Logger.LogDebug("[{MethodName}] Processing run steps: {RunId}", nameof(InvokeAsync), run.Id); // Execute functions in parallel and post results at once. - FunctionCallContent[] activeFunctionSteps = steps.Data.SelectMany(step => ParseFunctionStep(agent, step)).ToArray(); + FunctionCallContent[] activeFunctionSteps = steps.SelectMany(step => ParseFunctionStep(agent, step)).ToArray(); if (activeFunctionSteps.Length > 0) { // Emit function-call content @@ -129,7 +141,7 @@ protected override async IAsyncEnumerable InvokeAsync( // Process tool output ToolOutput[] toolOutputs = GenerateToolOutputs(functionResults); - await this._client.SubmitToolOutputsToRunAsync(run, toolOutputs, cancellationToken).ConfigureAwait(false); + await this._client.SubmitToolOutputsToRunAsync(run, toolOutputs).ConfigureAwait(false); // %%% CANCEL TOKEN } if (this.Logger.IsEnabled(LogLevel.Information)) // Avoid boxing if not enabled @@ -149,24 +161,22 @@ protected override async IAsyncEnumerable InvokeAsync( int messageCount = 0; foreach (RunStep completedStep in completedStepsToProcess) { - if (completedStep.Type.Equals(RunStepType.ToolCalls)) + if (completedStep.Type == RunStepType.ToolCalls) { - RunStepToolCallDetails toolCallDetails = (RunStepToolCallDetails)completedStep.StepDetails; - - foreach (RunStepToolCall toolCall in toolCallDetails.ToolCalls) + foreach (RunStepToolCall toolCall in completedStep.Details.ToolCalls) { ChatMessageContent? content = null; // Process code-interpreter content - if (toolCall is RunStepCodeInterpreterToolCall toolCodeInterpreter) + if (toolCall.ToolKind == RunStepToolCallKind.CodeInterpreter) { - content = GenerateCodeInterpreterContent(agent.GetName(), toolCodeInterpreter); + content = GenerateCodeInterpreterContent(agent.GetName(), toolCall.CodeInterpreterInput); } // Process function result content - else if (toolCall is RunStepFunctionToolCall toolFunction) + else if (toolCall.ToolKind == RunStepToolCallKind.Function) { - FunctionCallContent functionStep = functionSteps[toolFunction.Id]; // Function step always captured on invocation - content = GenerateFunctionResultContent(agent.GetName(), functionStep, toolFunction.Output); + FunctionCallContent functionStep = functionSteps[toolCall.ToolCallId]; // Function step always captured on invocation + content = GenerateFunctionResultContent(agent.GetName(), functionStep, toolCall.FunctionOutput); } if (content is not null) @@ -177,30 +187,28 @@ protected override async IAsyncEnumerable InvokeAsync( } } } - else if (completedStep.Type.Equals(RunStepType.MessageCreation)) + else if (completedStep.Type == RunStepType.MessageCreation) { - RunStepMessageCreationDetails messageCreationDetails = (RunStepMessageCreationDetails)completedStep.StepDetails; - // Retrieve the message - ThreadMessage? message = await this.RetrieveMessageAsync(messageCreationDetails, cancellationToken).ConfigureAwait(false); + ThreadMessage? message = await this.RetrieveMessageAsync(completedStep.Details.CreatedMessageId, cancellationToken).ConfigureAwait(false); if (message is not null) { AuthorRole role = new(message.Role.ToString()); - foreach (MessageContent itemContent in message.ContentItems) + foreach (MessageContent itemContent in message.Content) { ChatMessageContent? content = null; // Process text content - if (itemContent is MessageTextContent contentMessage) + if (!string.IsNullOrEmpty(itemContent.Text)) { - content = GenerateTextMessageContent(agent.GetName(), role, contentMessage); + content = GenerateTextMessageContent(agent.GetName(), role, itemContent); } // Process image content - else if (itemContent is MessageImageFileContent contentImage) + else if (itemContent.ImageFileId != null) { - content = GenerateImageFileContent(agent.GetName(), role, contentImage); + content = GenerateImageFileContent(agent.GetName(), role, itemContent); } if (content is not null) @@ -226,7 +234,7 @@ protected override async IAsyncEnumerable InvokeAsync( this.Logger.LogInformation("[{MethodName}] Completed run: {RunId}", nameof(InvokeAsync), run.Id); // Local function to assist in run polling (participates in method closure). - async Task> PollRunStatusAsync() + async Task PollRunStatusAsync() { this.Logger.LogInformation("[{MethodName}] Polling run status: {RunId}", nameof(PollRunStatusAsync), run.Id); @@ -252,32 +260,30 @@ async Task> PollRunStatusAsync() while (s_pollingStatuses.Contains(run.Status)); this.Logger.LogInformation("[{MethodName}] Run status is {RunStatus}: {RunId}", nameof(PollRunStatusAsync), run.Status, run.Id); - - return await this._client.GetRunStepsAsync(run, cancellationToken: cancellationToken).ConfigureAwait(false); } // Local function to capture kernel function state for further processing (participates in method closure). IEnumerable ParseFunctionStep(OpenAIAssistantAgent agent, RunStep step) { - if (step.Status == RunStepStatus.InProgress && step.StepDetails is RunStepToolCallDetails callDetails) + if (step.Status == RunStepStatus.InProgress && step.Type == RunStepType.ToolCalls) { - foreach (RunStepFunctionToolCall toolCall in callDetails.ToolCalls.OfType()) + foreach (RunStepToolCall toolCall in step.Details.ToolCalls) { - var nameParts = FunctionName.Parse(toolCall.Name, FunctionDelimiter); + var nameParts = FunctionName.Parse(toolCall.FunctionName, FunctionDelimiter); KernelArguments functionArguments = []; - if (!string.IsNullOrWhiteSpace(toolCall.Arguments)) + if (!string.IsNullOrWhiteSpace(toolCall.FunctionArguments)) { - Dictionary arguments = JsonSerializer.Deserialize>(toolCall.Arguments)!; + Dictionary arguments = JsonSerializer.Deserialize>(toolCall.FunctionArguments)!; foreach (var argumentKvp in arguments) { functionArguments[argumentKvp.Key] = argumentKvp.Value.ToString(); } } - var content = new FunctionCallContent(nameParts.Name, nameParts.PluginName, toolCall.Id, functionArguments); + var content = new FunctionCallContent(nameParts.Name, nameParts.PluginName, toolCall.ToolCallId, functionArguments); - functionSteps.Add(toolCall.Id, content); + functionSteps.Add(toolCall.ToolCallId, content); yield return content; } @@ -288,90 +294,82 @@ IEnumerable ParseFunctionStep(OpenAIAssistantAgent agent, R /// protected override async IAsyncEnumerable GetHistoryAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - PageableList messages; - - string? lastId = null; - do + await foreach (ThreadMessage message in this._client.GetMessagesAsync(this._threadId, ListOrder.NewestFirst, cancellationToken).ConfigureAwait(false)) { - messages = await this._client.GetMessagesAsync(this._threadId, limit: 100, ListSortOrder.Descending, after: lastId, null, cancellationToken).ConfigureAwait(false); - foreach (ThreadMessage message in messages) - { - AuthorRole role = new(message.Role.ToString()); + AuthorRole role = new(message.Role.ToString()); - string? assistantName = null; - if (!string.IsNullOrWhiteSpace(message.AssistantId) && - !this._agentNames.TryGetValue(message.AssistantId, out assistantName)) + string? assistantName = null; + if (!string.IsNullOrWhiteSpace(message.AssistantId) && + !this._agentNames.TryGetValue(message.AssistantId, out assistantName)) + { + Assistant assistant = await this._client.GetAssistantAsync(message.AssistantId).ConfigureAwait(false); // %%% CANCEL TOKEN + if (!string.IsNullOrWhiteSpace(assistant.Name)) { - Assistant assistant = await this._client.GetAssistantAsync(message.AssistantId, cancellationToken).ConfigureAwait(false); - if (!string.IsNullOrWhiteSpace(assistant.Name)) - { - this._agentNames.Add(assistant.Id, assistant.Name); - } + this._agentNames.Add(assistant.Id, assistant.Name); } + } - assistantName ??= message.AssistantId; + assistantName ??= message.AssistantId; - foreach (MessageContent item in message.ContentItems) - { - ChatMessageContent? content = null; - - if (item is MessageTextContent contentMessage) - { - content = GenerateTextMessageContent(assistantName, role, contentMessage); - } - else if (item is MessageImageFileContent contentImage) - { - content = GenerateImageFileContent(assistantName, role, contentImage); - } + foreach (MessageContent itemContent in message.Content) + { + ChatMessageContent? content = null; - if (content is not null) - { - yield return content; - } + if (!string.IsNullOrEmpty(itemContent.Text)) + { + content = GenerateTextMessageContent(assistantName, role, itemContent); + } + // Process image content + else if (itemContent.ImageFileId != null) + { + content = GenerateImageFileContent(assistantName, role, itemContent); } - lastId = message.Id; + if (content is not null) + { + yield return content; + } } } - while (messages.HasMore); } - private static AnnotationContent GenerateAnnotationContent(MessageTextAnnotation annotation) + private static AnnotationContent GenerateAnnotationContent(TextAnnotation annotation) { string? fileId = null; - if (annotation is MessageTextFileCitationAnnotation citationAnnotation) + + if (string.IsNullOrEmpty(annotation.OutputFileId)) { - fileId = citationAnnotation.FileId; + fileId = annotation.OutputFileId; } - else if (annotation is MessageTextFilePathAnnotation pathAnnotation) + else if (string.IsNullOrEmpty(annotation.InputFileId)) { - fileId = pathAnnotation.FileId; + fileId = annotation.InputFileId; } return new() { - Quote = annotation.Text, + Quote = annotation.TextToReplace, StartIndex = annotation.StartIndex, EndIndex = annotation.EndIndex, FileId = fileId, }; } - private static ChatMessageContent GenerateImageFileContent(string agentName, AuthorRole role, MessageImageFileContent contentImage) + private static ChatMessageContent GenerateImageFileContent(string agentName, AuthorRole role, MessageContent contentImage) { return new ChatMessageContent( role, [ - new FileReferenceContent(contentImage.FileId) + new FileReferenceContent(contentImage.ImageFileId) ]) { AuthorName = agentName, }; } - private static ChatMessageContent? GenerateTextMessageContent(string agentName, AuthorRole role, MessageTextContent contentMessage) + private static ChatMessageContent? GenerateTextMessageContent(string agentName, AuthorRole role, MessageContent contentMessage) { ChatMessageContent? messageContent = null; @@ -385,7 +383,7 @@ private static ChatMessageContent GenerateImageFileContent(string agentName, Aut AuthorName = agentName }; - foreach (MessageTextAnnotation annotation in contentMessage.Annotations) + foreach (TextAnnotation annotation in contentMessage.TextAnnotations) { messageContent.Items.Add(GenerateAnnotationContent(annotation)); } @@ -394,13 +392,13 @@ private static ChatMessageContent GenerateImageFileContent(string agentName, Aut return messageContent; } - private static ChatMessageContent GenerateCodeInterpreterContent(string agentName, RunStepCodeInterpreterToolCall contentCodeInterpreter) + private static ChatMessageContent GenerateCodeInterpreterContent(string agentName, string code) { return new ChatMessageContent( AuthorRole.Tool, [ - new TextContent(contentCodeInterpreter.Input) + new TextContent(code) ]) { AuthorName = agentName, @@ -469,7 +467,7 @@ private static ToolOutput[] GenerateToolOutputs(FunctionResultContent[] function return toolOutputs; } - private async Task RetrieveMessageAsync(RunStepMessageCreationDetails detail, CancellationToken cancellationToken) + private async Task RetrieveMessageAsync(string messageId, CancellationToken cancellationToken) { ThreadMessage? message = null; @@ -479,7 +477,7 @@ private static ToolOutput[] GenerateToolOutputs(FunctionResultContent[] function { try { - message = await this._client.GetMessageAsync(this._threadId, detail.MessageCreation.MessageId, cancellationToken).ConfigureAwait(false); + message = await this._client.GetMessageAsync(this._threadId, messageId, cancellationToken).ConfigureAwait(false); } catch (RequestFailedException exception) { diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantConfiguration.cs index aa037266e7d5..acc094b86969 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantConfiguration.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantConfiguration.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; using System.Net.Http; -using Azure.AI.OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -20,11 +19,6 @@ public sealed class OpenAIAssistantConfiguration /// public string? Endpoint { get; } - /// - /// An optional API version override. - /// - public AssistantsClientOptions.ServiceVersion? Version { get; init; } - /// /// Custom for HTTP requests. /// @@ -33,7 +27,7 @@ public sealed class OpenAIAssistantConfiguration /// /// Defineds polling behavior for Assistant API requests. /// - public PollingConfiguration Polling { get; } = new PollingConfiguration(); + public PollingConfiguration Polling { get; } = new(); /// /// Initializes a new instance of the class. diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index 3699e07ee1ed..e3e8706abf47 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -11,7 +11,7 @@ public sealed class OpenAIAssistantDefinition /// /// Identifies the AI model (OpenAI) or deployment (AzureOAI) this agent targets. /// - public string? ModelId { get; init; } + public string? Model { get; init; } /// /// The description of the assistant. @@ -41,7 +41,7 @@ public sealed class OpenAIAssistantDefinition /// /// Set if retrieval is enabled. /// - public bool EnableRetrieval { get; init; } + public bool EnableFileSearch { get; init; } /// /// A list of previously uploaded file IDs to attach to the assistant. diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AuthorRoleExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AuthorRoleExtensionsTests.cs index 0b0a0707e49a..997596796be1 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AuthorRoleExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AuthorRoleExtensionsTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. -using Azure.AI.OpenAI.Assistants; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI.Assistants; using Xunit; using KernelExtensions = Microsoft.SemanticKernel.Agents.OpenAI; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs index eeb8a4d3b9d1..2096012831ed 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. using System; using System.ComponentModel; -using Azure.AI.OpenAI.Assistants; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.OpenAI; +using OpenAI.Assistants; using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI.Extensions; @@ -26,11 +26,11 @@ public void VerifyKernelFunctionToFunctionTool() KernelFunction f2 = plugin[nameof(TestPlugin.TestFunction2)]; FunctionToolDefinition definition1 = f1.ToToolDefinition("testplugin", "-"); - Assert.StartsWith($"testplugin-{nameof(TestPlugin.TestFunction1)}", definition1.Name, StringComparison.Ordinal); + Assert.StartsWith($"testplugin-{nameof(TestPlugin.TestFunction1)}", definition1.FunctionName, StringComparison.Ordinal); Assert.Equal("test description", definition1.Description); FunctionToolDefinition definition2 = f2.ToToolDefinition("testplugin", "-"); - Assert.StartsWith($"testplugin-{nameof(TestPlugin.TestFunction2)}", definition2.Name, StringComparison.Ordinal); + Assert.StartsWith($"testplugin-{nameof(TestPlugin.TestFunction2)}", definition2.FunctionName, StringComparison.Ordinal); Assert.Equal("test description", definition2.Description); } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index 1d9a9ec9dfcf..45d9df826c7b 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -5,11 +5,11 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; -using Azure.AI.OpenAI.Assistants; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI.Assistants; using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI; @@ -33,7 +33,7 @@ public async Task VerifyOpenAIAssistantAgentCreationEmptyAsync() OpenAIAssistantDefinition definition = new() { - ModelId = "testmodel", + Model = "testmodel", }; this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentSimple); @@ -62,7 +62,7 @@ public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() OpenAIAssistantDefinition definition = new() { - ModelId = "testmodel", + Model = "testmodel", Name = "testname", Description = "testdescription", Instructions = "testinstructions", @@ -94,9 +94,9 @@ public async Task VerifyOpenAIAssistantAgentCreationEverythingAsync() OpenAIAssistantDefinition definition = new() { - ModelId = "testmodel", + Model = "testmodel", EnableCodeInterpreter = true, - EnableRetrieval = true, + EnableFileSearch = true, FileIds = ["#1", "#2"], Metadata = new Dictionary() { { "a", "1" } }, }; @@ -112,8 +112,8 @@ await OpenAIAssistantAgent.CreateAsync( Assert.NotNull(agent); Assert.Equal(2, agent.Tools.Count); Assert.True(agent.Tools.OfType().Any()); - Assert.True(agent.Tools.OfType().Any()); - Assert.NotEmpty(agent.FileIds); + //Assert.True(agent.Tools.OfType().Any()); %%% + //Assert.NotEmpty(agent.FileIds); %%% Assert.NotEmpty(agent.Metadata); } @@ -314,8 +314,7 @@ await OpenAIAssistantAgent.ListDefinitionsAsync( messages = await OpenAIAssistantAgent.ListDefinitionsAsync( - this.CreateTestConfiguration(), - maxResults: 4).ToArrayAsync(); + this.CreateTestConfiguration()).ToArrayAsync(); Assert.Equal(4, messages.Length); } @@ -370,7 +369,7 @@ private Task CreateAgentAsync() OpenAIAssistantDefinition definition = new() { - ModelId = "testmodel", + Model = "testmodel", }; this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentSimple); @@ -387,7 +386,7 @@ private OpenAIAssistantConfiguration CreateTestConfiguration(bool targetAzure = return new(apiKey: "fakekey", endpoint: targetAzure ? "https://localhost" : null) { HttpClient = this._httpClient, - Version = useVersion ? AssistantsClientOptions.ServiceVersion.V2024_02_15_Preview : null, + //Version = useVersion ? AssistantsClientOptions.ServiceVersion.V2024_02_15_Preview : null, %%% }; } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs index 3708ab50ab97..d2849f5d19fa 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs @@ -23,7 +23,7 @@ public void VerifyOpenAIAssistantConfigurationInitialState() Assert.Equal("testkey", config.ApiKey); Assert.Null(config.Endpoint); Assert.Null(config.HttpClient); - Assert.Null(config.Version); + //Assert.Null(config.Version); %%% } /// @@ -38,13 +38,13 @@ public void VerifyOpenAIAssistantConfigurationAssignment() new(apiKey: "testkey", endpoint: "https://localhost") { HttpClient = client, - Version = AssistantsClientOptions.ServiceVersion.V2024_02_15_Preview, + //Version = AssistantsClientOptions.ServiceVersion.V2024_02_15_Preview, %%% }; Assert.Equal("testkey", config.ApiKey); Assert.Equal("https://localhost", config.Endpoint); Assert.NotNull(config.HttpClient); - Assert.Equal(AssistantsClientOptions.ServiceVersion.V2024_02_15_Preview, config.Version); + //Assert.Equal(AssistantsClientOptions.ServiceVersion.V2024_02_15_Preview, config.Version); %%% } /// diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index b17b61211c18..48977edc122a 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -20,13 +20,13 @@ public void VerifyOpenAIAssistantDefinitionInitialState() Assert.Null(definition.Id); Assert.Null(definition.Name); - Assert.Null(definition.ModelId); + Assert.Null(definition.Model); Assert.Null(definition.Instructions); Assert.Null(definition.Description); Assert.Null(definition.Metadata); Assert.Null(definition.FileIds); Assert.False(definition.EnableCodeInterpreter); - Assert.False(definition.EnableRetrieval); + Assert.False(definition.EnableFileSearch); } /// @@ -40,23 +40,23 @@ public void VerifyOpenAIAssistantDefinitionAssignment() { Id = "testid", Name = "testname", - ModelId = "testmodel", + Model = "testmodel", Instructions = "testinstructions", Description = "testdescription", FileIds = ["id"], Metadata = new Dictionary() { { "a", "1" } }, EnableCodeInterpreter = true, - EnableRetrieval = true, + EnableFileSearch = true, }; Assert.Equal("testid", definition.Id); Assert.Equal("testname", definition.Name); - Assert.Equal("testmodel", definition.ModelId); + Assert.Equal("testmodel", definition.Model); Assert.Equal("testinstructions", definition.Instructions); Assert.Equal("testdescription", definition.Description); Assert.Single(definition.Metadata); Assert.Single(definition.FileIds); Assert.True(definition.EnableCodeInterpreter); - Assert.True(definition.EnableRetrieval); + Assert.True(definition.EnableFileSearch); } } diff --git a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs index 20d6dcad9146..d274ac7706d7 100644 --- a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs @@ -85,7 +85,7 @@ await OpenAIAssistantAgent.CreateAsync( new() { Instructions = "Answer questions about the menu.", - ModelId = modelName, + Model = modelName, }); AgentGroupChat chat = new(); diff --git a/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs b/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs index 8e65d7dcd88a..b9a76dd1b117 100644 --- a/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs +++ b/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs @@ -42,9 +42,9 @@ protected Kernel CreateKernelWithChatCompletion() if (this.UseOpenAIConfig) { - builder.AddOpenAIChatCompletion( - TestConfiguration.OpenAI.ChatModelId, - TestConfiguration.OpenAI.ApiKey); + //builder.AddOpenAIChatCompletion( %%% + // TestConfiguration.OpenAI.ChatModelId, + // TestConfiguration.OpenAI.ApiKey); } else { From 08a426e12ee991efcd4c6728c77eaa4107197f98 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 3 Jul 2024 11:55:16 -0700 Subject: [PATCH 002/121] Fix merge from parent --- .../Agents/OpenAI/AssistantThreadActions.cs | 182 +++++++++--------- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 3 +- .../Agents/OpenAI/OpenAIAssistantChannel.cs | 6 +- 3 files changed, 95 insertions(+), 96 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs index 37649844a230..b5376f52d81d 100644 --- a/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs @@ -1,4 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. +using System.ClientModel; using System.Collections.Generic; using System.Linq; using System.Net; @@ -7,9 +8,10 @@ using System.Threading; using System.Threading.Tasks; using Azure; -using Azure.AI.OpenAI.Assistants; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -49,7 +51,7 @@ internal static class AssistantThreadActions /// The message to add /// The to monitor for cancellation requests. The default is . /// if a system message is present, without taking any other action - public static async Task CreateMessageAsync(AssistantsClient client, string threadId, ChatMessageContent message, CancellationToken cancellationToken) + public static async Task CreateMessageAsync(AssistantClient client, string threadId, ChatMessageContent message, CancellationToken cancellationToken) { if (!s_messageRoles.Contains(message.Role)) { @@ -61,11 +63,17 @@ public static async Task CreateMessageAsync(AssistantsClient client, string thre return; } + MessageCreationOptions options = + new() + { + //Role = message.Role.ToMessageRole(), // %%% BUG: ASSIGNABLE + }; + await client.CreateMessageAsync( threadId, - message.Role.ToMessageRole(), - message.Content, - cancellationToken: cancellationToken).ConfigureAwait(false); + [message.Content], // %%% + options, + cancellationToken).ConfigureAwait(false); } /// @@ -75,56 +83,47 @@ await client.CreateMessageAsync( /// The thread identifier /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. - public static async IAsyncEnumerable GetMessagesAsync(AssistantsClient client, string threadId, [EnumeratorCancellation] CancellationToken cancellationToken) + public static async IAsyncEnumerable GetMessagesAsync(AssistantClient client, string threadId, [EnumeratorCancellation] CancellationToken cancellationToken) { Dictionary agentNames = []; // Cache agent names by their identifier - PageableList messages; - - string? lastId = null; - do + await foreach (ThreadMessage message in client.GetMessagesAsync(threadId, ListOrder.NewestFirst, cancellationToken).ConfigureAwait(false)) { - messages = await client.GetMessagesAsync(threadId, limit: 100, ListSortOrder.Descending, after: lastId, null, cancellationToken).ConfigureAwait(false); - foreach (ThreadMessage message in messages) - { - AuthorRole role = new(message.Role.ToString()); + AuthorRole role = new(message.Role.ToString()); - string? assistantName = null; - if (!string.IsNullOrWhiteSpace(message.AssistantId) && - !agentNames.TryGetValue(message.AssistantId, out assistantName)) + string? assistantName = null; + if (!string.IsNullOrWhiteSpace(message.AssistantId) && + !agentNames.TryGetValue(message.AssistantId, out assistantName)) + { + Assistant assistant = await client.GetAssistantAsync(message.AssistantId).ConfigureAwait(false); // %%% CANCEL TOKEN + if (!string.IsNullOrWhiteSpace(assistant.Name)) { - Assistant assistant = await client.GetAssistantAsync(message.AssistantId, cancellationToken).ConfigureAwait(false); - if (!string.IsNullOrWhiteSpace(assistant.Name)) - { - agentNames.Add(assistant.Id, assistant.Name); - } + agentNames.Add(assistant.Id, assistant.Name); } + } - assistantName ??= message.AssistantId; - - foreach (MessageContent item in message.ContentItems) - { - ChatMessageContent? content = null; + assistantName ??= message.AssistantId; - if (item is MessageTextContent contentMessage) - { - content = GenerateTextMessageContent(assistantName, role, contentMessage); - } - else if (item is MessageImageFileContent contentImage) - { - content = GenerateImageFileContent(assistantName, role, contentImage); - } + foreach (MessageContent itemContent in message.Content) + { + ChatMessageContent? content = null; - if (content is not null) - { - yield return content; - } + if (!string.IsNullOrEmpty(itemContent.Text)) + { + content = GenerateTextMessageContent(assistantName, role, itemContent); + } + // Process image content + else if (itemContent.ImageFileId != null) + { + content = GenerateImageFileContent(assistantName, role, itemContent); } - lastId = message.Id; + if (content is not null) + { + yield return content; + } } } - while (messages.HasMore); } /// @@ -139,7 +138,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist /// Asynchronous enumeration of messages. public static async IAsyncEnumerable InvokeAsync( OpenAIAssistantAgent agent, - AssistantsClient client, + AssistantClient client, string threadId, OpenAIAssistantConfiguration.PollingConfiguration pollingConfiguration, ILogger logger, @@ -154,15 +153,17 @@ public static async IAsyncEnumerable InvokeAsync( logger.LogDebug("[{MethodName}] Creating run for agent/thrad: {AgentId}/{ThreadId}", nameof(InvokeAsync), agent.Id, threadId); - CreateRunOptions options = - new(agent.Id) + RunCreationOptions options = + new() { - OverrideInstructions = agent.Instructions, - OverrideTools = tools, + //InstructionsOverride = agent.Instructions, + //ParallelToolCallsEnabled = true, // %%% + //ResponseFormat = %%% + //ToolsOverride = tools, %%% }; // Create run - ThreadRun run = await client.CreateRunAsync(threadId, options, cancellationToken).ConfigureAwait(false); + ThreadRun run = await client.CreateRunAsync(threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); logger.LogInformation("[{MethodName}] Created run: {RunId}", nameof(InvokeAsync), run.Id); @@ -173,7 +174,7 @@ public static async IAsyncEnumerable InvokeAsync( do { // Poll run and steps until actionable - PageableList steps = await PollRunStatusAsync().ConfigureAwait(false); + await PollRunStatusAsync().ConfigureAwait(false); // Is in terminal state? if (s_terminalStatuses.Contains(run.Status)) @@ -181,13 +182,15 @@ public static async IAsyncEnumerable InvokeAsync( throw new KernelException($"Agent Failure - Run terminated: {run.Status} [{run.Id}]: {run.LastError?.Message ?? "Unknown"}"); } + RunStep[] steps = await client.GetRunStepsAsync(run).ToArrayAsync(cancellationToken).ConfigureAwait(false); + // Is tool action required? if (run.Status == RunStatus.RequiresAction) { logger.LogDebug("[{MethodName}] Processing run steps: {RunId}", nameof(InvokeAsync), run.Id); // Execute functions in parallel and post results at once. - FunctionCallContent[] activeFunctionSteps = steps.Data.SelectMany(step => ParseFunctionStep(agent, step)).ToArray(); + FunctionCallContent[] activeFunctionSteps = steps.SelectMany(step => ParseFunctionStep(agent, step)).ToArray(); if (activeFunctionSteps.Length > 0) { // Emit function-call content @@ -202,7 +205,7 @@ public static async IAsyncEnumerable InvokeAsync( // Process tool output ToolOutput[] toolOutputs = GenerateToolOutputs(functionResults); - await client.SubmitToolOutputsToRunAsync(run, toolOutputs, cancellationToken).ConfigureAwait(false); + await client.SubmitToolOutputsToRunAsync(run, toolOutputs).ConfigureAwait(false); // %%% CANCEL TOKEN } if (logger.IsEnabled(LogLevel.Information)) // Avoid boxing if not enabled @@ -222,24 +225,22 @@ public static async IAsyncEnumerable InvokeAsync( int messageCount = 0; foreach (RunStep completedStep in completedStepsToProcess) { - if (completedStep.Type.Equals(RunStepType.ToolCalls)) + if (completedStep.Type == RunStepType.ToolCalls) { - RunStepToolCallDetails toolCallDetails = (RunStepToolCallDetails)completedStep.StepDetails; - - foreach (RunStepToolCall toolCall in toolCallDetails.ToolCalls) + foreach (RunStepToolCall toolCall in completedStep.Details.ToolCalls) { ChatMessageContent? content = null; // Process code-interpreter content - if (toolCall is RunStepCodeInterpreterToolCall toolCodeInterpreter) + if (toolCall.ToolKind == RunStepToolCallKind.CodeInterpreter) { - content = GenerateCodeInterpreterContent(agent.GetName(), toolCodeInterpreter); + content = GenerateCodeInterpreterContent(agent.GetName(), toolCall.CodeInterpreterInput); } // Process function result content - else if (toolCall is RunStepFunctionToolCall toolFunction) + else if (toolCall.ToolKind == RunStepToolCallKind.Function) { - FunctionCallContent functionStep = functionSteps[toolFunction.Id]; // Function step always captured on invocation - content = GenerateFunctionResultContent(agent.GetName(), functionStep, toolFunction.Output); + FunctionCallContent functionStep = functionSteps[toolCall.ToolCallId]; // Function step always captured on invocation + content = GenerateFunctionResultContent(agent.GetName(), functionStep, toolCall.FunctionOutput); } if (content is not null) @@ -250,30 +251,28 @@ public static async IAsyncEnumerable InvokeAsync( } } } - else if (completedStep.Type.Equals(RunStepType.MessageCreation)) + else if (completedStep.Type == RunStepType.MessageCreation) { - RunStepMessageCreationDetails messageCreationDetails = (RunStepMessageCreationDetails)completedStep.StepDetails; - // Retrieve the message - ThreadMessage? message = await RetrieveMessageAsync(messageCreationDetails, cancellationToken).ConfigureAwait(false); + ThreadMessage? message = await RetrieveMessageAsync(completedStep.Details.CreatedMessageId, cancellationToken).ConfigureAwait(false); if (message is not null) { AuthorRole role = new(message.Role.ToString()); - foreach (MessageContent itemContent in message.ContentItems) + foreach (MessageContent itemContent in message.Content) { ChatMessageContent? content = null; // Process text content - if (itemContent is MessageTextContent contentMessage) + if (!string.IsNullOrEmpty(itemContent.Text)) { - content = GenerateTextMessageContent(agent.GetName(), role, contentMessage); + content = GenerateTextMessageContent(agent.GetName(), role, itemContent); } // Process image content - else if (itemContent is MessageImageFileContent contentImage) + else if (itemContent.ImageFileId != null) { - content = GenerateImageFileContent(agent.GetName(), role, contentImage); + content = GenerateImageFileContent(agent.GetName(), role, itemContent); } if (content is not null) @@ -299,7 +298,7 @@ public static async IAsyncEnumerable InvokeAsync( logger.LogInformation("[{MethodName}] Completed run: {RunId}", nameof(InvokeAsync), run.Id); // Local function to assist in run polling (participates in method closure). - async Task> PollRunStatusAsync() + async Task PollRunStatusAsync() { logger.LogInformation("[{MethodName}] Polling run status: {RunId}", nameof(PollRunStatusAsync), run.Id); @@ -325,39 +324,37 @@ async Task> PollRunStatusAsync() while (s_pollingStatuses.Contains(run.Status)); logger.LogInformation("[{MethodName}] Run status is {RunStatus}: {RunId}", nameof(PollRunStatusAsync), run.Status, run.Id); - - return await client.GetRunStepsAsync(run, cancellationToken: cancellationToken).ConfigureAwait(false); } // Local function to capture kernel function state for further processing (participates in method closure). IEnumerable ParseFunctionStep(OpenAIAssistantAgent agent, RunStep step) { - if (step.Status == RunStepStatus.InProgress && step.StepDetails is RunStepToolCallDetails callDetails) + if (step.Status == RunStepStatus.InProgress && step.Type == RunStepType.ToolCalls) { - foreach (RunStepFunctionToolCall toolCall in callDetails.ToolCalls.OfType()) + foreach (RunStepToolCall toolCall in step.Details.ToolCalls) { - var nameParts = FunctionName.Parse(toolCall.Name, FunctionDelimiter); + var nameParts = FunctionName.Parse(toolCall.FunctionName, FunctionDelimiter); KernelArguments functionArguments = []; - if (!string.IsNullOrWhiteSpace(toolCall.Arguments)) + if (!string.IsNullOrWhiteSpace(toolCall.FunctionArguments)) { - Dictionary arguments = JsonSerializer.Deserialize>(toolCall.Arguments)!; + Dictionary arguments = JsonSerializer.Deserialize>(toolCall.FunctionArguments)!; foreach (var argumentKvp in arguments) { functionArguments[argumentKvp.Key] = argumentKvp.Value.ToString(); } } - var content = new FunctionCallContent(nameParts.Name, nameParts.PluginName, toolCall.Id, functionArguments); + var content = new FunctionCallContent(nameParts.Name, nameParts.PluginName, toolCall.ToolCallId, functionArguments); - functionSteps.Add(toolCall.Id, content); + functionSteps.Add(toolCall.ToolCallId, content); yield return content; } } } - async Task RetrieveMessageAsync(RunStepMessageCreationDetails detail, CancellationToken cancellationToken) + async Task RetrieveMessageAsync(string messageId, CancellationToken cancellationToken) { ThreadMessage? message = null; @@ -367,7 +364,7 @@ IEnumerable ParseFunctionStep(OpenAIAssistantAgent agent, R { try { - message = await client.GetMessageAsync(threadId, detail.MessageCreation.MessageId, cancellationToken).ConfigureAwait(false); + message = await client.GetMessageAsync(threadId, messageId, cancellationToken).ConfigureAwait(false); } catch (RequestFailedException exception) { @@ -390,42 +387,43 @@ IEnumerable ParseFunctionStep(OpenAIAssistantAgent agent, R } } - private static AnnotationContent GenerateAnnotationContent(MessageTextAnnotation annotation) + private static AnnotationContent GenerateAnnotationContent(TextAnnotation annotation) { string? fileId = null; - if (annotation is MessageTextFileCitationAnnotation citationAnnotation) + + if (string.IsNullOrEmpty(annotation.OutputFileId)) { - fileId = citationAnnotation.FileId; + fileId = annotation.OutputFileId; } - else if (annotation is MessageTextFilePathAnnotation pathAnnotation) + else if (string.IsNullOrEmpty(annotation.InputFileId)) { - fileId = pathAnnotation.FileId; + fileId = annotation.InputFileId; } return new() { - Quote = annotation.Text, + Quote = annotation.TextToReplace, StartIndex = annotation.StartIndex, EndIndex = annotation.EndIndex, FileId = fileId, }; } - private static ChatMessageContent GenerateImageFileContent(string agentName, AuthorRole role, MessageImageFileContent contentImage) + private static ChatMessageContent GenerateImageFileContent(string agentName, AuthorRole role, MessageContent contentImage) { return new ChatMessageContent( role, [ - new FileReferenceContent(contentImage.FileId) + new FileReferenceContent(contentImage.ImageFileId) ]) { AuthorName = agentName, }; } - private static ChatMessageContent? GenerateTextMessageContent(string agentName, AuthorRole role, MessageTextContent contentMessage) + private static ChatMessageContent? GenerateTextMessageContent(string agentName, AuthorRole role, MessageContent contentMessage) { ChatMessageContent? messageContent = null; @@ -439,7 +437,7 @@ private static ChatMessageContent GenerateImageFileContent(string agentName, Aut AuthorName = agentName }; - foreach (MessageTextAnnotation annotation in contentMessage.Annotations) + foreach (TextAnnotation annotation in contentMessage.TextAnnotations) { messageContent.Items.Add(GenerateAnnotationContent(annotation)); } @@ -448,13 +446,13 @@ private static ChatMessageContent GenerateImageFileContent(string agentName, Aut return messageContent; } - private static ChatMessageContent GenerateCodeInterpreterContent(string agentName, RunStepCodeInterpreterToolCall contentCodeInterpreter) + private static ChatMessageContent GenerateCodeInterpreterContent(string agentName, string code) { return new ChatMessageContent( AuthorRole.Tool, [ - new TextContent(contentCodeInterpreter.Input) + new TextContent(code) ]) { AuthorName = agentName, diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 6df555f5b15e..35705dbafb82 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -147,7 +147,8 @@ public static async Task RetrieveAsync( /// The thread identifier public async Task CreateThreadAsync(CancellationToken cancellationToken = default) { - AssistantThread thread = await this._client.CreateThreadAsync(cancellationToken).ConfigureAwait(false); + ThreadCreationOptions options = new(); // %%% + AssistantThread thread = await this._client.CreateThreadAsync(options, cancellationToken).ConfigureAwait(false); return thread.Id; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs index b84ef800ebd4..48faf44dab40 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs @@ -2,17 +2,17 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Azure.AI.OpenAI.Assistants; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// A specialization for use with . /// -internal sealed class OpenAIAssistantChannel(AssistantsClient client, string threadId, OpenAIAssistantConfiguration.PollingConfiguration pollingConfiguration) +internal sealed class OpenAIAssistantChannel(AssistantClient client, string threadId, OpenAIAssistantConfiguration.PollingConfiguration pollingConfiguration) : AgentChannel { - private readonly AssistantsClient _client = client; + private readonly AssistantClient _client = client; private readonly string _threadId = threadId; /// From d7a31eb41cd5ca2b9858b0e147a8e0502eefaf22 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 3 Jul 2024 11:57:46 -0700 Subject: [PATCH 003/121] Fix tool setup --- dotnet/src/Agents/OpenAI/AssistantThreadActions.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs index b5376f52d81d..a2f134abd9a2 100644 --- a/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs @@ -159,9 +159,14 @@ public static async IAsyncEnumerable InvokeAsync( //InstructionsOverride = agent.Instructions, //ParallelToolCallsEnabled = true, // %%% //ResponseFormat = %%% - //ToolsOverride = tools, %%% }; + foreach (ToolDefinition tool in tools) // %%% + { + options.ToolsOverride.Add(tool); + } + + // Create run ThreadRun run = await client.CreateRunAsync(threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); From efed79b1ee752c87cdae251384b28f89ec00e001 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 3 Jul 2024 12:34:30 -0700 Subject: [PATCH 004/121] Agent concepts and integration tests --- .../Agents/OpenAIAssistant_CodeInterpreter.cs | 56 ++++++++ dotnet/samples/ConceptsV2/ConceptsV2.csproj | 12 +- .../Step8_OpenAIAssistant.cs | 2 +- .../Agents/OpenAI/AssistantThreadActions.cs | 1 - .../Agents/OpenAIAssistantAgentTests.cs | 120 ++++++++++++++++++ .../IntegrationTestsV2.csproj | 2 + .../src/IntegrationTestsV2/testsettings.json | 97 ++++++++++++++ .../samples/InternalUtilities/BaseTest.cs | 2 +- 8 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs create mode 100644 dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs create mode 100644 dotnet/src/IntegrationTestsV2/testsettings.json diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs new file mode 100644 index 000000000000..cb110e8e0bad --- /dev/null +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace Agents; + +/// +/// Demonstrate using code-interpreter on . +/// +public class OpenAIAssistant_CodeInterpreter(ITestOutputHelper output) : BaseTest(output) +{ + protected override bool ForceOpenAI => true; + + [Fact] + public async Task UseCodeInterpreterToolWithOpenAIAssistantAgentAsync() + { + // Define the agent + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + kernel: new(), + config: new(this.ApiKey, this.Endpoint), + new() + { + EnableCodeInterpreter = true, // Enable code-interpreter + Model = this.Model, + }); + + // Create a chat for agent interaction. + var chat = new AgentGroupChat(); + + // Respond to user input + try + { + await InvokeAgentAsync("Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?"); + } + finally + { + await agent.DeleteAsync(); + } + + // Local function to invoke agent and display the conversation messages. + async Task InvokeAgentAsync(string input) + { + chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + + Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + + await foreach (var content in chat.InvokeAsync(agent)) + { + Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + } + } + } +} diff --git a/dotnet/samples/ConceptsV2/ConceptsV2.csproj b/dotnet/samples/ConceptsV2/ConceptsV2.csproj index a9fe41232166..e5185dd02bc1 100644 --- a/dotnet/samples/ConceptsV2/ConceptsV2.csproj +++ b/dotnet/samples/ConceptsV2/ConceptsV2.csproj @@ -1,4 +1,4 @@ - + Concepts @@ -41,12 +41,17 @@ + + + + + - + @@ -69,4 +74,7 @@ Always + + + diff --git a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs index dac184ea2c9a..c0395c7ca26e 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs @@ -14,7 +14,7 @@ namespace GettingStarted; /// public class Step8_OpenAIAssistant(ITestOutputHelper output) : BaseTest(output) { - protected override bool ForceOpenAI => true; + protected override bool ForceOpenAI => false; private const string HostName = "Host"; private const string HostInstructions = "Answer questions about the menu."; diff --git a/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs index a2f134abd9a2..0d7153816b2f 100644 --- a/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs @@ -166,7 +166,6 @@ public static async IAsyncEnumerable InvokeAsync( options.ToolsOverride.Add(tool); } - // Create run ThreadRun run = await client.CreateRunAsync(threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs new file mode 100644 index 000000000000..18628dbf66e1 --- /dev/null +++ b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.ComponentModel; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; +using SemanticKernel.IntegrationTests.TestSettings; +using Xunit; + +namespace SemanticKernel.IntegrationTests.Agents.OpenAI; + +#pragma warning disable xUnit1004 // Contains test methods used in manual verification. Disable warning for this file only. + +public sealed class OpenAIAssistantAgentTests +{ + private readonly IConfigurationRoot _configuration = new ConfigurationBuilder() + .AddJsonFile(path: "testsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile(path: "testsettings.development.json", optional: true, reloadOnChange: true) + .AddEnvironmentVariables() + .AddUserSecrets() + .Build(); + + /// + /// Integration test for using function calling + /// and targeting Open AI services. + /// + [Theory(Skip = "OpenAI will often throttle requests. This test is for manual verification.")] + [InlineData("What is the special soup?", "Clam Chowder")] + public async Task OpenAIAssistantAgentTestAsync(string input, string expectedAnswerContains) + { + var openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); + Assert.NotNull(openAIConfiguration); + + await this.ExecuteAgentAsync( + new(openAIConfiguration.ApiKey), + openAIConfiguration.ModelId, + input, + expectedAnswerContains); + } + + /// + /// Integration test for using function calling + /// and targeting Azure OpenAI services. + /// + [Theory/*(Skip = "No supported endpoint configured.")*/] + [InlineData("What is the special soup?", "Clam Chowder")] + public async Task AzureOpenAIAssistantAgentAsync(string input, string expectedAnswerContains) + { + var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); + Assert.NotNull(azureOpenAIConfiguration); + + await this.ExecuteAgentAsync( + new(azureOpenAIConfiguration.ApiKey, azureOpenAIConfiguration.Endpoint), + azureOpenAIConfiguration.ChatDeploymentName!, + input, + expectedAnswerContains); + } + + private async Task ExecuteAgentAsync( + OpenAIAssistantConfiguration config, + string modelName, + string input, + string expected) + { + // Arrange + Kernel kernel = new(); + + KernelPlugin plugin = KernelPluginFactory.CreateFromType(); + kernel.Plugins.Add(plugin); + + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + kernel, + config, + new() + { + Instructions = "Answer questions about the menu.", + Model = modelName, + }); + + AgentGroupChat chat = new(); + chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + + // Act + StringBuilder builder = new(); + await foreach (var message in chat.InvokeAsync(agent)) + { + builder.Append(message.Content); + } + + // Assert + Assert.Contains(expected, builder.ToString(), StringComparison.OrdinalIgnoreCase); + } + + public sealed class MenuPlugin + { + [KernelFunction, Description("Provides a list of specials from the menu.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")] + public string GetSpecials() + { + return @" +Special Soup: Clam Chowder +Special Salad: Cobb Salad +Special Drink: Chai Tea +"; + } + + [KernelFunction, Description("Provides the price of the requested menu item.")] + public string GetItemPrice( + [Description("The name of the menu item.")] + string menuItem) + { + return "$9.99"; + } + } +} diff --git a/dotnet/src/IntegrationTestsV2/IntegrationTestsV2.csproj b/dotnet/src/IntegrationTestsV2/IntegrationTestsV2.csproj index 13bcc5ba0f44..fc22c692e42f 100644 --- a/dotnet/src/IntegrationTestsV2/IntegrationTestsV2.csproj +++ b/dotnet/src/IntegrationTestsV2/IntegrationTestsV2.csproj @@ -42,6 +42,8 @@ + + diff --git a/dotnet/src/IntegrationTestsV2/testsettings.json b/dotnet/src/IntegrationTestsV2/testsettings.json new file mode 100644 index 000000000000..66df73f8b7a5 --- /dev/null +++ b/dotnet/src/IntegrationTestsV2/testsettings.json @@ -0,0 +1,97 @@ +{ + "OpenAI": { + "ServiceId": "gpt-3.5-turbo-instruct", + "ModelId": "gpt-3.5-turbo-instruct", + "ApiKey": "" + }, + "AzureOpenAI": { + "ServiceId": "azure-gpt-35-turbo-instruct", + "DeploymentName": "gpt-35-turbo-instruct", + "ChatDeploymentName": "gpt-4", + "Endpoint": "", + "ApiKey": "" + }, + "OpenAIEmbeddings": { + "ServiceId": "text-embedding-ada-002", + "ModelId": "text-embedding-ada-002", + "ApiKey": "" + }, + "AzureOpenAIEmbeddings": { + "ServiceId": "azure-text-embedding-ada-002", + "DeploymentName": "ada-002", + "Endpoint": "", + "ApiKey": "" + }, + "OpenAITextToAudio": { + "ServiceId": "tts-1", + "ModelId": "tts-1", + "ApiKey": "" + }, + "AzureOpenAITextToAudio": { + "ServiceId": "azure-tts", + "DeploymentName": "tts", + "Endpoint": "", + "ApiKey": "" + }, + "OpenAIAudioToText": { + "ServiceId": "whisper-1", + "ModelId": "whisper-1", + "ApiKey": "" + }, + "AzureOpenAIAudioToText": { + "ServiceId": "azure-whisper", + "DeploymentName": "whisper", + "Endpoint": "", + "ApiKey": "" + }, + "HuggingFace": { + "ApiKey": "" + }, + "GoogleAI": { + "EmbeddingModelId": "embedding-001", + "ApiKey": "", + "Gemini": { + "ModelId": "gemini-1.5-flash", + "VisionModelId": "gemini-1.5-flash" + } + }, + "VertexAI": { + "EmbeddingModelId": "textembedding-gecko@003", + "BearerKey": "", + "Location": "us-central1", + "ProjectId": "", + "Gemini": { + "ModelId": "gemini-1.5-flash", + "VisionModelId": "gemini-1.5-flash" + } + }, + "Bing": { + "ApiKey": "" + }, + "Postgres": { + "ConnectionString": "" + }, + "MongoDB": { + "ConnectionString": "", + "VectorSearchCollection": "dotnetMSKNearestTest.nearestSearch" + }, + "AzureCosmosDB": { + "ConnectionString": "" + }, + "SqlServer": { + "ConnectionString": "" + }, + "Planners": { + "AzureOpenAI": { + "ServiceId": "azure-gpt-35-turbo", + "DeploymentName": "gpt-35-turbo", + "Endpoint": "", + "ApiKey": "" + }, + "OpenAI": { + "ServiceId": "openai-gpt-4", + "ModelId": "gpt-4", + "ApiKey": "" + } + } +} \ No newline at end of file diff --git a/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs b/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs index b9a76dd1b117..671524865d33 100644 --- a/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs +++ b/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs @@ -42,7 +42,7 @@ protected Kernel CreateKernelWithChatCompletion() if (this.UseOpenAIConfig) { - //builder.AddOpenAIChatCompletion( %%% + //builder.AddOpenAIChatCompletion( // %%% // TestConfiguration.OpenAI.ChatModelId, // TestConfiguration.OpenAI.ApiKey); } From 348cc9b9349487a125f6675e520a30b09944fa0f Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 5 Jul 2024 10:40:27 -0700 Subject: [PATCH 005/121] Checkpoint --- .../AddHeaderRequestPolicy.cs | 2 +- .../{ => Internal}/AssistantThreadActions.cs | 13 +- .../OpenAI/Internal/OpenAIClientFactory.cs | 109 +++++++++ .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 215 +++++------------- .../Agents/OpenAI/OpenAIAssistantChannel.cs | 4 +- .../OpenAI/OpenAIAssistantConfiguration.cs | 85 ------- .../OpenAI/OpenAIAssistantDefinition.cs | 13 +- .../src/Agents/OpenAI/OpenAIConfiguration.cs | 96 ++++++++ dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs | 90 ++++++++ .../Agents/OpenAI/OpenAIVectorStoreBuilder.cs | 144 ++++++++++++ .../Agents/OpenAI/RunPollingConfiguration.cs | 40 ++++ .../Azure/AddHeaderRequestPolicyTests.cs | 2 +- .../src/Http/HttpHeaderConstant.cs | 3 + 13 files changed, 550 insertions(+), 266 deletions(-) rename dotnet/src/Agents/OpenAI/{Azure => Internal}/AddHeaderRequestPolicy.cs (88%) rename dotnet/src/Agents/OpenAI/{ => Internal}/AssistantThreadActions.cs (96%) create mode 100644 dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs delete mode 100644 dotnet/src/Agents/OpenAI/OpenAIAssistantConfiguration.cs create mode 100644 dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs create mode 100644 dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs create mode 100644 dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs create mode 100644 dotnet/src/Agents/OpenAI/RunPollingConfiguration.cs diff --git a/dotnet/src/Agents/OpenAI/Azure/AddHeaderRequestPolicy.cs b/dotnet/src/Agents/OpenAI/Internal/AddHeaderRequestPolicy.cs similarity index 88% rename from dotnet/src/Agents/OpenAI/Azure/AddHeaderRequestPolicy.cs rename to dotnet/src/Agents/OpenAI/Internal/AddHeaderRequestPolicy.cs index 084e533fe757..1fb0698ffa77 100644 --- a/dotnet/src/Agents/OpenAI/Azure/AddHeaderRequestPolicy.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AddHeaderRequestPolicy.cs @@ -2,7 +2,7 @@ using Azure.Core; using Azure.Core.Pipeline; -namespace Microsoft.SemanticKernel.Agents.OpenAI.Azure; +namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Helper class to inject headers into Azure SDK HTTP pipeline diff --git a/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs similarity index 96% rename from dotnet/src/Agents/OpenAI/AssistantThreadActions.cs rename to dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 0d7153816b2f..72358eb56eec 100644 --- a/dotnet/src/Agents/OpenAI/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -132,7 +132,6 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist /// The assistant agent to interact with the thread. /// The assistant client /// The thread identifier - /// Config to utilize when polling for run state. /// The logger to utilize (might be agent or channel scoped) /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. @@ -140,7 +139,6 @@ public static async IAsyncEnumerable InvokeAsync( OpenAIAssistantAgent agent, AssistantClient client, string threadId, - OpenAIAssistantConfiguration.PollingConfiguration pollingConfiguration, ILogger logger, [EnumeratorCancellation] CancellationToken cancellationToken) { @@ -149,7 +147,7 @@ public static async IAsyncEnumerable InvokeAsync( throw new KernelException($"Agent Failure - {nameof(OpenAIAssistantAgent)} agent is deleted: {agent.Id}."); } - ToolDefinition[]? tools = [.. agent.Tools, .. agent.Kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name, FunctionDelimiter)))]; + ToolDefinition[]? tools = [.. agent.Definition.Tools, .. agent.Kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name, FunctionDelimiter)))]; logger.LogDebug("[{MethodName}] Creating run for agent/thrad: {AgentId}/{ThreadId}", nameof(InvokeAsync), agent.Id, threadId); @@ -161,10 +159,7 @@ public static async IAsyncEnumerable InvokeAsync( //ResponseFormat = %%% }; - foreach (ToolDefinition tool in tools) // %%% - { - options.ToolsOverride.Add(tool); - } + options.ToolsOverride.AddRange(tools); // Create run ThreadRun run = await client.CreateRunAsync(threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); @@ -311,7 +306,7 @@ async Task PollRunStatusAsync() do { // Reduce polling frequency after a couple attempts - await Task.Delay(count >= 2 ? pollingConfiguration.RunPollingInterval : pollingConfiguration.RunPollingBackoff, cancellationToken).ConfigureAwait(false); + await Task.Delay(count >= 2 ? agent.Polling.RunPollingInterval : agent.Polling.RunPollingBackoff, cancellationToken).ConfigureAwait(false); ++count; #pragma warning disable CA1031 // Do not catch general exception types @@ -380,7 +375,7 @@ IEnumerable ParseFunctionStep(OpenAIAssistantAgent agent, R if (retry) { - await Task.Delay(pollingConfiguration.MessageSynchronizationDelay, cancellationToken).ConfigureAwait(false); + await Task.Delay(agent.Polling.MessageSynchronizationDelay, cancellationToken).ConfigureAwait(false); } ++count; diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs new file mode 100644 index 000000000000..bce45b415321 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.ClientModel.Primitives; +using System.Net.Http; +using System.Threading; +using Azure.AI.OpenAI; +using Microsoft.SemanticKernel.Http; +using OpenAI; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +internal static class OpenAIClientFactory +{ + /// + /// Avoids an exception from OpenAI Client when a custom endpoint is provided without an API key. + /// + private const string SingleSpaceKey = " "; + + /// + /// %%% + /// + /// + /// + public static OpenAIClient CreateClient(OpenAIConfiguration config) + { + OpenAIClient client; + + // Inspect options + switch (config.Type) + { + case OpenAIConfiguration.OpenAIConfigurationType.AzureOpenAIKey: + { + AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(config); + client = new AzureOpenAIClient(config.Endpoint, config.ApiKey!, clientOptions); + break; + } + case OpenAIConfiguration.OpenAIConfigurationType.AzureOpenAICredential: + { + AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(config); + client = new AzureOpenAIClient(config.Endpoint, config.Credentials!, clientOptions); + break; + } + case OpenAIConfiguration.OpenAIConfigurationType.OpenAI: + { + OpenAIClientOptions clientOptions = CreateOpenAIClientOptions(config); + client = new OpenAIClient(config.ApiKey ?? SingleSpaceKey, clientOptions); + break; + } + default: + throw new KernelException($"Unsupported configuration type: {config.Type}"); + } + + return client; + } + + private static AzureOpenAIClientOptions CreateAzureClientOptions(OpenAIConfiguration config) + { + AzureOpenAIClientOptions options = + new() + { + ApplicationId = HttpHeaderConstant.Values.UserAgent, + Endpoint = config.Endpoint, + }; + + ConfigureClientOptions(config.HttpClient, options); + + return options; + } + + private static OpenAIClientOptions CreateOpenAIClientOptions(OpenAIConfiguration config) + { + OpenAIClientOptions options = + new() + { + ApplicationId = HttpHeaderConstant.Values.UserAgent, + Endpoint = config.Endpoint ?? config.HttpClient?.BaseAddress, + }; + + if (!string.IsNullOrWhiteSpace(config.OrganizationId)) + { + options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.OpenAIOrganizationId, config.OrganizationId!), PipelinePosition.PerCall); + } + + ConfigureClientOptions(config.HttpClient, options); + + return options; + } + + private static void ConfigureClientOptions(HttpClient? httpClient, OpenAIClientOptions options) + { + options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall); + + if (httpClient is not null) + { + options.Transport = new HttpClientPipelineTransport(httpClient); + options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable retry policy if and only if a custom HttpClient is provided. + options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable default timeout + } + } + + private static GenericActionPipelinePolicy CreateRequestHeaderPolicy(string headerName, string headerValue) + => + new((message) => + { + if (message?.Request?.Headers?.TryGetValue(headerName, out string? _) == false) + { + message.Request.Headers.Set(headerName, headerValue); + } + }); +} diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 35705dbafb82..fca25fcc0dc4 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -1,15 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.ClientModel.Primitives; using System.Collections.Generic; using System.Linq; -using System.Net.Http; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Azure.AI.OpenAI; using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Http; using OpenAI; using OpenAI.Assistants; @@ -18,28 +12,15 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// A specialization based on Open AI Assistant / GPT. /// -public sealed partial class OpenAIAssistantAgent : KernelAgent +public sealed class OpenAIAssistantAgent : KernelAgent { - private readonly Assistant _assistant; private readonly AssistantClient _client; - private readonly OpenAIAssistantConfiguration _config; - - ///// - ///// A list of previously uploaded file IDs to attach to the assistant. - ///// - //public IReadOnlyList FileIds => this._assistant.FileIds; %%% + private readonly string[] _channelKeys; /// - /// A set of up to 16 key/value pairs that can be attached to an agent, used for - /// storing additional information about that object in a structured format.Keys - /// may be up to 64 characters in length and values may be up to 512 characters in length. + /// %%% /// - public IReadOnlyDictionary Metadata => this._assistant.Metadata; - - /// - /// Expose predefined tools. - /// - internal IReadOnlyList Tools => this._assistant.Tools; + public Assistant Definition { get; } /// /// Set when the assistant has been deleted via . @@ -47,6 +28,11 @@ public sealed partial class OpenAIAssistantAgent : KernelAgent /// public bool IsDeleted { get; private set; } + /// + /// %%% + /// + public RunPollingConfiguration Polling { get; } = new(); + /// /// Define a new . /// @@ -57,7 +43,7 @@ public sealed partial class OpenAIAssistantAgent : KernelAgent /// An instance public static async Task CreateAsync( Kernel kernel, - OpenAIAssistantConfiguration config, + OpenAIConfiguration config, OpenAIAssistantDefinition definition, CancellationToken cancellationToken = default) { @@ -75,7 +61,7 @@ public static async Task CreateAsync( // Instantiate the agent return - new OpenAIAssistantAgent(client, model, config) + new OpenAIAssistantAgent(client, model, DefineChannelKeys(config)) { Kernel = kernel, }; @@ -87,29 +73,14 @@ public static async Task CreateAsync( /// Configuration for accessing the Assistants API service, such as the api-key. /// The to monitor for cancellation requests. The default is . /// An list of objects. - public static async IAsyncEnumerable ListDefinitionsAsync( - OpenAIAssistantConfiguration config, - [EnumeratorCancellation] CancellationToken cancellationToken = default) + public static IAsyncEnumerable ListDefinitionsAsync( + OpenAIConfiguration config, + CancellationToken cancellationToken = default) { // Create the client AssistantClient client = CreateClient(config); - await foreach (Assistant assistant in client.GetAssistantsAsync(ListOrder.NewestFirst, cancellationToken).ConfigureAwait(false)) - { - yield return - new() - { - Id = assistant.Id, - Name = assistant.Name, - Description = assistant.Description, - Instructions = assistant.Instructions, - EnableCodeInterpreter = assistant.Tools.Any(t => t is CodeInterpreterToolDefinition), - EnableFileSearch = assistant.Tools.Any(t => t is FileSearchToolDefinition), - //FileIds = assistant.FileIds, %%% - Metadata = assistant.Metadata, - Model = assistant.Model, - }; - } + return client.GetAssistantsAsync(ListOrder.NewestFirst, cancellationToken); } /// @@ -122,7 +93,7 @@ public static async IAsyncEnumerable ListDefinitionsA /// An instance public static async Task RetrieveAsync( Kernel kernel, - OpenAIAssistantConfiguration config, + OpenAIConfiguration config, string id, CancellationToken cancellationToken = default) { @@ -134,7 +105,7 @@ public static async Task RetrieveAsync( // Instantiate the agent return - new OpenAIAssistantAgent(client, model, config) + new OpenAIAssistantAgent(client, model, DefineChannelKeys(config)) { Kernel = kernel, }; @@ -225,32 +196,11 @@ public IAsyncEnumerable InvokeAsync( { this.ThrowIfDeleted(); - return AssistantThreadActions.InvokeAsync(this, this._client, threadId, this._config.Polling, this.Logger, cancellationToken); + return AssistantThreadActions.InvokeAsync(this, this._client, threadId, this.Logger, cancellationToken); } /// - protected override IEnumerable GetChannelKeys() - { - // Distinguish from other channel types. - yield return typeof(AgentChannel).FullName!; - - // Distinguish between different Azure OpenAI endpoints or OpenAI services. - yield return this._config.Endpoint ?? "openai"; - - // Custom client receives dedicated channel. - if (this._config.HttpClient is not null) - { - if (this._config.HttpClient.BaseAddress is not null) - { - yield return this._config.HttpClient.BaseAddress.AbsoluteUri; - } - - foreach (string header in this._config.HttpClient.DefaultRequestHeaders.SelectMany(h => h.Value)) - { - yield return header; - } - } - } + protected override IEnumerable GetChannelKeys() => this._channelKeys; /// protected override async Task CreateChannelAsync(CancellationToken cancellationToken) @@ -262,7 +212,7 @@ protected override async Task CreateChannelAsync(CancellationToken this.Logger.LogInformation("[{MethodName}] Created assistant thread: {ThreadId}", nameof(CreateChannelAsync), thread.Id); return - new OpenAIAssistantChannel(this._client, thread.Id, this._config.Polling) + new OpenAIAssistantChannel(this._client, thread.Id) { Logger = this.LoggerFactory.CreateLogger() }; @@ -282,35 +232,16 @@ internal void ThrowIfDeleted() private OpenAIAssistantAgent( AssistantClient client, Assistant model, - OpenAIAssistantConfiguration config) + IEnumerable channelKeys) { - this._assistant = model; + this.Definition = model; this._client = client; - this._config = config; - - this.Description = this._assistant.Description; - this.Id = this._assistant.Id; - this.Name = this._assistant.Name; - this.Instructions = this._assistant.Instructions; - } - - private static AzureOpenAIClientOptions GetAzureOpenAIClientOptions(HttpClient? httpClient) - { - AzureOpenAIClientOptions options = new() - { - ApplicationId = HttpHeaderConstant.Values.UserAgent, - }; - - options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall); - - if (httpClient is not null) - { - options.Transport = new HttpClientPipelineTransport(httpClient); - options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable Azure SDK retry policy if and only if a custom HttpClient is provided. - options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable Azure SDK default timeout - } + this._channelKeys = channelKeys.ToArray(); - return options; + this.Description = model.Description; + this.Id = model.Id; + this.Name = model.Name; + this.Instructions = model.Instructions; } private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAssistantDefinition definition) @@ -323,95 +254,57 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss Name = definition.Name, // %%% ResponseFormat = // %%% Temperature = - // %%% ToolResources - //assistantCreationOptions.FileIds.AddRange(definition.FileIds ?? []); %%% - // %%% NucleusSamplingFactor + // %%% NucleusSamplingFactor = }; - // %%% COPY METADATA - // Metadata = definition.Metadata?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + if (definition.Metadata != null) + { + foreach (KeyValuePair item in definition.Metadata) + { + assistantCreationOptions.Metadata[item.Key] = item.Value; + } + } if (definition.EnableCodeInterpreter) { assistantCreationOptions.Tools.Add(new CodeInterpreterToolDefinition()); } - if (definition.EnableFileSearch) + if (!string.IsNullOrWhiteSpace(definition.VectorStoreId)) { assistantCreationOptions.Tools.Add(new FileSearchToolDefinition()); + assistantCreationOptions.ToolResources.FileSearch.VectorStoreIds.Add(definition.VectorStoreId); } return assistantCreationOptions; } - private static AssistantClient CreateClient(OpenAIAssistantConfiguration config) + private static AssistantClient CreateClient(OpenAIConfiguration config) { - OpenAIClient client; - - // Inspect options - if (!string.IsNullOrWhiteSpace(config.Endpoint)) // %%% INSUFFICENT (BOTH HAVE ENDPOINT OPTION) - { - // Create client configured for Azure OpenAI, if endpoint definition is present. - AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(config.HttpClient); - client = new AzureOpenAIClient(new Uri(config.Endpoint), config.ApiKey, clientOptions); - } - else - { - // Otherwise, create client configured for OpenAI. - OpenAIClientOptions clientOptions = CreateClientOptions(config.HttpClient, config.Endpoint); - client = new OpenAIClient(config.ApiKey, clientOptions); - } - - return client.GetAssistantClient(); - } - - internal static AzureOpenAIClientOptions CreateAzureClientOptions(HttpClient? httpClient) - { - AzureOpenAIClientOptions options = new() - { - ApplicationId = HttpHeaderConstant.Values.UserAgent, - }; - - options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall); - - if (httpClient is not null) - { - options.Transport = new HttpClientPipelineTransport(httpClient); - options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable Azure SDK retry policy if and only if a custom HttpClient is provided. - options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable Azure SDK default timeout - } - - return options; + OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); + return openAIClient.GetAssistantClient(); } - private static OpenAIClientOptions CreateClientOptions(HttpClient? httpClient, string? endpoint) + private static IEnumerable DefineChannelKeys(OpenAIConfiguration config) { - OpenAIClientOptions options = new() - { - ApplicationId = HttpHeaderConstant.Values.UserAgent, - Endpoint = string.IsNullOrEmpty(endpoint) ? null : new Uri(endpoint) - }; + // Distinguish from other channel types. + yield return typeof(AgentChannel).FullName!; - options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall); + // Distinguish between different Azure OpenAI endpoints or OpenAI services. + yield return config.Endpoint != null ? config.Endpoint.ToString() : "openai"; - if (httpClient is not null) + // Custom client receives dedicated channel. + if (config.HttpClient is not null) { - options.Transport = new HttpClientPipelineTransport(httpClient); - options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable retry policy if and only if a custom HttpClient is provided. - options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable default timeout - } - - return options; - } + if (config.HttpClient.BaseAddress is not null) + { + yield return config.HttpClient.BaseAddress.AbsoluteUri; + } - private static GenericActionPipelinePolicy CreateRequestHeaderPolicy(string headerName, string headerValue) - { - return new GenericActionPipelinePolicy((message) => - { - if (message?.Request?.Headers?.TryGetValue(headerName, out string? _) == false) + foreach (string header in config.HttpClient.DefaultRequestHeaders.SelectMany(h => h.Value)) { - message.Request.Headers.Set(headerName, headerValue); + yield return header; } - }); + } } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs index 48faf44dab40..19bbf4eb3294 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs @@ -9,7 +9,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// A specialization for use with . /// -internal sealed class OpenAIAssistantChannel(AssistantClient client, string threadId, OpenAIAssistantConfiguration.PollingConfiguration pollingConfiguration) +internal sealed class OpenAIAssistantChannel(AssistantClient client, string threadId) : AgentChannel { private readonly AssistantClient _client = client; @@ -31,7 +31,7 @@ protected override IAsyncEnumerable InvokeAsync( { agent.ThrowIfDeleted(); - return AssistantThreadActions.InvokeAsync(agent, this._client, this._threadId, pollingConfiguration, this.Logger, cancellationToken); + return AssistantThreadActions.InvokeAsync(agent, this._client, this._threadId, this.Logger, cancellationToken); } /// diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantConfiguration.cs deleted file mode 100644 index acc094b86969..000000000000 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantConfiguration.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using System.Net.Http; - -namespace Microsoft.SemanticKernel.Agents.OpenAI; - -/// -/// Configuration to target an OpenAI Assistant API. -/// -public sealed class OpenAIAssistantConfiguration -{ - /// - /// The Assistants API Key. - /// - public string ApiKey { get; } - - /// - /// An optional endpoint if targeting Azure OpenAI Assistants API. - /// - public string? Endpoint { get; } - - /// - /// Custom for HTTP requests. - /// - public HttpClient? HttpClient { get; init; } - - /// - /// Defineds polling behavior for Assistant API requests. - /// - public PollingConfiguration Polling { get; } = new(); - - /// - /// Initializes a new instance of the class. - /// - /// The Assistants API Key - /// An optional endpoint if targeting Azure OpenAI Assistants API - public OpenAIAssistantConfiguration(string apiKey, string? endpoint = null) - { - Verify.NotNullOrWhiteSpace(apiKey); - if (!string.IsNullOrWhiteSpace(endpoint)) - { - // Only verify `endpoint` when provided (AzureOAI vs OpenAI) - Verify.StartsWith(endpoint, "https://", "The Azure OpenAI endpoint must start with 'https://'"); - } - - this.ApiKey = apiKey; - this.Endpoint = endpoint; - } - - /// - /// Configuration and defaults associated with polling behavior for Assistant API requests. - /// - public sealed class PollingConfiguration - { - /// - /// The default polling interval when monitoring thread-run status. - /// - public static TimeSpan DefaultPollingInterval { get; } = TimeSpan.FromMilliseconds(500); - - /// - /// The default back-off interval when monitoring thread-run status. - /// - public static TimeSpan DefaultPollingBackoff { get; } = TimeSpan.FromSeconds(1); - - /// - /// The default polling delay when retrying message retrieval due to a 404/NotFound from synchronization lag. - /// - public static TimeSpan DefaultMessageSynchronizationDelay { get; } = TimeSpan.FromMilliseconds(500); - - /// - /// The polling interval when monitoring thread-run status. - /// - public TimeSpan RunPollingInterval { get; set; } = DefaultPollingInterval; - - /// - /// The back-off interval when monitoring thread-run status. - /// - public TimeSpan RunPollingBackoff { get; set; } = DefaultPollingBackoff; - - /// - /// The polling delay when retrying message retrieval due to a 404/NotFound from synchronization lag. - /// - public TimeSpan MessageSynchronizationDelay { get; set; } = DefaultMessageSynchronizationDelay; - } -} diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index e3e8706abf47..47fd333d348b 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -9,7 +9,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; public sealed class OpenAIAssistantDefinition { /// - /// Identifies the AI model (OpenAI) or deployment (AzureOAI) this agent targets. + /// Identifies the AI model targeted by the agent. /// public string? Model { get; init; } @@ -39,14 +39,13 @@ public sealed class OpenAIAssistantDefinition public bool EnableCodeInterpreter { get; init; } /// - /// Set if retrieval is enabled. + /// Enables file-serach if specified. /// - public bool EnableFileSearch { get; init; } + public string? VectorStoreId { get; init; } - /// - /// A list of previously uploaded file IDs to attach to the assistant. - /// - public IEnumerable? FileIds { get; init; } + // %%% ResponseFormat + // %%% Temperature + // %%% NucleusSamplingFactor /// /// A set of up to 16 key/value pairs that can be attached to an agent, used for diff --git a/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs new file mode 100644 index 000000000000..a0c606beca06 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Net.Http; +using Azure.Core; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// Configuration for OpenAI services. +/// +public sealed class OpenAIConfiguration +{ + internal enum OpenAIConfigurationType + { + AzureOpenAIKey, + AzureOpenAICredential, + OpenAI, + } + + /// + /// %%% + /// + /// + /// + /// + /// + public static OpenAIConfiguration ForAzureOpenAI(Uri endpoint, string apiKey, HttpClient? httpClient = null) => + // %%% VERIFY + new() + { + ApiKey = apiKey, + Endpoint = endpoint, + HttpClient = httpClient, + Type = OpenAIConfigurationType.AzureOpenAIKey, + }; + + /// + /// %%% + /// + /// + /// + /// + /// + public static OpenAIConfiguration ForAzureOpenAI(Uri endpoint, TokenCredential credentials, HttpClient? httpClient = null) => + // %%% VERIFY + new() + { + Credentials = credentials, + Endpoint = endpoint, + HttpClient = httpClient, + Type = OpenAIConfigurationType.AzureOpenAICredential, + }; + + /// + /// %%% + /// + /// + /// + /// + /// + public static OpenAIConfiguration ForOpenAI(Uri endpoint, string apiKey, HttpClient? httpClient = null) => + // %%% VERIFY + new() + { + ApiKey = apiKey, + Endpoint = endpoint, + HttpClient = httpClient, + Type = OpenAIConfigurationType.OpenAI, + }; + + /// + /// %%% + /// + /// + /// + /// + /// + /// + public static OpenAIConfiguration ForOpenAI(Uri endpoint, string apiKey, string organizationId, HttpClient? httpClient = null) => + // %%% VERIFY + new() + { + ApiKey = apiKey, + Endpoint = endpoint, + HttpClient = httpClient, + OrganizationId = organizationId, + Type = OpenAIConfigurationType.OpenAI, + }; + + internal string? ApiKey { get; init; } + internal TokenCredential? Credentials { get; init; } + internal Uri? Endpoint { get; init; } + internal HttpClient? HttpClient { get; init; } + internal string? OrganizationId { get; init; } + internal OpenAIConfigurationType Type { get; init; } +} diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs new file mode 100644 index 000000000000..94d59cb88e7a --- /dev/null +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using OpenAI; +using OpenAI.VectorStores; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// %%% +/// +public sealed class OpenAIVectorStore +{ + private readonly VectorStoreClient _client; + + /// + /// %%% + /// + public string VectorStoreId { get; } + + /// + /// %%% + /// + /// + /// + /// + public static IAsyncEnumerable GetVectorStoresAsync(OpenAIConfiguration config, CancellationToken cancellationToken = default) + { + OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); + VectorStoreClient client = openAIClient.GetVectorStoreClient(); + + return client.GetVectorStoresAsync(ListOrder.NewestFirst, cancellationToken); + } + + /// + /// %%% + /// + /// + /// + public OpenAIVectorStore(string vectorStoreId, OpenAIConfiguration config) + { + OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); + this._client = openAIClient.GetVectorStoreClient(); + + this.VectorStoreId = vectorStoreId; + } + + // %%% BATCH JOBS ??? + + /// + /// %%% + /// + /// + /// + /// + public async Task AddFileAsync(string fileId, CancellationToken cancellationToken = default) => + await this._client.AddFileToVectorStoreAsync(this.VectorStoreId, fileId, cancellationToken).ConfigureAwait(false); + + /// + /// %%% + /// + /// + /// + public async Task DeleteAsync(CancellationToken cancellationToken = default) => + await this._client.DeleteVectorStoreAsync(this.VectorStoreId, cancellationToken).ConfigureAwait(false); + + /// + /// %%% + /// + /// + /// + public async IAsyncEnumerable GetFilesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (VectorStoreFileAssociation file in this._client.GetFileAssociationsAsync(this.VectorStoreId, ListOrder.NewestFirst, filter: null, cancellationToken).ConfigureAwait(false)) // %%% FILTER + { + yield return file.FileId; + } + } + + /// + /// %%% + /// + /// + /// + /// + public async Task RemoveFileAsync(string fileId, CancellationToken cancellationToken = default) => + await this._client.RemoveFileFromStoreAsync(this.VectorStoreId, fileId, cancellationToken).ConfigureAwait(false); +} diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs new file mode 100644 index 000000000000..555f3adfb7f3 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using OpenAI; +using OpenAI.VectorStores; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// %%% +/// +public sealed class OpenAIVectorStoreBuilder(OpenAIConfiguration config) +{ + private string? _name; + private FileChunkingStrategy? _chunkingStrategy; + private VectorStoreExpirationPolicy? _expirationPolicy; + private List? _fileIds; + private Dictionary? _metadata; + + /// + /// %%% + /// + /// + public OpenAIVectorStoreBuilder AddFile(string fileId) + { + this._fileIds ??= []; + this._fileIds.Add(fileId); + + return this; + } + + /// + /// %%% + /// + /// + public OpenAIVectorStoreBuilder AddFile(string[] fileIds) + { + this._fileIds ??= []; + this._fileIds.AddRange(fileIds); + + return this; + } + + /// + /// %%% + /// + /// + /// + public OpenAIVectorStoreBuilder WithChunkingStrategy(int maxTokensPerChunk, int overlappingTokenCount) + { + this._chunkingStrategy = FileChunkingStrategy.CreateStaticStrategy(maxTokensPerChunk, overlappingTokenCount); + + return this; + } + + /// + /// %%% + /// + /// + public OpenAIVectorStoreBuilder WithExpiration(TimeSpan duration) + { + this._expirationPolicy = new VectorStoreExpirationPolicy(VectorStoreExpirationAnchor.LastActiveAt, duration.Days); + + return this; + } + + /// + /// %%% + /// + /// + /// + /// + public OpenAIVectorStoreBuilder WithMetadata(string key, string value) + { + this._metadata ??= []; + + this._metadata[key] = value; + + return this; + } + + /// + /// %%% + /// + /// + /// + public OpenAIVectorStoreBuilder WithMetadata(IDictionary metadata) + { + this._metadata ??= []; + + foreach (KeyValuePair item in this._metadata) + { + this._metadata[item.Key] = item.Value; + } + + return this; + } + + /// + /// %%% + /// + /// + /// + public OpenAIVectorStoreBuilder WithName(string name) + { + this._name = name; + + return this; + } + + /// + /// %%% + /// + /// + /// + public async Task CreateAsync(CancellationToken cancellationToken) + { + OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); + VectorStoreClient client = openAIClient.GetVectorStoreClient(); + + VectorStoreCreationOptions options = + new() + { + FileIds = this._fileIds, + ChunkingStrategy = this._chunkingStrategy, + ExpirationPolicy = this._expirationPolicy, + Name = this._name, + }; + + if (this._metadata != null) + { + foreach (KeyValuePair item in this._metadata) + { + options.Metadata.Add(item.Key, item.Value); + } + } + + VectorStore store = await client.CreateVectorStoreAsync(options, cancellationToken).ConfigureAwait(false); + + return store; + } +} diff --git a/dotnet/src/Agents/OpenAI/RunPollingConfiguration.cs b/dotnet/src/Agents/OpenAI/RunPollingConfiguration.cs new file mode 100644 index 000000000000..e534128a4e49 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/RunPollingConfiguration.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// Configuration and defaults associated with polling behavior for Assistant API run processing. +/// +public sealed class RunPollingConfiguration +{ + /// + /// The default polling interval when monitoring thread-run status. + /// + public static TimeSpan DefaultPollingInterval { get; } = TimeSpan.FromMilliseconds(500); + + /// + /// The default back-off interval when monitoring thread-run status. + /// + public static TimeSpan DefaultPollingBackoff { get; } = TimeSpan.FromSeconds(1); + + /// + /// The default polling delay when retrying message retrieval due to a 404/NotFound from synchronization lag. + /// + public static TimeSpan DefaultMessageSynchronizationDelay { get; } = TimeSpan.FromMilliseconds(500); + + /// + /// The polling interval when monitoring thread-run status. + /// + public TimeSpan RunPollingInterval { get; set; } = DefaultPollingInterval; + + /// + /// The back-off interval when monitoring thread-run status. + /// + public TimeSpan RunPollingBackoff { get; set; } = DefaultPollingBackoff; + + /// + /// The polling delay when retrying message retrieval due to a 404/NotFound from synchronization lag. + /// + public TimeSpan MessageSynchronizationDelay { get; set; } = DefaultMessageSynchronizationDelay; +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs index b1e4d397eded..3c2945ad0fb9 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs @@ -2,7 +2,7 @@ using System.Linq; using Azure.Core; using Azure.Core.Pipeline; -using Microsoft.SemanticKernel.Agents.OpenAI.Azure; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI.Azure; diff --git a/dotnet/src/InternalUtilities/src/Http/HttpHeaderConstant.cs b/dotnet/src/InternalUtilities/src/Http/HttpHeaderConstant.cs index db45523ee3bd..a0d0dea0b50a 100644 --- a/dotnet/src/InternalUtilities/src/Http/HttpHeaderConstant.cs +++ b/dotnet/src/InternalUtilities/src/Http/HttpHeaderConstant.cs @@ -13,6 +13,9 @@ public static class Names { /// HTTP header name to use to include the Semantic Kernel package version in all HTTP requests issued by Semantic Kernel. public static string SemanticKernelVersion => "Semantic-Kernel-Version"; + + /// HTTP header name to use to include the Open AI organization identifier. + public static string OpenAIOrganizationId => "OpenAI-Organization"; } public static class Values From 9da6154babec19a45645e86cb937ba712ac52d1a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 5 Jul 2024 11:50:43 -0700 Subject: [PATCH 006/121] Checkpoint --- .../Agents/OpenAIAssistant_CodeInterpreter.cs | 4 +- .../Step8_OpenAIAssistant.cs | 2 +- .../Extensions/KernelFunctionExtensions.cs | 7 +- .../OpenAI/Internal/AssistantThreadActions.cs | 10 +-- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 65 +++++++++++++++---- .../src/Agents/OpenAI/OpenAIConfiguration.cs | 16 ++--- .../Azure/AddHeaderRequestPolicyTests.cs | 2 +- .../KernelFunctionExtensionsTests.cs | 4 +- .../OpenAI/OpenAIAssistantAgentTests.cs | 36 +++++----- .../OpenAIAssistantConfigurationTests.cs | 27 ++------ .../OpenAI/OpenAIAssistantDefinitionTests.cs | 9 +-- 11 files changed, 96 insertions(+), 86 deletions(-) diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs index cb110e8e0bad..83282d2eee82 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs @@ -11,7 +11,7 @@ namespace Agents; /// public class OpenAIAssistant_CodeInterpreter(ITestOutputHelper output) : BaseTest(output) { - protected override bool ForceOpenAI => true; + protected override bool ForceOpenAI => false; [Fact] public async Task UseCodeInterpreterToolWithOpenAIAssistantAgentAsync() @@ -20,7 +20,7 @@ public async Task UseCodeInterpreterToolWithOpenAIAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: new(this.ApiKey, this.Endpoint), + config: OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)), // %%% MODE new() { EnableCodeInterpreter = true, // Enable code-interpreter diff --git a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs index c0395c7ca26e..f282a1ee4c88 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs @@ -26,7 +26,7 @@ public async Task UseSingleOpenAIAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: new(this.ApiKey, this.Endpoint), + config: OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)), // %%% MODES new() { Instructions = HostInstructions, diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs index 63eec124f0ae..97a439729ff3 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs @@ -13,9 +13,8 @@ internal static class KernelFunctionExtensions /// /// The source function /// The plugin name - /// The delimiter character /// An OpenAI tool definition - public static FunctionToolDefinition ToToolDefinition(this KernelFunction function, string pluginName, string delimiter) + public static FunctionToolDefinition ToToolDefinition(this KernelFunction function, string pluginName) { var metadata = function.Metadata; if (metadata.Parameters.Count > 0) @@ -47,10 +46,10 @@ public static FunctionToolDefinition ToToolDefinition(this KernelFunction functi required, }; - return new FunctionToolDefinition(FunctionName.ToFullyQualifiedName(function.Name, pluginName, delimiter), function.Description, BinaryData.FromObjectAsJson(spec)); + return new FunctionToolDefinition(FunctionName.ToFullyQualifiedName(function.Name, pluginName), function.Description, BinaryData.FromObjectAsJson(spec)); } - return new FunctionToolDefinition(FunctionName.ToFullyQualifiedName(function.Name, pluginName, delimiter), function.Description); + return new FunctionToolDefinition(FunctionName.ToFullyQualifiedName(function.Name, pluginName), function.Description); } private static string ConvertType(Type? type) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 72358eb56eec..50d7d9d3b351 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -using System.ClientModel; using System.Collections.Generic; using System.Linq; using System.Net; @@ -20,9 +19,6 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// internal static class AssistantThreadActions { - /*AssistantsClient client, string threadId, OpenAIAssistantConfiguration.PollingConfiguration pollingConfiguration*/ - private const string FunctionDelimiter = "-"; - private static readonly HashSet s_messageRoles = [ AuthorRole.User, @@ -147,8 +143,6 @@ public static async IAsyncEnumerable InvokeAsync( throw new KernelException($"Agent Failure - {nameof(OpenAIAssistantAgent)} agent is deleted: {agent.Id}."); } - ToolDefinition[]? tools = [.. agent.Definition.Tools, .. agent.Kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name, FunctionDelimiter)))]; - logger.LogDebug("[{MethodName}] Creating run for agent/thrad: {AgentId}/{ThreadId}", nameof(InvokeAsync), agent.Id, threadId); RunCreationOptions options = @@ -159,7 +153,7 @@ public static async IAsyncEnumerable InvokeAsync( //ResponseFormat = %%% }; - options.ToolsOverride.AddRange(tools); + options.ToolsOverride.AddRange(agent.Tools); // Create run ThreadRun run = await client.CreateRunAsync(threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); @@ -332,7 +326,7 @@ IEnumerable ParseFunctionStep(OpenAIAssistantAgent agent, R { foreach (RunStepToolCall toolCall in step.Details.ToolCalls) { - var nameParts = FunctionName.Parse(toolCall.FunctionName, FunctionDelimiter); + var nameParts = FunctionName.Parse(toolCall.FunctionName); KernelArguments functionArguments = []; if (!string.IsNullOrWhiteSpace(toolCall.FunctionArguments)) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index fca25fcc0dc4..129f99642465 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -14,13 +15,14 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// public sealed class OpenAIAssistantAgent : KernelAgent { + private readonly Assistant _assistant; private readonly AssistantClient _client; private readonly string[] _channelKeys; /// /// %%% /// - public Assistant Definition { get; } + public OpenAIAssistantDefinition Definition { get; private init; } /// /// Set when the assistant has been deleted via . @@ -33,6 +35,11 @@ public sealed class OpenAIAssistantAgent : KernelAgent /// public RunPollingConfiguration Polling { get; } = new(); + /// + /// Expose predefined tools merged with available kernel functions. + /// + internal IReadOnlyList Tools => [.. this._assistant.Tools, .. this.Kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))]; + /// /// Define a new . /// @@ -73,14 +80,18 @@ public static async Task CreateAsync( /// Configuration for accessing the Assistants API service, such as the api-key. /// The to monitor for cancellation requests. The default is . /// An list of objects. - public static IAsyncEnumerable ListDefinitionsAsync( + public static async IAsyncEnumerable ListDefinitionsAsync( OpenAIConfiguration config, - CancellationToken cancellationToken = default) + [EnumeratorCancellation] CancellationToken cancellationToken = default) { // Create the client AssistantClient client = CreateClient(config); - return client.GetAssistantsAsync(ListOrder.NewestFirst, cancellationToken); + // Query and enumerate assistant definitions + await foreach (Assistant model in client.GetAssistantsAsync(ListOrder.NewestFirst, cancellationToken).ConfigureAwait(false)) + { + yield return CreateAssistantDefinition(model); + } } /// @@ -234,24 +245,57 @@ private OpenAIAssistantAgent( Assistant model, IEnumerable channelKeys) { - this.Definition = model; + this._assistant = model; this._client = client; this._channelKeys = channelKeys.ToArray(); - this.Description = model.Description; - this.Id = model.Id; - this.Name = model.Name; - this.Instructions = model.Instructions; + this.Definition = CreateAssistantDefinition(model); + + this.Description = this._assistant.Description; + this.Id = this._assistant.Id; + this.Name = this._assistant.Name; + this.Instructions = this._assistant.Instructions; } + private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant model) + => + new() + { + Id = model.Id, + Name = model.Name, + Description = model.Description, + Instructions = model.Instructions, + EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), + VectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.Single(), + Metadata = model.Metadata, + Model = model.Model, + }; + private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAssistantDefinition definition) { + bool enableFileSearch = !string.IsNullOrWhiteSpace(definition.VectorStoreId); + + ToolResources? toolResources = null; + + if (enableFileSearch) + { + toolResources = + new ToolResources() + { + FileSearch = new FileSearchToolResources() + { + VectorStoreIds = [definition.VectorStoreId!], + } + }; + } + AssistantCreationOptions assistantCreationOptions = new() { Description = definition.Description, Instructions = definition.Instructions, Name = definition.Name, + ToolResources = toolResources, // %%% ResponseFormat = // %%% Temperature = // %%% NucleusSamplingFactor = @@ -270,10 +314,9 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss assistantCreationOptions.Tools.Add(new CodeInterpreterToolDefinition()); } - if (!string.IsNullOrWhiteSpace(definition.VectorStoreId)) + if (enableFileSearch) { assistantCreationOptions.Tools.Add(new FileSearchToolDefinition()); - assistantCreationOptions.ToolResources.FileSearch.VectorStoreIds.Add(definition.VectorStoreId); } return assistantCreationOptions; diff --git a/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs index a0c606beca06..26bdefff975d 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs @@ -20,11 +20,11 @@ internal enum OpenAIConfigurationType /// /// %%% /// - /// /// + /// /// /// - public static OpenAIConfiguration ForAzureOpenAI(Uri endpoint, string apiKey, HttpClient? httpClient = null) => + public static OpenAIConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, HttpClient? httpClient = null) => // %%% VERIFY new() { @@ -37,11 +37,11 @@ public static OpenAIConfiguration ForAzureOpenAI(Uri endpoint, string apiKey, Ht /// /// %%% /// - /// /// + /// /// /// - public static OpenAIConfiguration ForAzureOpenAI(Uri endpoint, TokenCredential credentials, HttpClient? httpClient = null) => + public static OpenAIConfiguration ForAzureOpenAI(TokenCredential credentials, Uri endpoint, HttpClient? httpClient = null) => // %%% VERIFY new() { @@ -54,11 +54,11 @@ public static OpenAIConfiguration ForAzureOpenAI(Uri endpoint, TokenCredential c /// /// %%% /// - /// /// + /// /// /// - public static OpenAIConfiguration ForOpenAI(Uri endpoint, string apiKey, HttpClient? httpClient = null) => + public static OpenAIConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, HttpClient? httpClient = null) => // %%% VERIFY new() { @@ -71,12 +71,12 @@ public static OpenAIConfiguration ForOpenAI(Uri endpoint, string apiKey, HttpCli /// /// %%% /// - /// /// /// + /// /// /// - public static OpenAIConfiguration ForOpenAI(Uri endpoint, string apiKey, string organizationId, HttpClient? httpClient = null) => + public static OpenAIConfiguration ForOpenAI(string apiKey, string organizationId, Uri? endpoint = null, HttpClient? httpClient = null) => // %%% VERIFY new() { diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs index 3c2945ad0fb9..0a4076055fae 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs @@ -2,7 +2,7 @@ using System.Linq; using Azure.Core; using Azure.Core.Pipeline; -using Microsoft.SemanticKernel.Agents.OpenAI.Internal; +using Microsoft.SemanticKernel.Agents.OpenAI; using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI.Azure; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs index 2096012831ed..6d690e909457 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs @@ -25,11 +25,11 @@ public void VerifyKernelFunctionToFunctionTool() KernelFunction f1 = plugin[nameof(TestPlugin.TestFunction1)]; KernelFunction f2 = plugin[nameof(TestPlugin.TestFunction2)]; - FunctionToolDefinition definition1 = f1.ToToolDefinition("testplugin", "-"); + FunctionToolDefinition definition1 = f1.ToToolDefinition("testplugin"); Assert.StartsWith($"testplugin-{nameof(TestPlugin.TestFunction1)}", definition1.FunctionName, StringComparison.Ordinal); Assert.Equal("test description", definition1.Description); - FunctionToolDefinition definition2 = f2.ToToolDefinition("testplugin", "-"); + FunctionToolDefinition definition2 = f2.ToToolDefinition("testplugin"); Assert.StartsWith($"testplugin-{nameof(TestPlugin.TestFunction2)}", definition2.FunctionName, StringComparison.Ordinal); Assert.Equal("test description", definition2.Description); } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index 45d9df826c7b..a196d74dd74c 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -41,7 +41,7 @@ public async Task VerifyOpenAIAssistantAgentCreationEmptyAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( this._emptyKernel, - this.CreateTestConfiguration(targetAzure: true, useVersion: true), + this.CreateTestConfiguration(targetAzure: true), definition); Assert.NotNull(agent); @@ -96,8 +96,7 @@ public async Task VerifyOpenAIAssistantAgentCreationEverythingAsync() { Model = "testmodel", EnableCodeInterpreter = true, - EnableFileSearch = true, - FileIds = ["#1", "#2"], + VectorStoreId = "#vs", Metadata = new Dictionary() { { "a", "1" } }, }; @@ -112,9 +111,10 @@ await OpenAIAssistantAgent.CreateAsync( Assert.NotNull(agent); Assert.Equal(2, agent.Tools.Count); Assert.True(agent.Tools.OfType().Any()); - //Assert.True(agent.Tools.OfType().Any()); %%% - //Assert.NotEmpty(agent.FileIds); %%% - Assert.NotEmpty(agent.Metadata); + Assert.True(agent.Tools.OfType().Any()); + Assert.Equal("#vs", agent.Definition.VectorStoreId); + Assert.NotNull(agent.Definition.Metadata); + Assert.NotEmpty(agent.Definition.Metadata); } /// @@ -381,14 +381,10 @@ private Task CreateAgentAsync() definition); } - private OpenAIAssistantConfiguration CreateTestConfiguration(bool targetAzure = false, bool useVersion = false) - { - return new(apiKey: "fakekey", endpoint: targetAzure ? "https://localhost" : null) - { - HttpClient = this._httpClient, - //Version = useVersion ? AssistantsClientOptions.ServiceVersion.V2024_02_15_Preview : null, %%% - }; - } + private OpenAIConfiguration CreateTestConfiguration(bool targetAzure = false) + => targetAzure ? + OpenAIConfiguration.ForAzureOpenAI(apiKey: "fakekey", endpoint: new Uri("https://localhost"), this._httpClient) : + OpenAIConfiguration.ForOpenAI(apiKey: "fakekey", endpoint: null, this._httpClient); private void SetupResponse(HttpStatusCode statusCode, string content) { @@ -469,10 +465,14 @@ private static class ResponseContent "type": "code_interpreter" }, { - "type": "retrieval" + "type": "file_search" } ], - "file_ids": ["#1", "#2"], + "tool_resources": { + "file_search": { + "vector_store_ids": ["#vs"] + } + }, "metadata": {"a": "1"} } """; @@ -747,7 +747,6 @@ private static class ResponseContent "model": "gpt-4-turbo", "instructions": "You are a helpful assistant designed to make me better at coding!", "tools": [], - "file_ids": [], "metadata": {} }, { @@ -759,7 +758,6 @@ private static class ResponseContent "model": "gpt-4-turbo", "instructions": "You are a helpful assistant designed to make me better at coding!", "tools": [], - "file_ids": [], "metadata": {} }, { @@ -771,7 +769,6 @@ private static class ResponseContent "model": "gpt-4-turbo", "instructions": null, "tools": [], - "file_ids": [], "metadata": {} } ], @@ -795,7 +792,6 @@ private static class ResponseContent "model": "gpt-4-turbo", "instructions": "You are a helpful assistant designed to make me better at coding!", "tools": [], - "file_ids": [], "metadata": {} } ], diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs index d2849f5d19fa..67672c17cd4e 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; using System.Net.Http; -using Azure.AI.OpenAI.Assistants; using Microsoft.SemanticKernel.Agents.OpenAI; using Xunit; @@ -18,12 +17,11 @@ public class OpenAIAssistantConfigurationTests [Fact] public void VerifyOpenAIAssistantConfigurationInitialState() { - OpenAIAssistantConfiguration config = new(apiKey: "testkey"); + OpenAIConfiguration config = OpenAIConfiguration.ForOpenAI(apiKey: "testkey"); Assert.Equal("testkey", config.ApiKey); Assert.Null(config.Endpoint); Assert.Null(config.HttpClient); - //Assert.Null(config.Version); %%% } /// @@ -34,28 +32,11 @@ public void VerifyOpenAIAssistantConfigurationAssignment() { using HttpClient client = new(); - OpenAIAssistantConfiguration config = - new(apiKey: "testkey", endpoint: "https://localhost") - { - HttpClient = client, - //Version = AssistantsClientOptions.ServiceVersion.V2024_02_15_Preview, %%% - }; + OpenAIConfiguration config = OpenAIConfiguration.ForOpenAI(apiKey: "testkey", endpoint: new Uri("https://localhost"), client); Assert.Equal("testkey", config.ApiKey); - Assert.Equal("https://localhost", config.Endpoint); + Assert.NotNull(config.Endpoint); + Assert.Equal("https://localhost/", config.Endpoint.ToString()); Assert.NotNull(config.HttpClient); - //Assert.Equal(AssistantsClientOptions.ServiceVersion.V2024_02_15_Preview, config.Version); %%% - } - - /// - /// Verify secure endpoint. - /// - [Fact] - public void VerifyOpenAIAssistantConfigurationThrows() - { - using HttpClient client = new(); - - Assert.Throws( - () => new OpenAIAssistantConfiguration(apiKey: "testkey", endpoint: "http://localhost")); } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index 48977edc122a..9c19372188ff 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -24,9 +24,8 @@ public void VerifyOpenAIAssistantDefinitionInitialState() Assert.Null(definition.Instructions); Assert.Null(definition.Description); Assert.Null(definition.Metadata); - Assert.Null(definition.FileIds); + Assert.Null(definition.VectorStoreId); Assert.False(definition.EnableCodeInterpreter); - Assert.False(definition.EnableFileSearch); } /// @@ -43,10 +42,9 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Model = "testmodel", Instructions = "testinstructions", Description = "testdescription", - FileIds = ["id"], + VectorStoreId = "#vs", Metadata = new Dictionary() { { "a", "1" } }, EnableCodeInterpreter = true, - EnableFileSearch = true, }; Assert.Equal("testid", definition.Id); @@ -54,9 +52,8 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Assert.Equal("testmodel", definition.Model); Assert.Equal("testinstructions", definition.Instructions); Assert.Equal("testdescription", definition.Description); + Assert.Equal("#vs", definition.VectorStoreId); Assert.Single(definition.Metadata); - Assert.Single(definition.FileIds); Assert.True(definition.EnableCodeInterpreter); - Assert.True(definition.EnableFileSearch); } } From 9b506cb31d50f7124d821b263441cf4d7ad8f61e Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 5 Jul 2024 11:55:41 -0700 Subject: [PATCH 007/121] Integration test update --- .../Agents/OpenAIAssistantAgentTests.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs index 18628dbf66e1..6c4a85104d54 100644 --- a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs @@ -11,6 +11,9 @@ using SemanticKernel.IntegrationTests.TestSettings; using Xunit; +using OpenAIConfiguration = Microsoft.SemanticKernel.Agents.OpenAI.OpenAIConfiguration; +using OpenAISettings = SemanticKernel.IntegrationTests.TestSettings.OpenAIConfiguration; + namespace SemanticKernel.IntegrationTests.Agents.OpenAI; #pragma warning disable xUnit1004 // Contains test methods used in manual verification. Disable warning for this file only. @@ -32,12 +35,12 @@ public sealed class OpenAIAssistantAgentTests [InlineData("What is the special soup?", "Clam Chowder")] public async Task OpenAIAssistantAgentTestAsync(string input, string expectedAnswerContains) { - var openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); - Assert.NotNull(openAIConfiguration); + OpenAISettings openAISettings = this._configuration.GetSection("OpenAI").Get(); + Assert.NotNull(openAISettings); await this.ExecuteAgentAsync( - new(openAIConfiguration.ApiKey), - openAIConfiguration.ModelId, + OpenAIConfiguration.ForOpenAI(openAISettings.ApiKey), + openAISettings.ModelId, input, expectedAnswerContains); } @@ -54,14 +57,14 @@ public async Task AzureOpenAIAssistantAgentAsync(string input, string expectedAn Assert.NotNull(azureOpenAIConfiguration); await this.ExecuteAgentAsync( - new(azureOpenAIConfiguration.ApiKey, azureOpenAIConfiguration.Endpoint), + OpenAIConfiguration.ForAzureOpenAI(azureOpenAIConfiguration.ApiKey, new Uri(azureOpenAIConfiguration.Endpoint)), azureOpenAIConfiguration.ChatDeploymentName!, input, expectedAnswerContains); } private async Task ExecuteAgentAsync( - OpenAIAssistantConfiguration config, + OpenAIConfiguration config, string modelName, string input, string expected) From f240d2712e23f1311b23f203f8ff6a9f11d34b66 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 5 Jul 2024 16:44:51 -0700 Subject: [PATCH 008/121] Check point w/ more samples --- .../Agents/OpenAIAssistant_ChartMaker.cs | 91 +++ .../Agents/OpenAIAssistant_CodeInterpreter.cs | 10 +- .../OpenAIAssistant_FileManipulation.cs | 103 +++ .../Agents/OpenAIAssistant_FileSearch.cs | 94 +++ dotnet/samples/ConceptsV2/ConceptsV2.csproj | 3 - dotnet/samples/ConceptsV2/Resources/sales.csv | 701 ++++++++++++++++++ .../ConceptsV2/Resources/travelinfo.txt | 217 ++++++ .../Step8_OpenAIAssistant.cs | 2 +- dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj | 1 + .../OpenAI/Internal/AssistantThreadActions.cs | 63 +- .../OpenAI/Internal/OpenAIClientFactory.cs | 30 +- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 21 +- .../OpenAI/OpenAIAssistantDefinition.cs | 21 +- .../src/Agents/OpenAI/OpenAIConfiguration.cs | 15 +- .../Agents/OpenAI/OpenAIVectorStoreBuilder.cs | 2 +- .../OpenAI/OpenAIAssistantAgentTests.cs | 8 +- .../OpenAI/OpenAIAssistantDefinitionTests.cs | 6 +- ...onTests.cs => OpenAIConfigurationTests.cs} | 6 +- .../Agents/OpenAIAssistantAgentTests.cs | 2 +- .../Agents/OpenAIAssistantAgentTests.cs | 2 +- 20 files changed, 1332 insertions(+), 66 deletions(-) create mode 100644 dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs create mode 100644 dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs create mode 100644 dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs create mode 100644 dotnet/samples/ConceptsV2/Resources/sales.csv create mode 100644 dotnet/samples/ConceptsV2/Resources/travelinfo.txt rename dotnet/src/Agents/UnitTests/OpenAI/{OpenAIAssistantConfigurationTests.cs => OpenAIConfigurationTests.cs} (91%) diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs new file mode 100644 index 000000000000..70b4fe3d99fe --- /dev/null +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All rights reserved. +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace Agents; + +/// +/// Demonstrate using code-interpreter with to +/// produce image content displays the requested charts. +/// +public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseTest(output) +{ + /// + /// Target Open AI services. + /// + protected override bool ForceOpenAI => true; + + private const string AgentName = "ChartMaker"; + private const string AgentInstructions = "Create charts as requested without explanation."; + + [Fact] + public async Task GenerateChartWithOpenAIAssistantAgentAsync() + { + // Define the agent + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + kernel: new(), + config: GetOpenAIConfiguration(), + new() + { + Instructions = AgentInstructions, + Name = AgentName, + EnableCodeInterpreter = true, + ModelName = this.Model, + }); + + // Create a chat for agent interaction. + var chat = new AgentGroupChat(); + + // Respond to user input + try + { + await InvokeAgentAsync( + """ + Display this data using a bar-chart: + + Banding Brown Pink Yellow Sum + X00000 339 433 126 898 + X00300 48 421 222 691 + X12345 16 395 352 763 + Others 23 373 156 552 + Sum 426 1622 856 2904 + """); + + await InvokeAgentAsync("Can you regenerate this same chart using the category names as the bar colors?"); // %%% WHY NOT ??? + } + finally + { + await agent.DeleteAsync(); + } + + // Local function to invoke agent and display the conversation messages. + async Task InvokeAgentAsync(string input) + { + chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + + Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + + await foreach (var message in chat.InvokeAsync(agent)) + { + if (!string.IsNullOrWhiteSpace(message.Content)) + { + Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); + } + + foreach (var fileReference in message.Items.OfType()) + { + Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: @{fileReference.FileId}"); + } + } + } + } + + private OpenAIConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIConfiguration.ForOpenAI(this.ApiKey) : + OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); +} diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs index 83282d2eee82..5a5314935eca 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs @@ -20,11 +20,11 @@ public async Task UseCodeInterpreterToolWithOpenAIAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)), // %%% MODE + config: GetOpenAIConfiguration(), new() { EnableCodeInterpreter = true, // Enable code-interpreter - Model = this.Model, + ModelName = this.Model, }); // Create a chat for agent interaction. @@ -53,4 +53,10 @@ async Task InvokeAgentAsync(string input) } } } + + private OpenAIConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIConfiguration.ForOpenAI(this.ApiKey) : + OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs new file mode 100644 index 000000000000..1124f385709d --- /dev/null +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Text; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI; +using OpenAI.Files; +using Resources; + +namespace Agents; + +/// +/// Demonstrate using code-interpreter to manipulate and generate csv files with . +/// +public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTest(output) +{ + /// + /// Target OpenAI services. + /// + protected override bool ForceOpenAI => true; + + [Fact] + public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() + { + OpenAIClient rootClient = OpenAIClientFactory.CreateClient(GetOpenAIConfiguration()); + FileClient fileClient = rootClient.GetFileClient(); + + await using Stream fileStream = EmbeddedResource.ReadStream("sales.csv")!; + OpenAIFileInfo fileInfo = + await fileClient.UploadFileAsync( + fileStream, + "sales.csv", + FileUploadPurpose.Assistants); + + //OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); %%% + //OpenAIFileReference uploadFile = + // await fileService.UploadContentAsync( + // new BinaryContent(await EmbeddedResource.ReadAllAsync("sales.csv"), mimeType: "text/plain"), + // new OpenAIFileUploadExecutionSettings("sales.csv", OpenAIFilePurpose.Assistants)); + + // Define the agent + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + kernel: new(), + config: GetOpenAIConfiguration(), + new() + { + EnableCodeInterpreter = true, // Enable code-interpreter + ModelName = this.Model, + }); + + // Create a chat for agent interaction. + AgentGroupChat chat = new(); + + // Respond to user input + try + { + await InvokeAgentAsync("Which segment had the most sales?"); + await InvokeAgentAsync("List the top 5 countries that generated the most profit."); + await InvokeAgentAsync("Create a tab delimited file report of profit by each country per month."); + } + finally + { + await agent.DeleteAsync(); + //await fileService.DeleteFileAsync(uploadFile.Id); %%% + await fileClient.DeleteFileAsync(fileInfo.Id); + } + + // Local function to invoke agent and display the conversation messages. + async Task InvokeAgentAsync(string input) + { + chat.AddChatMessage( + new(AuthorRole.User, content: null) + { + Items = [new TextContent(input), new FileReferenceContent(fileInfo.Id)] + }); + + Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + + await foreach (ChatMessageContent message in chat.InvokeAsync(agent)) + { + Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); + + foreach (AnnotationContent annotation in message.Items.OfType()) + { + Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}"); + BinaryData fileData = await fileClient.DownloadFileAsync(annotation.FileId!); + Console.WriteLine(Encoding.Default.GetString(fileData.ToArray())); + //BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!); %%% + //byte[] byteContent = fileContent.Data?.ToArray() ?? []; + //Console.WriteLine(Encoding.Default.GetString(byteContent)); + } + } + } + } + + private OpenAIConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIConfiguration.ForOpenAI(this.ApiKey) : + OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); +} diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs new file mode 100644 index 000000000000..e73ad5aa9378 --- /dev/null +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft. All rights reserved. +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI.Files; +using OpenAI; +using Resources; +using OpenAI.VectorStores; + +namespace Agents; + +/// +/// Demonstrate using retrieval on . +/// +public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseTest(output) +{ + /// + /// Retrieval tool not supported on Azure OpenAI. + /// + protected override bool ForceOpenAI => true; + + [Fact] + public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync() + { + OpenAIClient rootClient = OpenAIClientFactory.CreateClient(GetOpenAIConfiguration()); + FileClient fileClient = rootClient.GetFileClient(); + + Stream fileStream = EmbeddedResource.ReadStream("travelinfo.txt")!; // %%% USING + OpenAIFileInfo fileInfo = + await fileClient.UploadFileAsync( + fileStream, + "travelinfo.txt", + FileUploadPurpose.Assistants); + + VectorStore vectorStore = + await new OpenAIVectorStoreBuilder(GetOpenAIConfiguration()) + .AddFile(fileInfo.Id) + .CreateAsync(); + + OpenAIVectorStore openAIStore = new(vectorStore.Id, GetOpenAIConfiguration()); + + //OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); %%% + //OpenAIFileReference uploadFile = + // await fileService.UploadContentAsync(new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"), + // new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants)); + + // Define the agent + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + kernel: new(), + config: GetOpenAIConfiguration(), + new() + { + ModelName = this.Model, + VectorStoreId = vectorStore.Id, // %%% + }); + + // Create a chat for agent interaction. + var chat = new AgentGroupChat(); + + // Respond to user input + try + { + await InvokeAgentAsync("Where did sam go?"); + await InvokeAgentAsync("When does the flight leave Seattle?"); + await InvokeAgentAsync("What is the hotel contact info at the destination?"); + } + finally + { + await agent.DeleteAsync(); + await openAIStore.DeleteAsync(); + } + + // Local function to invoke agent and display the conversation messages. + async Task InvokeAgentAsync(string input) + { + chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + + Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + + await foreach (var content in chat.InvokeAsync(agent)) + { + Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + } + } + } + + private OpenAIConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIConfiguration.ForOpenAI(this.ApiKey) : + OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); +} diff --git a/dotnet/samples/ConceptsV2/ConceptsV2.csproj b/dotnet/samples/ConceptsV2/ConceptsV2.csproj index e5185dd02bc1..8eee3868170b 100644 --- a/dotnet/samples/ConceptsV2/ConceptsV2.csproj +++ b/dotnet/samples/ConceptsV2/ConceptsV2.csproj @@ -74,7 +74,4 @@ Always - - - diff --git a/dotnet/samples/ConceptsV2/Resources/sales.csv b/dotnet/samples/ConceptsV2/Resources/sales.csv new file mode 100644 index 000000000000..4a355d11bf83 --- /dev/null +++ b/dotnet/samples/ConceptsV2/Resources/sales.csv @@ -0,0 +1,701 @@ +Segment,Country,Product,Units Sold,Sale Price,Gross Sales,Discounts,Sales,COGS,Profit,Date,Month Number,Month Name,Year +Government,Canada,Carretera,1618.5,20.00,32370.00,0.00,32370.00,16185.00,16185.00,1/1/2014,1,January,2014 +Government,Germany,Carretera,1321,20.00,26420.00,0.00,26420.00,13210.00,13210.00,1/1/2014,1,January,2014 +Midmarket,France,Carretera,2178,15.00,32670.00,0.00,32670.00,21780.00,10890.00,6/1/2014,6,June,2014 +Midmarket,Germany,Carretera,888,15.00,13320.00,0.00,13320.00,8880.00,4440.00,6/1/2014,6,June,2014 +Midmarket,Mexico,Carretera,2470,15.00,37050.00,0.00,37050.00,24700.00,12350.00,6/1/2014,6,June,2014 +Government,Germany,Carretera,1513,350.00,529550.00,0.00,529550.00,393380.00,136170.00,12/1/2014,12,December,2014 +Midmarket,Germany,Montana,921,15.00,13815.00,0.00,13815.00,9210.00,4605.00,3/1/2014,3,March,2014 +Channel Partners,Canada,Montana,2518,12.00,30216.00,0.00,30216.00,7554.00,22662.00,6/1/2014,6,June,2014 +Government,France,Montana,1899,20.00,37980.00,0.00,37980.00,18990.00,18990.00,6/1/2014,6,June,2014 +Channel Partners,Germany,Montana,1545,12.00,18540.00,0.00,18540.00,4635.00,13905.00,6/1/2014,6,June,2014 +Midmarket,Mexico,Montana,2470,15.00,37050.00,0.00,37050.00,24700.00,12350.00,6/1/2014,6,June,2014 +Enterprise,Canada,Montana,2665.5,125.00,333187.50,0.00,333187.50,319860.00,13327.50,7/1/2014,7,July,2014 +Small Business,Mexico,Montana,958,300.00,287400.00,0.00,287400.00,239500.00,47900.00,8/1/2014,8,August,2014 +Government,Germany,Montana,2146,7.00,15022.00,0.00,15022.00,10730.00,4292.00,9/1/2014,9,September,2014 +Enterprise,Canada,Montana,345,125.00,43125.00,0.00,43125.00,41400.00,1725.00,10/1/2013,10,October,2013 +Midmarket,United States of America,Montana,615,15.00,9225.00,0.00,9225.00,6150.00,3075.00,12/1/2014,12,December,2014 +Government,Canada,Paseo,292,20.00,5840.00,0.00,5840.00,2920.00,2920.00,2/1/2014,2,February,2014 +Midmarket,Mexico,Paseo,974,15.00,14610.00,0.00,14610.00,9740.00,4870.00,2/1/2014,2,February,2014 +Channel Partners,Canada,Paseo,2518,12.00,30216.00,0.00,30216.00,7554.00,22662.00,6/1/2014,6,June,2014 +Government,Germany,Paseo,1006,350.00,352100.00,0.00,352100.00,261560.00,90540.00,6/1/2014,6,June,2014 +Channel Partners,Germany,Paseo,367,12.00,4404.00,0.00,4404.00,1101.00,3303.00,7/1/2014,7,July,2014 +Government,Mexico,Paseo,883,7.00,6181.00,0.00,6181.00,4415.00,1766.00,8/1/2014,8,August,2014 +Midmarket,France,Paseo,549,15.00,8235.00,0.00,8235.00,5490.00,2745.00,9/1/2013,9,September,2013 +Small Business,Mexico,Paseo,788,300.00,236400.00,0.00,236400.00,197000.00,39400.00,9/1/2013,9,September,2013 +Midmarket,Mexico,Paseo,2472,15.00,37080.00,0.00,37080.00,24720.00,12360.00,9/1/2014,9,September,2014 +Government,United States of America,Paseo,1143,7.00,8001.00,0.00,8001.00,5715.00,2286.00,10/1/2014,10,October,2014 +Government,Canada,Paseo,1725,350.00,603750.00,0.00,603750.00,448500.00,155250.00,11/1/2013,11,November,2013 +Channel Partners,United States of America,Paseo,912,12.00,10944.00,0.00,10944.00,2736.00,8208.00,11/1/2013,11,November,2013 +Midmarket,Canada,Paseo,2152,15.00,32280.00,0.00,32280.00,21520.00,10760.00,12/1/2013,12,December,2013 +Government,Canada,Paseo,1817,20.00,36340.00,0.00,36340.00,18170.00,18170.00,12/1/2014,12,December,2014 +Government,Germany,Paseo,1513,350.00,529550.00,0.00,529550.00,393380.00,136170.00,12/1/2014,12,December,2014 +Government,Mexico,Velo,1493,7.00,10451.00,0.00,10451.00,7465.00,2986.00,1/1/2014,1,January,2014 +Enterprise,France,Velo,1804,125.00,225500.00,0.00,225500.00,216480.00,9020.00,2/1/2014,2,February,2014 +Channel Partners,Germany,Velo,2161,12.00,25932.00,0.00,25932.00,6483.00,19449.00,3/1/2014,3,March,2014 +Government,Germany,Velo,1006,350.00,352100.00,0.00,352100.00,261560.00,90540.00,6/1/2014,6,June,2014 +Channel Partners,Germany,Velo,1545,12.00,18540.00,0.00,18540.00,4635.00,13905.00,6/1/2014,6,June,2014 +Enterprise,United States of America,Velo,2821,125.00,352625.00,0.00,352625.00,338520.00,14105.00,8/1/2014,8,August,2014 +Enterprise,Canada,Velo,345,125.00,43125.00,0.00,43125.00,41400.00,1725.00,10/1/2013,10,October,2013 +Small Business,Canada,VTT,2001,300.00,600300.00,0.00,600300.00,500250.00,100050.00,2/1/2014,2,February,2014 +Channel Partners,Germany,VTT,2838,12.00,34056.00,0.00,34056.00,8514.00,25542.00,4/1/2014,4,April,2014 +Midmarket,France,VTT,2178,15.00,32670.00,0.00,32670.00,21780.00,10890.00,6/1/2014,6,June,2014 +Midmarket,Germany,VTT,888,15.00,13320.00,0.00,13320.00,8880.00,4440.00,6/1/2014,6,June,2014 +Government,France,VTT,1527,350.00,534450.00,0.00,534450.00,397020.00,137430.00,9/1/2013,9,September,2013 +Small Business,France,VTT,2151,300.00,645300.00,0.00,645300.00,537750.00,107550.00,9/1/2014,9,September,2014 +Government,Canada,VTT,1817,20.00,36340.00,0.00,36340.00,18170.00,18170.00,12/1/2014,12,December,2014 +Government,France,Amarilla,2750,350.00,962500.00,0.00,962500.00,715000.00,247500.00,2/1/2014,2,February,2014 +Channel Partners,United States of America,Amarilla,1953,12.00,23436.00,0.00,23436.00,5859.00,17577.00,4/1/2014,4,April,2014 +Enterprise,Germany,Amarilla,4219.5,125.00,527437.50,0.00,527437.50,506340.00,21097.50,4/1/2014,4,April,2014 +Government,France,Amarilla,1899,20.00,37980.00,0.00,37980.00,18990.00,18990.00,6/1/2014,6,June,2014 +Government,Germany,Amarilla,1686,7.00,11802.00,0.00,11802.00,8430.00,3372.00,7/1/2014,7,July,2014 +Channel Partners,United States of America,Amarilla,2141,12.00,25692.00,0.00,25692.00,6423.00,19269.00,8/1/2014,8,August,2014 +Government,United States of America,Amarilla,1143,7.00,8001.00,0.00,8001.00,5715.00,2286.00,10/1/2014,10,October,2014 +Midmarket,United States of America,Amarilla,615,15.00,9225.00,0.00,9225.00,6150.00,3075.00,12/1/2014,12,December,2014 +Government,France,Paseo,3945,7.00,27615.00,276.15,27338.85,19725.00,7613.85,1/1/2014,1,January,2014 +Midmarket,France,Paseo,2296,15.00,34440.00,344.40,34095.60,22960.00,11135.60,2/1/2014,2,February,2014 +Government,France,Paseo,1030,7.00,7210.00,72.10,7137.90,5150.00,1987.90,5/1/2014,5,May,2014 +Government,France,Velo,639,7.00,4473.00,44.73,4428.27,3195.00,1233.27,11/1/2014,11,November,2014 +Government,Canada,VTT,1326,7.00,9282.00,92.82,9189.18,6630.00,2559.18,3/1/2014,3,March,2014 +Channel Partners,United States of America,Carretera,1858,12.00,22296.00,222.96,22073.04,5574.00,16499.04,2/1/2014,2,February,2014 +Government,Mexico,Carretera,1210,350.00,423500.00,4235.00,419265.00,314600.00,104665.00,3/1/2014,3,March,2014 +Government,United States of America,Carretera,2529,7.00,17703.00,177.03,17525.97,12645.00,4880.97,7/1/2014,7,July,2014 +Channel Partners,Canada,Carretera,1445,12.00,17340.00,173.40,17166.60,4335.00,12831.60,9/1/2014,9,September,2014 +Enterprise,United States of America,Carretera,330,125.00,41250.00,412.50,40837.50,39600.00,1237.50,9/1/2013,9,September,2013 +Channel Partners,France,Carretera,2671,12.00,32052.00,320.52,31731.48,8013.00,23718.48,9/1/2014,9,September,2014 +Channel Partners,Germany,Carretera,766,12.00,9192.00,91.92,9100.08,2298.00,6802.08,10/1/2013,10,October,2013 +Small Business,Mexico,Carretera,494,300.00,148200.00,1482.00,146718.00,123500.00,23218.00,10/1/2013,10,October,2013 +Government,Mexico,Carretera,1397,350.00,488950.00,4889.50,484060.50,363220.00,120840.50,10/1/2014,10,October,2014 +Government,France,Carretera,2155,350.00,754250.00,7542.50,746707.50,560300.00,186407.50,12/1/2014,12,December,2014 +Midmarket,Mexico,Montana,2214,15.00,33210.00,332.10,32877.90,22140.00,10737.90,3/1/2014,3,March,2014 +Small Business,United States of America,Montana,2301,300.00,690300.00,6903.00,683397.00,575250.00,108147.00,4/1/2014,4,April,2014 +Government,France,Montana,1375.5,20.00,27510.00,275.10,27234.90,13755.00,13479.90,7/1/2014,7,July,2014 +Government,Canada,Montana,1830,7.00,12810.00,128.10,12681.90,9150.00,3531.90,8/1/2014,8,August,2014 +Small Business,United States of America,Montana,2498,300.00,749400.00,7494.00,741906.00,624500.00,117406.00,9/1/2013,9,September,2013 +Enterprise,United States of America,Montana,663,125.00,82875.00,828.75,82046.25,79560.00,2486.25,10/1/2013,10,October,2013 +Midmarket,United States of America,Paseo,1514,15.00,22710.00,227.10,22482.90,15140.00,7342.90,2/1/2014,2,February,2014 +Government,United States of America,Paseo,4492.5,7.00,31447.50,314.48,31133.03,22462.50,8670.53,4/1/2014,4,April,2014 +Enterprise,United States of America,Paseo,727,125.00,90875.00,908.75,89966.25,87240.00,2726.25,6/1/2014,6,June,2014 +Enterprise,France,Paseo,787,125.00,98375.00,983.75,97391.25,94440.00,2951.25,6/1/2014,6,June,2014 +Enterprise,Mexico,Paseo,1823,125.00,227875.00,2278.75,225596.25,218760.00,6836.25,7/1/2014,7,July,2014 +Midmarket,Germany,Paseo,747,15.00,11205.00,112.05,11092.95,7470.00,3622.95,9/1/2014,9,September,2014 +Channel Partners,Germany,Paseo,766,12.00,9192.00,91.92,9100.08,2298.00,6802.08,10/1/2013,10,October,2013 +Small Business,United States of America,Paseo,2905,300.00,871500.00,8715.00,862785.00,726250.00,136535.00,11/1/2014,11,November,2014 +Government,France,Paseo,2155,350.00,754250.00,7542.50,746707.50,560300.00,186407.50,12/1/2014,12,December,2014 +Government,France,Velo,3864,20.00,77280.00,772.80,76507.20,38640.00,37867.20,4/1/2014,4,April,2014 +Government,Mexico,Velo,362,7.00,2534.00,25.34,2508.66,1810.00,698.66,5/1/2014,5,May,2014 +Enterprise,Canada,Velo,923,125.00,115375.00,1153.75,114221.25,110760.00,3461.25,8/1/2014,8,August,2014 +Enterprise,United States of America,Velo,663,125.00,82875.00,828.75,82046.25,79560.00,2486.25,10/1/2013,10,October,2013 +Government,Canada,Velo,2092,7.00,14644.00,146.44,14497.56,10460.00,4037.56,11/1/2013,11,November,2013 +Government,Germany,VTT,263,7.00,1841.00,18.41,1822.59,1315.00,507.59,3/1/2014,3,March,2014 +Government,Canada,VTT,943.5,350.00,330225.00,3302.25,326922.75,245310.00,81612.75,4/1/2014,4,April,2014 +Enterprise,United States of America,VTT,727,125.00,90875.00,908.75,89966.25,87240.00,2726.25,6/1/2014,6,June,2014 +Enterprise,France,VTT,787,125.00,98375.00,983.75,97391.25,94440.00,2951.25,6/1/2014,6,June,2014 +Small Business,Germany,VTT,986,300.00,295800.00,2958.00,292842.00,246500.00,46342.00,9/1/2014,9,September,2014 +Small Business,Mexico,VTT,494,300.00,148200.00,1482.00,146718.00,123500.00,23218.00,10/1/2013,10,October,2013 +Government,Mexico,VTT,1397,350.00,488950.00,4889.50,484060.50,363220.00,120840.50,10/1/2014,10,October,2014 +Enterprise,France,VTT,1744,125.00,218000.00,2180.00,215820.00,209280.00,6540.00,11/1/2014,11,November,2014 +Channel Partners,United States of America,Amarilla,1989,12.00,23868.00,238.68,23629.32,5967.00,17662.32,9/1/2013,9,September,2013 +Midmarket,France,Amarilla,321,15.00,4815.00,48.15,4766.85,3210.00,1556.85,11/1/2013,11,November,2013 +Enterprise,Canada,Carretera,742.5,125.00,92812.50,1856.25,90956.25,89100.00,1856.25,4/1/2014,4,April,2014 +Channel Partners,Canada,Carretera,1295,12.00,15540.00,310.80,15229.20,3885.00,11344.20,10/1/2014,10,October,2014 +Small Business,Germany,Carretera,214,300.00,64200.00,1284.00,62916.00,53500.00,9416.00,10/1/2013,10,October,2013 +Government,France,Carretera,2145,7.00,15015.00,300.30,14714.70,10725.00,3989.70,11/1/2013,11,November,2013 +Government,Canada,Carretera,2852,350.00,998200.00,19964.00,978236.00,741520.00,236716.00,12/1/2014,12,December,2014 +Channel Partners,United States of America,Montana,1142,12.00,13704.00,274.08,13429.92,3426.00,10003.92,6/1/2014,6,June,2014 +Government,United States of America,Montana,1566,20.00,31320.00,626.40,30693.60,15660.00,15033.60,10/1/2014,10,October,2014 +Channel Partners,Mexico,Montana,690,12.00,8280.00,165.60,8114.40,2070.00,6044.40,11/1/2014,11,November,2014 +Enterprise,Mexico,Montana,1660,125.00,207500.00,4150.00,203350.00,199200.00,4150.00,11/1/2013,11,November,2013 +Midmarket,Canada,Paseo,2363,15.00,35445.00,708.90,34736.10,23630.00,11106.10,2/1/2014,2,February,2014 +Small Business,France,Paseo,918,300.00,275400.00,5508.00,269892.00,229500.00,40392.00,5/1/2014,5,May,2014 +Small Business,Germany,Paseo,1728,300.00,518400.00,10368.00,508032.00,432000.00,76032.00,5/1/2014,5,May,2014 +Channel Partners,United States of America,Paseo,1142,12.00,13704.00,274.08,13429.92,3426.00,10003.92,6/1/2014,6,June,2014 +Enterprise,Mexico,Paseo,662,125.00,82750.00,1655.00,81095.00,79440.00,1655.00,6/1/2014,6,June,2014 +Channel Partners,Canada,Paseo,1295,12.00,15540.00,310.80,15229.20,3885.00,11344.20,10/1/2014,10,October,2014 +Enterprise,Germany,Paseo,809,125.00,101125.00,2022.50,99102.50,97080.00,2022.50,10/1/2013,10,October,2013 +Enterprise,Mexico,Paseo,2145,125.00,268125.00,5362.50,262762.50,257400.00,5362.50,10/1/2013,10,October,2013 +Channel Partners,France,Paseo,1785,12.00,21420.00,428.40,20991.60,5355.00,15636.60,11/1/2013,11,November,2013 +Small Business,Canada,Paseo,1916,300.00,574800.00,11496.00,563304.00,479000.00,84304.00,12/1/2014,12,December,2014 +Government,Canada,Paseo,2852,350.00,998200.00,19964.00,978236.00,741520.00,236716.00,12/1/2014,12,December,2014 +Enterprise,Canada,Paseo,2729,125.00,341125.00,6822.50,334302.50,327480.00,6822.50,12/1/2014,12,December,2014 +Midmarket,United States of America,Paseo,1925,15.00,28875.00,577.50,28297.50,19250.00,9047.50,12/1/2013,12,December,2013 +Government,United States of America,Paseo,2013,7.00,14091.00,281.82,13809.18,10065.00,3744.18,12/1/2013,12,December,2013 +Channel Partners,France,Paseo,1055,12.00,12660.00,253.20,12406.80,3165.00,9241.80,12/1/2014,12,December,2014 +Channel Partners,Mexico,Paseo,1084,12.00,13008.00,260.16,12747.84,3252.00,9495.84,12/1/2014,12,December,2014 +Government,United States of America,Velo,1566,20.00,31320.00,626.40,30693.60,15660.00,15033.60,10/1/2014,10,October,2014 +Government,Germany,Velo,2966,350.00,1038100.00,20762.00,1017338.00,771160.00,246178.00,10/1/2013,10,October,2013 +Government,Germany,Velo,2877,350.00,1006950.00,20139.00,986811.00,748020.00,238791.00,10/1/2014,10,October,2014 +Enterprise,Germany,Velo,809,125.00,101125.00,2022.50,99102.50,97080.00,2022.50,10/1/2013,10,October,2013 +Enterprise,Mexico,Velo,2145,125.00,268125.00,5362.50,262762.50,257400.00,5362.50,10/1/2013,10,October,2013 +Channel Partners,France,Velo,1055,12.00,12660.00,253.20,12406.80,3165.00,9241.80,12/1/2014,12,December,2014 +Government,Mexico,Velo,544,20.00,10880.00,217.60,10662.40,5440.00,5222.40,12/1/2013,12,December,2013 +Channel Partners,Mexico,Velo,1084,12.00,13008.00,260.16,12747.84,3252.00,9495.84,12/1/2014,12,December,2014 +Enterprise,Mexico,VTT,662,125.00,82750.00,1655.00,81095.00,79440.00,1655.00,6/1/2014,6,June,2014 +Small Business,Germany,VTT,214,300.00,64200.00,1284.00,62916.00,53500.00,9416.00,10/1/2013,10,October,2013 +Government,Germany,VTT,2877,350.00,1006950.00,20139.00,986811.00,748020.00,238791.00,10/1/2014,10,October,2014 +Enterprise,Canada,VTT,2729,125.00,341125.00,6822.50,334302.50,327480.00,6822.50,12/1/2014,12,December,2014 +Government,United States of America,VTT,266,350.00,93100.00,1862.00,91238.00,69160.00,22078.00,12/1/2013,12,December,2013 +Government,Mexico,VTT,1940,350.00,679000.00,13580.00,665420.00,504400.00,161020.00,12/1/2013,12,December,2013 +Small Business,Germany,Amarilla,259,300.00,77700.00,1554.00,76146.00,64750.00,11396.00,3/1/2014,3,March,2014 +Small Business,Mexico,Amarilla,1101,300.00,330300.00,6606.00,323694.00,275250.00,48444.00,3/1/2014,3,March,2014 +Enterprise,Germany,Amarilla,2276,125.00,284500.00,5690.00,278810.00,273120.00,5690.00,5/1/2014,5,May,2014 +Government,Germany,Amarilla,2966,350.00,1038100.00,20762.00,1017338.00,771160.00,246178.00,10/1/2013,10,October,2013 +Government,United States of America,Amarilla,1236,20.00,24720.00,494.40,24225.60,12360.00,11865.60,11/1/2014,11,November,2014 +Government,France,Amarilla,941,20.00,18820.00,376.40,18443.60,9410.00,9033.60,11/1/2014,11,November,2014 +Small Business,Canada,Amarilla,1916,300.00,574800.00,11496.00,563304.00,479000.00,84304.00,12/1/2014,12,December,2014 +Enterprise,France,Carretera,4243.5,125.00,530437.50,15913.13,514524.38,509220.00,5304.38,4/1/2014,4,April,2014 +Government,Germany,Carretera,2580,20.00,51600.00,1548.00,50052.00,25800.00,24252.00,4/1/2014,4,April,2014 +Small Business,Germany,Carretera,689,300.00,206700.00,6201.00,200499.00,172250.00,28249.00,6/1/2014,6,June,2014 +Channel Partners,United States of America,Carretera,1947,12.00,23364.00,700.92,22663.08,5841.00,16822.08,9/1/2014,9,September,2014 +Channel Partners,Canada,Carretera,908,12.00,10896.00,326.88,10569.12,2724.00,7845.12,12/1/2013,12,December,2013 +Government,Germany,Montana,1958,7.00,13706.00,411.18,13294.82,9790.00,3504.82,2/1/2014,2,February,2014 +Channel Partners,France,Montana,1901,12.00,22812.00,684.36,22127.64,5703.00,16424.64,6/1/2014,6,June,2014 +Government,France,Montana,544,7.00,3808.00,114.24,3693.76,2720.00,973.76,9/1/2014,9,September,2014 +Government,Germany,Montana,1797,350.00,628950.00,18868.50,610081.50,467220.00,142861.50,9/1/2013,9,September,2013 +Enterprise,France,Montana,1287,125.00,160875.00,4826.25,156048.75,154440.00,1608.75,12/1/2014,12,December,2014 +Enterprise,Germany,Montana,1706,125.00,213250.00,6397.50,206852.50,204720.00,2132.50,12/1/2014,12,December,2014 +Small Business,France,Paseo,2434.5,300.00,730350.00,21910.50,708439.50,608625.00,99814.50,1/1/2014,1,January,2014 +Enterprise,Canada,Paseo,1774,125.00,221750.00,6652.50,215097.50,212880.00,2217.50,3/1/2014,3,March,2014 +Channel Partners,France,Paseo,1901,12.00,22812.00,684.36,22127.64,5703.00,16424.64,6/1/2014,6,June,2014 +Small Business,Germany,Paseo,689,300.00,206700.00,6201.00,200499.00,172250.00,28249.00,6/1/2014,6,June,2014 +Enterprise,Germany,Paseo,1570,125.00,196250.00,5887.50,190362.50,188400.00,1962.50,6/1/2014,6,June,2014 +Channel Partners,United States of America,Paseo,1369.5,12.00,16434.00,493.02,15940.98,4108.50,11832.48,7/1/2014,7,July,2014 +Enterprise,Canada,Paseo,2009,125.00,251125.00,7533.75,243591.25,241080.00,2511.25,10/1/2014,10,October,2014 +Midmarket,Germany,Paseo,1945,15.00,29175.00,875.25,28299.75,19450.00,8849.75,10/1/2013,10,October,2013 +Enterprise,France,Paseo,1287,125.00,160875.00,4826.25,156048.75,154440.00,1608.75,12/1/2014,12,December,2014 +Enterprise,Germany,Paseo,1706,125.00,213250.00,6397.50,206852.50,204720.00,2132.50,12/1/2014,12,December,2014 +Enterprise,Canada,Velo,2009,125.00,251125.00,7533.75,243591.25,241080.00,2511.25,10/1/2014,10,October,2014 +Small Business,United States of America,VTT,2844,300.00,853200.00,25596.00,827604.00,711000.00,116604.00,2/1/2014,2,February,2014 +Channel Partners,Mexico,VTT,1916,12.00,22992.00,689.76,22302.24,5748.00,16554.24,4/1/2014,4,April,2014 +Enterprise,Germany,VTT,1570,125.00,196250.00,5887.50,190362.50,188400.00,1962.50,6/1/2014,6,June,2014 +Small Business,Canada,VTT,1874,300.00,562200.00,16866.00,545334.00,468500.00,76834.00,8/1/2014,8,August,2014 +Government,Mexico,VTT,1642,350.00,574700.00,17241.00,557459.00,426920.00,130539.00,8/1/2014,8,August,2014 +Midmarket,Germany,VTT,1945,15.00,29175.00,875.25,28299.75,19450.00,8849.75,10/1/2013,10,October,2013 +Government,Canada,Carretera,831,20.00,16620.00,498.60,16121.40,8310.00,7811.40,5/1/2014,5,May,2014 +Government,Mexico,Paseo,1760,7.00,12320.00,369.60,11950.40,8800.00,3150.40,9/1/2013,9,September,2013 +Government,Canada,Velo,3850.5,20.00,77010.00,2310.30,74699.70,38505.00,36194.70,4/1/2014,4,April,2014 +Channel Partners,Germany,VTT,2479,12.00,29748.00,892.44,28855.56,7437.00,21418.56,1/1/2014,1,January,2014 +Midmarket,Mexico,Montana,2031,15.00,30465.00,1218.60,29246.40,20310.00,8936.40,10/1/2014,10,October,2014 +Midmarket,Mexico,Paseo,2031,15.00,30465.00,1218.60,29246.40,20310.00,8936.40,10/1/2014,10,October,2014 +Midmarket,France,Paseo,2261,15.00,33915.00,1356.60,32558.40,22610.00,9948.40,12/1/2013,12,December,2013 +Government,United States of America,Velo,736,20.00,14720.00,588.80,14131.20,7360.00,6771.20,9/1/2013,9,September,2013 +Government,Canada,Carretera,2851,7.00,19957.00,798.28,19158.72,14255.00,4903.72,10/1/2013,10,October,2013 +Small Business,Germany,Carretera,2021,300.00,606300.00,24252.00,582048.00,505250.00,76798.00,10/1/2014,10,October,2014 +Government,United States of America,Carretera,274,350.00,95900.00,3836.00,92064.00,71240.00,20824.00,12/1/2014,12,December,2014 +Midmarket,Canada,Montana,1967,15.00,29505.00,1180.20,28324.80,19670.00,8654.80,3/1/2014,3,March,2014 +Small Business,Germany,Montana,1859,300.00,557700.00,22308.00,535392.00,464750.00,70642.00,8/1/2014,8,August,2014 +Government,Canada,Montana,2851,7.00,19957.00,798.28,19158.72,14255.00,4903.72,10/1/2013,10,October,2013 +Small Business,Germany,Montana,2021,300.00,606300.00,24252.00,582048.00,505250.00,76798.00,10/1/2014,10,October,2014 +Enterprise,Mexico,Montana,1138,125.00,142250.00,5690.00,136560.00,136560.00,0.00,12/1/2014,12,December,2014 +Government,Canada,Paseo,4251,7.00,29757.00,1190.28,28566.72,21255.00,7311.72,1/1/2014,1,January,2014 +Enterprise,Germany,Paseo,795,125.00,99375.00,3975.00,95400.00,95400.00,0.00,3/1/2014,3,March,2014 +Small Business,Germany,Paseo,1414.5,300.00,424350.00,16974.00,407376.00,353625.00,53751.00,4/1/2014,4,April,2014 +Small Business,United States of America,Paseo,2918,300.00,875400.00,35016.00,840384.00,729500.00,110884.00,5/1/2014,5,May,2014 +Government,United States of America,Paseo,3450,350.00,1207500.00,48300.00,1159200.00,897000.00,262200.00,7/1/2014,7,July,2014 +Enterprise,France,Paseo,2988,125.00,373500.00,14940.00,358560.00,358560.00,0.00,7/1/2014,7,July,2014 +Midmarket,Canada,Paseo,218,15.00,3270.00,130.80,3139.20,2180.00,959.20,9/1/2014,9,September,2014 +Government,Canada,Paseo,2074,20.00,41480.00,1659.20,39820.80,20740.00,19080.80,9/1/2014,9,September,2014 +Government,United States of America,Paseo,1056,20.00,21120.00,844.80,20275.20,10560.00,9715.20,9/1/2014,9,September,2014 +Midmarket,United States of America,Paseo,671,15.00,10065.00,402.60,9662.40,6710.00,2952.40,10/1/2013,10,October,2013 +Midmarket,Mexico,Paseo,1514,15.00,22710.00,908.40,21801.60,15140.00,6661.60,10/1/2013,10,October,2013 +Government,United States of America,Paseo,274,350.00,95900.00,3836.00,92064.00,71240.00,20824.00,12/1/2014,12,December,2014 +Enterprise,Mexico,Paseo,1138,125.00,142250.00,5690.00,136560.00,136560.00,0.00,12/1/2014,12,December,2014 +Channel Partners,United States of America,Velo,1465,12.00,17580.00,703.20,16876.80,4395.00,12481.80,3/1/2014,3,March,2014 +Government,Canada,Velo,2646,20.00,52920.00,2116.80,50803.20,26460.00,24343.20,9/1/2013,9,September,2013 +Government,France,Velo,2177,350.00,761950.00,30478.00,731472.00,566020.00,165452.00,10/1/2014,10,October,2014 +Channel Partners,France,VTT,866,12.00,10392.00,415.68,9976.32,2598.00,7378.32,5/1/2014,5,May,2014 +Government,United States of America,VTT,349,350.00,122150.00,4886.00,117264.00,90740.00,26524.00,9/1/2013,9,September,2013 +Government,France,VTT,2177,350.00,761950.00,30478.00,731472.00,566020.00,165452.00,10/1/2014,10,October,2014 +Midmarket,Mexico,VTT,1514,15.00,22710.00,908.40,21801.60,15140.00,6661.60,10/1/2013,10,October,2013 +Government,Mexico,Amarilla,1865,350.00,652750.00,26110.00,626640.00,484900.00,141740.00,2/1/2014,2,February,2014 +Enterprise,Mexico,Amarilla,1074,125.00,134250.00,5370.00,128880.00,128880.00,0.00,4/1/2014,4,April,2014 +Government,Germany,Amarilla,1907,350.00,667450.00,26698.00,640752.00,495820.00,144932.00,9/1/2014,9,September,2014 +Midmarket,United States of America,Amarilla,671,15.00,10065.00,402.60,9662.40,6710.00,2952.40,10/1/2013,10,October,2013 +Government,Canada,Amarilla,1778,350.00,622300.00,24892.00,597408.00,462280.00,135128.00,12/1/2013,12,December,2013 +Government,Germany,Montana,1159,7.00,8113.00,405.65,7707.35,5795.00,1912.35,10/1/2013,10,October,2013 +Government,Germany,Paseo,1372,7.00,9604.00,480.20,9123.80,6860.00,2263.80,1/1/2014,1,January,2014 +Government,Canada,Paseo,2349,7.00,16443.00,822.15,15620.85,11745.00,3875.85,9/1/2013,9,September,2013 +Government,Mexico,Paseo,2689,7.00,18823.00,941.15,17881.85,13445.00,4436.85,10/1/2014,10,October,2014 +Channel Partners,Canada,Paseo,2431,12.00,29172.00,1458.60,27713.40,7293.00,20420.40,12/1/2014,12,December,2014 +Channel Partners,Canada,Velo,2431,12.00,29172.00,1458.60,27713.40,7293.00,20420.40,12/1/2014,12,December,2014 +Government,Mexico,VTT,2689,7.00,18823.00,941.15,17881.85,13445.00,4436.85,10/1/2014,10,October,2014 +Government,Mexico,Amarilla,1683,7.00,11781.00,589.05,11191.95,8415.00,2776.95,7/1/2014,7,July,2014 +Channel Partners,Mexico,Amarilla,1123,12.00,13476.00,673.80,12802.20,3369.00,9433.20,8/1/2014,8,August,2014 +Government,Germany,Amarilla,1159,7.00,8113.00,405.65,7707.35,5795.00,1912.35,10/1/2013,10,October,2013 +Channel Partners,France,Carretera,1865,12.00,22380.00,1119.00,21261.00,5595.00,15666.00,2/1/2014,2,February,2014 +Channel Partners,Germany,Carretera,1116,12.00,13392.00,669.60,12722.40,3348.00,9374.40,2/1/2014,2,February,2014 +Government,France,Carretera,1563,20.00,31260.00,1563.00,29697.00,15630.00,14067.00,5/1/2014,5,May,2014 +Small Business,United States of America,Carretera,991,300.00,297300.00,14865.00,282435.00,247750.00,34685.00,6/1/2014,6,June,2014 +Government,Germany,Carretera,1016,7.00,7112.00,355.60,6756.40,5080.00,1676.40,11/1/2013,11,November,2013 +Midmarket,Mexico,Carretera,2791,15.00,41865.00,2093.25,39771.75,27910.00,11861.75,11/1/2014,11,November,2014 +Government,United States of America,Carretera,570,7.00,3990.00,199.50,3790.50,2850.00,940.50,12/1/2014,12,December,2014 +Government,France,Carretera,2487,7.00,17409.00,870.45,16538.55,12435.00,4103.55,12/1/2014,12,December,2014 +Government,France,Montana,1384.5,350.00,484575.00,24228.75,460346.25,359970.00,100376.25,1/1/2014,1,January,2014 +Enterprise,United States of America,Montana,3627,125.00,453375.00,22668.75,430706.25,435240.00,-4533.75,7/1/2014,7,July,2014 +Government,Mexico,Montana,720,350.00,252000.00,12600.00,239400.00,187200.00,52200.00,9/1/2013,9,September,2013 +Channel Partners,Germany,Montana,2342,12.00,28104.00,1405.20,26698.80,7026.00,19672.80,11/1/2014,11,November,2014 +Small Business,Mexico,Montana,1100,300.00,330000.00,16500.00,313500.00,275000.00,38500.00,12/1/2013,12,December,2013 +Government,France,Paseo,1303,20.00,26060.00,1303.00,24757.00,13030.00,11727.00,2/1/2014,2,February,2014 +Enterprise,United States of America,Paseo,2992,125.00,374000.00,18700.00,355300.00,359040.00,-3740.00,3/1/2014,3,March,2014 +Enterprise,France,Paseo,2385,125.00,298125.00,14906.25,283218.75,286200.00,-2981.25,3/1/2014,3,March,2014 +Small Business,Mexico,Paseo,1607,300.00,482100.00,24105.00,457995.00,401750.00,56245.00,4/1/2014,4,April,2014 +Government,United States of America,Paseo,2327,7.00,16289.00,814.45,15474.55,11635.00,3839.55,5/1/2014,5,May,2014 +Small Business,United States of America,Paseo,991,300.00,297300.00,14865.00,282435.00,247750.00,34685.00,6/1/2014,6,June,2014 +Government,United States of America,Paseo,602,350.00,210700.00,10535.00,200165.00,156520.00,43645.00,6/1/2014,6,June,2014 +Midmarket,France,Paseo,2620,15.00,39300.00,1965.00,37335.00,26200.00,11135.00,9/1/2014,9,September,2014 +Government,Canada,Paseo,1228,350.00,429800.00,21490.00,408310.00,319280.00,89030.00,10/1/2013,10,October,2013 +Government,Canada,Paseo,1389,20.00,27780.00,1389.00,26391.00,13890.00,12501.00,10/1/2013,10,October,2013 +Enterprise,United States of America,Paseo,861,125.00,107625.00,5381.25,102243.75,103320.00,-1076.25,10/1/2014,10,October,2014 +Enterprise,France,Paseo,704,125.00,88000.00,4400.00,83600.00,84480.00,-880.00,10/1/2013,10,October,2013 +Government,Canada,Paseo,1802,20.00,36040.00,1802.00,34238.00,18020.00,16218.00,12/1/2013,12,December,2013 +Government,United States of America,Paseo,2663,20.00,53260.00,2663.00,50597.00,26630.00,23967.00,12/1/2014,12,December,2014 +Government,France,Paseo,2136,7.00,14952.00,747.60,14204.40,10680.00,3524.40,12/1/2013,12,December,2013 +Midmarket,Germany,Paseo,2116,15.00,31740.00,1587.00,30153.00,21160.00,8993.00,12/1/2013,12,December,2013 +Midmarket,United States of America,Velo,555,15.00,8325.00,416.25,7908.75,5550.00,2358.75,1/1/2014,1,January,2014 +Midmarket,Mexico,Velo,2861,15.00,42915.00,2145.75,40769.25,28610.00,12159.25,1/1/2014,1,January,2014 +Enterprise,Germany,Velo,807,125.00,100875.00,5043.75,95831.25,96840.00,-1008.75,2/1/2014,2,February,2014 +Government,United States of America,Velo,602,350.00,210700.00,10535.00,200165.00,156520.00,43645.00,6/1/2014,6,June,2014 +Government,United States of America,Velo,2832,20.00,56640.00,2832.00,53808.00,28320.00,25488.00,8/1/2014,8,August,2014 +Government,France,Velo,1579,20.00,31580.00,1579.00,30001.00,15790.00,14211.00,8/1/2014,8,August,2014 +Enterprise,United States of America,Velo,861,125.00,107625.00,5381.25,102243.75,103320.00,-1076.25,10/1/2014,10,October,2014 +Enterprise,France,Velo,704,125.00,88000.00,4400.00,83600.00,84480.00,-880.00,10/1/2013,10,October,2013 +Government,France,Velo,1033,20.00,20660.00,1033.00,19627.00,10330.00,9297.00,12/1/2013,12,December,2013 +Small Business,Germany,Velo,1250,300.00,375000.00,18750.00,356250.00,312500.00,43750.00,12/1/2014,12,December,2014 +Government,Canada,VTT,1389,20.00,27780.00,1389.00,26391.00,13890.00,12501.00,10/1/2013,10,October,2013 +Government,United States of America,VTT,1265,20.00,25300.00,1265.00,24035.00,12650.00,11385.00,11/1/2013,11,November,2013 +Government,Germany,VTT,2297,20.00,45940.00,2297.00,43643.00,22970.00,20673.00,11/1/2013,11,November,2013 +Government,United States of America,VTT,2663,20.00,53260.00,2663.00,50597.00,26630.00,23967.00,12/1/2014,12,December,2014 +Government,United States of America,VTT,570,7.00,3990.00,199.50,3790.50,2850.00,940.50,12/1/2014,12,December,2014 +Government,France,VTT,2487,7.00,17409.00,870.45,16538.55,12435.00,4103.55,12/1/2014,12,December,2014 +Government,Germany,Amarilla,1350,350.00,472500.00,23625.00,448875.00,351000.00,97875.00,2/1/2014,2,February,2014 +Government,Canada,Amarilla,552,350.00,193200.00,9660.00,183540.00,143520.00,40020.00,8/1/2014,8,August,2014 +Government,Canada,Amarilla,1228,350.00,429800.00,21490.00,408310.00,319280.00,89030.00,10/1/2013,10,October,2013 +Small Business,Germany,Amarilla,1250,300.00,375000.00,18750.00,356250.00,312500.00,43750.00,12/1/2014,12,December,2014 +Midmarket,France,Paseo,3801,15.00,57015.00,3420.90,53594.10,38010.00,15584.10,4/1/2014,4,April,2014 +Government,United States of America,Carretera,1117.5,20.00,22350.00,1341.00,21009.00,11175.00,9834.00,1/1/2014,1,January,2014 +Midmarket,Canada,Carretera,2844,15.00,42660.00,2559.60,40100.40,28440.00,11660.40,6/1/2014,6,June,2014 +Channel Partners,Mexico,Carretera,562,12.00,6744.00,404.64,6339.36,1686.00,4653.36,9/1/2014,9,September,2014 +Channel Partners,Canada,Carretera,2299,12.00,27588.00,1655.28,25932.72,6897.00,19035.72,10/1/2013,10,October,2013 +Midmarket,United States of America,Carretera,2030,15.00,30450.00,1827.00,28623.00,20300.00,8323.00,11/1/2014,11,November,2014 +Government,United States of America,Carretera,263,7.00,1841.00,110.46,1730.54,1315.00,415.54,11/1/2013,11,November,2013 +Enterprise,Germany,Carretera,887,125.00,110875.00,6652.50,104222.50,106440.00,-2217.50,12/1/2013,12,December,2013 +Government,Mexico,Montana,980,350.00,343000.00,20580.00,322420.00,254800.00,67620.00,4/1/2014,4,April,2014 +Government,Germany,Montana,1460,350.00,511000.00,30660.00,480340.00,379600.00,100740.00,5/1/2014,5,May,2014 +Government,France,Montana,1403,7.00,9821.00,589.26,9231.74,7015.00,2216.74,10/1/2013,10,October,2013 +Channel Partners,United States of America,Montana,2723,12.00,32676.00,1960.56,30715.44,8169.00,22546.44,11/1/2014,11,November,2014 +Government,France,Paseo,1496,350.00,523600.00,31416.00,492184.00,388960.00,103224.00,6/1/2014,6,June,2014 +Channel Partners,Canada,Paseo,2299,12.00,27588.00,1655.28,25932.72,6897.00,19035.72,10/1/2013,10,October,2013 +Government,United States of America,Paseo,727,350.00,254450.00,15267.00,239183.00,189020.00,50163.00,10/1/2013,10,October,2013 +Enterprise,Canada,Velo,952,125.00,119000.00,7140.00,111860.00,114240.00,-2380.00,2/1/2014,2,February,2014 +Enterprise,United States of America,Velo,2755,125.00,344375.00,20662.50,323712.50,330600.00,-6887.50,2/1/2014,2,February,2014 +Midmarket,Germany,Velo,1530,15.00,22950.00,1377.00,21573.00,15300.00,6273.00,5/1/2014,5,May,2014 +Government,France,Velo,1496,350.00,523600.00,31416.00,492184.00,388960.00,103224.00,6/1/2014,6,June,2014 +Government,Mexico,Velo,1498,7.00,10486.00,629.16,9856.84,7490.00,2366.84,6/1/2014,6,June,2014 +Small Business,France,Velo,1221,300.00,366300.00,21978.00,344322.00,305250.00,39072.00,10/1/2013,10,October,2013 +Government,France,Velo,2076,350.00,726600.00,43596.00,683004.00,539760.00,143244.00,10/1/2013,10,October,2013 +Midmarket,Canada,VTT,2844,15.00,42660.00,2559.60,40100.40,28440.00,11660.40,6/1/2014,6,June,2014 +Government,Mexico,VTT,1498,7.00,10486.00,629.16,9856.84,7490.00,2366.84,6/1/2014,6,June,2014 +Small Business,France,VTT,1221,300.00,366300.00,21978.00,344322.00,305250.00,39072.00,10/1/2013,10,October,2013 +Government,Mexico,VTT,1123,20.00,22460.00,1347.60,21112.40,11230.00,9882.40,11/1/2013,11,November,2013 +Small Business,Canada,VTT,2436,300.00,730800.00,43848.00,686952.00,609000.00,77952.00,12/1/2013,12,December,2013 +Enterprise,France,Amarilla,1987.5,125.00,248437.50,14906.25,233531.25,238500.00,-4968.75,1/1/2014,1,January,2014 +Government,Mexico,Amarilla,1679,350.00,587650.00,35259.00,552391.00,436540.00,115851.00,9/1/2014,9,September,2014 +Government,United States of America,Amarilla,727,350.00,254450.00,15267.00,239183.00,189020.00,50163.00,10/1/2013,10,October,2013 +Government,France,Amarilla,1403,7.00,9821.00,589.26,9231.74,7015.00,2216.74,10/1/2013,10,October,2013 +Government,France,Amarilla,2076,350.00,726600.00,43596.00,683004.00,539760.00,143244.00,10/1/2013,10,October,2013 +Government,France,Montana,1757,20.00,35140.00,2108.40,33031.60,17570.00,15461.60,10/1/2013,10,October,2013 +Midmarket,United States of America,Paseo,2198,15.00,32970.00,1978.20,30991.80,21980.00,9011.80,8/1/2014,8,August,2014 +Midmarket,Germany,Paseo,1743,15.00,26145.00,1568.70,24576.30,17430.00,7146.30,8/1/2014,8,August,2014 +Midmarket,United States of America,Paseo,1153,15.00,17295.00,1037.70,16257.30,11530.00,4727.30,10/1/2014,10,October,2014 +Government,France,Paseo,1757,20.00,35140.00,2108.40,33031.60,17570.00,15461.60,10/1/2013,10,October,2013 +Government,Germany,Velo,1001,20.00,20020.00,1201.20,18818.80,10010.00,8808.80,8/1/2014,8,August,2014 +Government,Mexico,Velo,1333,7.00,9331.00,559.86,8771.14,6665.00,2106.14,11/1/2014,11,November,2014 +Midmarket,United States of America,VTT,1153,15.00,17295.00,1037.70,16257.30,11530.00,4727.30,10/1/2014,10,October,2014 +Channel Partners,Mexico,Carretera,727,12.00,8724.00,610.68,8113.32,2181.00,5932.32,2/1/2014,2,February,2014 +Channel Partners,Canada,Carretera,1884,12.00,22608.00,1582.56,21025.44,5652.00,15373.44,8/1/2014,8,August,2014 +Government,Mexico,Carretera,1834,20.00,36680.00,2567.60,34112.40,18340.00,15772.40,9/1/2013,9,September,2013 +Channel Partners,Mexico,Montana,2340,12.00,28080.00,1965.60,26114.40,7020.00,19094.40,1/1/2014,1,January,2014 +Channel Partners,France,Montana,2342,12.00,28104.00,1967.28,26136.72,7026.00,19110.72,11/1/2014,11,November,2014 +Government,France,Paseo,1031,7.00,7217.00,505.19,6711.81,5155.00,1556.81,9/1/2013,9,September,2013 +Midmarket,Canada,Velo,1262,15.00,18930.00,1325.10,17604.90,12620.00,4984.90,5/1/2014,5,May,2014 +Government,Canada,Velo,1135,7.00,7945.00,556.15,7388.85,5675.00,1713.85,6/1/2014,6,June,2014 +Government,United States of America,Velo,547,7.00,3829.00,268.03,3560.97,2735.00,825.97,11/1/2014,11,November,2014 +Government,Canada,Velo,1582,7.00,11074.00,775.18,10298.82,7910.00,2388.82,12/1/2014,12,December,2014 +Channel Partners,France,VTT,1738.5,12.00,20862.00,1460.34,19401.66,5215.50,14186.16,4/1/2014,4,April,2014 +Channel Partners,Germany,VTT,2215,12.00,26580.00,1860.60,24719.40,6645.00,18074.40,9/1/2013,9,September,2013 +Government,Canada,VTT,1582,7.00,11074.00,775.18,10298.82,7910.00,2388.82,12/1/2014,12,December,2014 +Government,Canada,Amarilla,1135,7.00,7945.00,556.15,7388.85,5675.00,1713.85,6/1/2014,6,June,2014 +Government,United States of America,Carretera,1761,350.00,616350.00,43144.50,573205.50,457860.00,115345.50,3/1/2014,3,March,2014 +Small Business,France,Carretera,448,300.00,134400.00,9408.00,124992.00,112000.00,12992.00,6/1/2014,6,June,2014 +Small Business,France,Carretera,2181,300.00,654300.00,45801.00,608499.00,545250.00,63249.00,10/1/2014,10,October,2014 +Government,France,Montana,1976,20.00,39520.00,2766.40,36753.60,19760.00,16993.60,10/1/2014,10,October,2014 +Small Business,France,Montana,2181,300.00,654300.00,45801.00,608499.00,545250.00,63249.00,10/1/2014,10,October,2014 +Enterprise,Germany,Montana,2500,125.00,312500.00,21875.00,290625.00,300000.00,-9375.00,11/1/2013,11,November,2013 +Small Business,Canada,Paseo,1702,300.00,510600.00,35742.00,474858.00,425500.00,49358.00,5/1/2014,5,May,2014 +Small Business,France,Paseo,448,300.00,134400.00,9408.00,124992.00,112000.00,12992.00,6/1/2014,6,June,2014 +Enterprise,Germany,Paseo,3513,125.00,439125.00,30738.75,408386.25,421560.00,-13173.75,7/1/2014,7,July,2014 +Midmarket,France,Paseo,2101,15.00,31515.00,2206.05,29308.95,21010.00,8298.95,8/1/2014,8,August,2014 +Midmarket,United States of America,Paseo,2931,15.00,43965.00,3077.55,40887.45,29310.00,11577.45,9/1/2013,9,September,2013 +Government,France,Paseo,1535,20.00,30700.00,2149.00,28551.00,15350.00,13201.00,9/1/2014,9,September,2014 +Small Business,Germany,Paseo,1123,300.00,336900.00,23583.00,313317.00,280750.00,32567.00,9/1/2013,9,September,2013 +Small Business,Canada,Paseo,1404,300.00,421200.00,29484.00,391716.00,351000.00,40716.00,11/1/2013,11,November,2013 +Channel Partners,Mexico,Paseo,2763,12.00,33156.00,2320.92,30835.08,8289.00,22546.08,11/1/2013,11,November,2013 +Government,Germany,Paseo,2125,7.00,14875.00,1041.25,13833.75,10625.00,3208.75,12/1/2013,12,December,2013 +Small Business,France,Velo,1659,300.00,497700.00,34839.00,462861.00,414750.00,48111.00,7/1/2014,7,July,2014 +Government,Mexico,Velo,609,20.00,12180.00,852.60,11327.40,6090.00,5237.40,8/1/2014,8,August,2014 +Enterprise,Germany,Velo,2087,125.00,260875.00,18261.25,242613.75,250440.00,-7826.25,9/1/2014,9,September,2014 +Government,France,Velo,1976,20.00,39520.00,2766.40,36753.60,19760.00,16993.60,10/1/2014,10,October,2014 +Government,United States of America,Velo,1421,20.00,28420.00,1989.40,26430.60,14210.00,12220.60,12/1/2013,12,December,2013 +Small Business,United States of America,Velo,1372,300.00,411600.00,28812.00,382788.00,343000.00,39788.00,12/1/2014,12,December,2014 +Government,Germany,Velo,588,20.00,11760.00,823.20,10936.80,5880.00,5056.80,12/1/2013,12,December,2013 +Channel Partners,Canada,VTT,3244.5,12.00,38934.00,2725.38,36208.62,9733.50,26475.12,1/1/2014,1,January,2014 +Small Business,France,VTT,959,300.00,287700.00,20139.00,267561.00,239750.00,27811.00,2/1/2014,2,February,2014 +Small Business,Mexico,VTT,2747,300.00,824100.00,57687.00,766413.00,686750.00,79663.00,2/1/2014,2,February,2014 +Enterprise,Canada,Amarilla,1645,125.00,205625.00,14393.75,191231.25,197400.00,-6168.75,5/1/2014,5,May,2014 +Government,France,Amarilla,2876,350.00,1006600.00,70462.00,936138.00,747760.00,188378.00,9/1/2014,9,September,2014 +Enterprise,Germany,Amarilla,994,125.00,124250.00,8697.50,115552.50,119280.00,-3727.50,9/1/2013,9,September,2013 +Government,Canada,Amarilla,1118,20.00,22360.00,1565.20,20794.80,11180.00,9614.80,11/1/2014,11,November,2014 +Small Business,United States of America,Amarilla,1372,300.00,411600.00,28812.00,382788.00,343000.00,39788.00,12/1/2014,12,December,2014 +Government,Canada,Montana,488,7.00,3416.00,273.28,3142.72,2440.00,702.72,2/1/2014,2,February,2014 +Government,United States of America,Montana,1282,20.00,25640.00,2051.20,23588.80,12820.00,10768.80,6/1/2014,6,June,2014 +Government,Canada,Paseo,257,7.00,1799.00,143.92,1655.08,1285.00,370.08,5/1/2014,5,May,2014 +Government,United States of America,Amarilla,1282,20.00,25640.00,2051.20,23588.80,12820.00,10768.80,6/1/2014,6,June,2014 +Enterprise,Mexico,Carretera,1540,125.00,192500.00,15400.00,177100.00,184800.00,-7700.00,8/1/2014,8,August,2014 +Midmarket,France,Carretera,490,15.00,7350.00,588.00,6762.00,4900.00,1862.00,11/1/2014,11,November,2014 +Government,Mexico,Carretera,1362,350.00,476700.00,38136.00,438564.00,354120.00,84444.00,12/1/2014,12,December,2014 +Midmarket,France,Montana,2501,15.00,37515.00,3001.20,34513.80,25010.00,9503.80,3/1/2014,3,March,2014 +Government,Canada,Montana,708,20.00,14160.00,1132.80,13027.20,7080.00,5947.20,6/1/2014,6,June,2014 +Government,Germany,Montana,645,20.00,12900.00,1032.00,11868.00,6450.00,5418.00,7/1/2014,7,July,2014 +Small Business,France,Montana,1562,300.00,468600.00,37488.00,431112.00,390500.00,40612.00,8/1/2014,8,August,2014 +Small Business,Canada,Montana,1283,300.00,384900.00,30792.00,354108.00,320750.00,33358.00,9/1/2013,9,September,2013 +Midmarket,Germany,Montana,711,15.00,10665.00,853.20,9811.80,7110.00,2701.80,12/1/2014,12,December,2014 +Enterprise,Mexico,Paseo,1114,125.00,139250.00,11140.00,128110.00,133680.00,-5570.00,3/1/2014,3,March,2014 +Government,Germany,Paseo,1259,7.00,8813.00,705.04,8107.96,6295.00,1812.96,4/1/2014,4,April,2014 +Government,Germany,Paseo,1095,7.00,7665.00,613.20,7051.80,5475.00,1576.80,5/1/2014,5,May,2014 +Government,Germany,Paseo,1366,20.00,27320.00,2185.60,25134.40,13660.00,11474.40,6/1/2014,6,June,2014 +Small Business,Mexico,Paseo,2460,300.00,738000.00,59040.00,678960.00,615000.00,63960.00,6/1/2014,6,June,2014 +Government,United States of America,Paseo,678,7.00,4746.00,379.68,4366.32,3390.00,976.32,8/1/2014,8,August,2014 +Government,Germany,Paseo,1598,7.00,11186.00,894.88,10291.12,7990.00,2301.12,8/1/2014,8,August,2014 +Government,Germany,Paseo,2409,7.00,16863.00,1349.04,15513.96,12045.00,3468.96,9/1/2013,9,September,2013 +Government,Germany,Paseo,1934,20.00,38680.00,3094.40,35585.60,19340.00,16245.60,9/1/2014,9,September,2014 +Government,Mexico,Paseo,2993,20.00,59860.00,4788.80,55071.20,29930.00,25141.20,9/1/2014,9,September,2014 +Government,Germany,Paseo,2146,350.00,751100.00,60088.00,691012.00,557960.00,133052.00,11/1/2013,11,November,2013 +Government,Mexico,Paseo,1946,7.00,13622.00,1089.76,12532.24,9730.00,2802.24,12/1/2013,12,December,2013 +Government,Mexico,Paseo,1362,350.00,476700.00,38136.00,438564.00,354120.00,84444.00,12/1/2014,12,December,2014 +Channel Partners,Canada,Velo,598,12.00,7176.00,574.08,6601.92,1794.00,4807.92,3/1/2014,3,March,2014 +Government,United States of America,Velo,2907,7.00,20349.00,1627.92,18721.08,14535.00,4186.08,6/1/2014,6,June,2014 +Government,Germany,Velo,2338,7.00,16366.00,1309.28,15056.72,11690.00,3366.72,6/1/2014,6,June,2014 +Small Business,France,Velo,386,300.00,115800.00,9264.00,106536.00,96500.00,10036.00,11/1/2013,11,November,2013 +Small Business,Mexico,Velo,635,300.00,190500.00,15240.00,175260.00,158750.00,16510.00,12/1/2014,12,December,2014 +Government,France,VTT,574.5,350.00,201075.00,16086.00,184989.00,149370.00,35619.00,4/1/2014,4,April,2014 +Government,Germany,VTT,2338,7.00,16366.00,1309.28,15056.72,11690.00,3366.72,6/1/2014,6,June,2014 +Government,France,VTT,381,350.00,133350.00,10668.00,122682.00,99060.00,23622.00,8/1/2014,8,August,2014 +Government,Germany,VTT,422,350.00,147700.00,11816.00,135884.00,109720.00,26164.00,8/1/2014,8,August,2014 +Small Business,Canada,VTT,2134,300.00,640200.00,51216.00,588984.00,533500.00,55484.00,9/1/2014,9,September,2014 +Small Business,United States of America,VTT,808,300.00,242400.00,19392.00,223008.00,202000.00,21008.00,12/1/2013,12,December,2013 +Government,Canada,Amarilla,708,20.00,14160.00,1132.80,13027.20,7080.00,5947.20,6/1/2014,6,June,2014 +Government,United States of America,Amarilla,2907,7.00,20349.00,1627.92,18721.08,14535.00,4186.08,6/1/2014,6,June,2014 +Government,Germany,Amarilla,1366,20.00,27320.00,2185.60,25134.40,13660.00,11474.40,6/1/2014,6,June,2014 +Small Business,Mexico,Amarilla,2460,300.00,738000.00,59040.00,678960.00,615000.00,63960.00,6/1/2014,6,June,2014 +Government,Germany,Amarilla,1520,20.00,30400.00,2432.00,27968.00,15200.00,12768.00,11/1/2014,11,November,2014 +Midmarket,Germany,Amarilla,711,15.00,10665.00,853.20,9811.80,7110.00,2701.80,12/1/2014,12,December,2014 +Channel Partners,Mexico,Amarilla,1375,12.00,16500.00,1320.00,15180.00,4125.00,11055.00,12/1/2013,12,December,2013 +Small Business,Mexico,Amarilla,635,300.00,190500.00,15240.00,175260.00,158750.00,16510.00,12/1/2014,12,December,2014 +Government,United States of America,VTT,436.5,20.00,8730.00,698.40,8031.60,4365.00,3666.60,7/1/2014,7,July,2014 +Small Business,Canada,Carretera,1094,300.00,328200.00,29538.00,298662.00,273500.00,25162.00,6/1/2014,6,June,2014 +Channel Partners,Mexico,Carretera,367,12.00,4404.00,396.36,4007.64,1101.00,2906.64,10/1/2013,10,October,2013 +Small Business,Canada,Montana,3802.5,300.00,1140750.00,102667.50,1038082.50,950625.00,87457.50,4/1/2014,4,April,2014 +Government,France,Montana,1666,350.00,583100.00,52479.00,530621.00,433160.00,97461.00,5/1/2014,5,May,2014 +Small Business,France,Montana,322,300.00,96600.00,8694.00,87906.00,80500.00,7406.00,9/1/2013,9,September,2013 +Channel Partners,Canada,Montana,2321,12.00,27852.00,2506.68,25345.32,6963.00,18382.32,11/1/2014,11,November,2014 +Enterprise,France,Montana,1857,125.00,232125.00,20891.25,211233.75,222840.00,-11606.25,11/1/2013,11,November,2013 +Government,Canada,Montana,1611,7.00,11277.00,1014.93,10262.07,8055.00,2207.07,12/1/2013,12,December,2013 +Enterprise,United States of America,Montana,2797,125.00,349625.00,31466.25,318158.75,335640.00,-17481.25,12/1/2014,12,December,2014 +Small Business,Germany,Montana,334,300.00,100200.00,9018.00,91182.00,83500.00,7682.00,12/1/2013,12,December,2013 +Small Business,Mexico,Paseo,2565,300.00,769500.00,69255.00,700245.00,641250.00,58995.00,1/1/2014,1,January,2014 +Government,Mexico,Paseo,2417,350.00,845950.00,76135.50,769814.50,628420.00,141394.50,1/1/2014,1,January,2014 +Midmarket,United States of America,Paseo,3675,15.00,55125.00,4961.25,50163.75,36750.00,13413.75,4/1/2014,4,April,2014 +Small Business,Canada,Paseo,1094,300.00,328200.00,29538.00,298662.00,273500.00,25162.00,6/1/2014,6,June,2014 +Midmarket,France,Paseo,1227,15.00,18405.00,1656.45,16748.55,12270.00,4478.55,10/1/2014,10,October,2014 +Channel Partners,Mexico,Paseo,367,12.00,4404.00,396.36,4007.64,1101.00,2906.64,10/1/2013,10,October,2013 +Small Business,France,Paseo,1324,300.00,397200.00,35748.00,361452.00,331000.00,30452.00,11/1/2014,11,November,2014 +Channel Partners,Germany,Paseo,1775,12.00,21300.00,1917.00,19383.00,5325.00,14058.00,11/1/2013,11,November,2013 +Enterprise,United States of America,Paseo,2797,125.00,349625.00,31466.25,318158.75,335640.00,-17481.25,12/1/2014,12,December,2014 +Midmarket,Mexico,Velo,245,15.00,3675.00,330.75,3344.25,2450.00,894.25,5/1/2014,5,May,2014 +Small Business,Canada,Velo,3793.5,300.00,1138050.00,102424.50,1035625.50,948375.00,87250.50,7/1/2014,7,July,2014 +Government,Germany,Velo,1307,350.00,457450.00,41170.50,416279.50,339820.00,76459.50,7/1/2014,7,July,2014 +Enterprise,Canada,Velo,567,125.00,70875.00,6378.75,64496.25,68040.00,-3543.75,9/1/2014,9,September,2014 +Enterprise,Mexico,Velo,2110,125.00,263750.00,23737.50,240012.50,253200.00,-13187.50,9/1/2014,9,September,2014 +Government,Canada,Velo,1269,350.00,444150.00,39973.50,404176.50,329940.00,74236.50,10/1/2014,10,October,2014 +Channel Partners,United States of America,VTT,1956,12.00,23472.00,2112.48,21359.52,5868.00,15491.52,1/1/2014,1,January,2014 +Small Business,Germany,VTT,2659,300.00,797700.00,71793.00,725907.00,664750.00,61157.00,2/1/2014,2,February,2014 +Government,United States of America,VTT,1351.5,350.00,473025.00,42572.25,430452.75,351390.00,79062.75,4/1/2014,4,April,2014 +Channel Partners,Germany,VTT,880,12.00,10560.00,950.40,9609.60,2640.00,6969.60,5/1/2014,5,May,2014 +Small Business,United States of America,VTT,1867,300.00,560100.00,50409.00,509691.00,466750.00,42941.00,9/1/2014,9,September,2014 +Channel Partners,France,VTT,2234,12.00,26808.00,2412.72,24395.28,6702.00,17693.28,9/1/2013,9,September,2013 +Midmarket,France,VTT,1227,15.00,18405.00,1656.45,16748.55,12270.00,4478.55,10/1/2014,10,October,2014 +Enterprise,Mexico,VTT,877,125.00,109625.00,9866.25,99758.75,105240.00,-5481.25,11/1/2014,11,November,2014 +Government,United States of America,Amarilla,2071,350.00,724850.00,65236.50,659613.50,538460.00,121153.50,9/1/2014,9,September,2014 +Government,Canada,Amarilla,1269,350.00,444150.00,39973.50,404176.50,329940.00,74236.50,10/1/2014,10,October,2014 +Midmarket,Germany,Amarilla,970,15.00,14550.00,1309.50,13240.50,9700.00,3540.50,11/1/2013,11,November,2013 +Government,Mexico,Amarilla,1694,20.00,33880.00,3049.20,30830.80,16940.00,13890.80,11/1/2014,11,November,2014 +Government,Germany,Carretera,663,20.00,13260.00,1193.40,12066.60,6630.00,5436.60,5/1/2014,5,May,2014 +Government,Canada,Carretera,819,7.00,5733.00,515.97,5217.03,4095.00,1122.03,7/1/2014,7,July,2014 +Channel Partners,Germany,Carretera,1580,12.00,18960.00,1706.40,17253.60,4740.00,12513.60,9/1/2014,9,September,2014 +Government,Mexico,Carretera,521,7.00,3647.00,328.23,3318.77,2605.00,713.77,12/1/2014,12,December,2014 +Government,United States of America,Paseo,973,20.00,19460.00,1751.40,17708.60,9730.00,7978.60,3/1/2014,3,March,2014 +Government,Mexico,Paseo,1038,20.00,20760.00,1868.40,18891.60,10380.00,8511.60,6/1/2014,6,June,2014 +Government,Germany,Paseo,360,7.00,2520.00,226.80,2293.20,1800.00,493.20,10/1/2014,10,October,2014 +Channel Partners,France,Velo,1967,12.00,23604.00,2124.36,21479.64,5901.00,15578.64,3/1/2014,3,March,2014 +Midmarket,Mexico,Velo,2628,15.00,39420.00,3547.80,35872.20,26280.00,9592.20,4/1/2014,4,April,2014 +Government,Germany,VTT,360,7.00,2520.00,226.80,2293.20,1800.00,493.20,10/1/2014,10,October,2014 +Government,France,VTT,2682,20.00,53640.00,4827.60,48812.40,26820.00,21992.40,11/1/2013,11,November,2013 +Government,Mexico,VTT,521,7.00,3647.00,328.23,3318.77,2605.00,713.77,12/1/2014,12,December,2014 +Government,Mexico,Amarilla,1038,20.00,20760.00,1868.40,18891.60,10380.00,8511.60,6/1/2014,6,June,2014 +Midmarket,Canada,Amarilla,1630.5,15.00,24457.50,2201.18,22256.33,16305.00,5951.33,7/1/2014,7,July,2014 +Channel Partners,France,Amarilla,306,12.00,3672.00,330.48,3341.52,918.00,2423.52,12/1/2013,12,December,2013 +Channel Partners,United States of America,Carretera,386,12.00,4632.00,463.20,4168.80,1158.00,3010.80,10/1/2013,10,October,2013 +Government,United States of America,Montana,2328,7.00,16296.00,1629.60,14666.40,11640.00,3026.40,9/1/2014,9,September,2014 +Channel Partners,United States of America,Paseo,386,12.00,4632.00,463.20,4168.80,1158.00,3010.80,10/1/2013,10,October,2013 +Enterprise,United States of America,Carretera,3445.5,125.00,430687.50,43068.75,387618.75,413460.00,-25841.25,4/1/2014,4,April,2014 +Enterprise,France,Carretera,1482,125.00,185250.00,18525.00,166725.00,177840.00,-11115.00,12/1/2013,12,December,2013 +Government,United States of America,Montana,2313,350.00,809550.00,80955.00,728595.00,601380.00,127215.00,5/1/2014,5,May,2014 +Enterprise,United States of America,Montana,1804,125.00,225500.00,22550.00,202950.00,216480.00,-13530.00,11/1/2013,11,November,2013 +Midmarket,France,Montana,2072,15.00,31080.00,3108.00,27972.00,20720.00,7252.00,12/1/2014,12,December,2014 +Government,France,Paseo,1954,20.00,39080.00,3908.00,35172.00,19540.00,15632.00,3/1/2014,3,March,2014 +Small Business,Mexico,Paseo,591,300.00,177300.00,17730.00,159570.00,147750.00,11820.00,5/1/2014,5,May,2014 +Midmarket,France,Paseo,2167,15.00,32505.00,3250.50,29254.50,21670.00,7584.50,10/1/2013,10,October,2013 +Government,Germany,Paseo,241,20.00,4820.00,482.00,4338.00,2410.00,1928.00,10/1/2014,10,October,2014 +Midmarket,Germany,Velo,681,15.00,10215.00,1021.50,9193.50,6810.00,2383.50,1/1/2014,1,January,2014 +Midmarket,Germany,Velo,510,15.00,7650.00,765.00,6885.00,5100.00,1785.00,4/1/2014,4,April,2014 +Midmarket,United States of America,Velo,790,15.00,11850.00,1185.00,10665.00,7900.00,2765.00,5/1/2014,5,May,2014 +Government,France,Velo,639,350.00,223650.00,22365.00,201285.00,166140.00,35145.00,7/1/2014,7,July,2014 +Enterprise,United States of America,Velo,1596,125.00,199500.00,19950.00,179550.00,191520.00,-11970.00,9/1/2014,9,September,2014 +Small Business,United States of America,Velo,2294,300.00,688200.00,68820.00,619380.00,573500.00,45880.00,10/1/2013,10,October,2013 +Government,Germany,Velo,241,20.00,4820.00,482.00,4338.00,2410.00,1928.00,10/1/2014,10,October,2014 +Government,Germany,Velo,2665,7.00,18655.00,1865.50,16789.50,13325.00,3464.50,11/1/2014,11,November,2014 +Enterprise,Canada,Velo,1916,125.00,239500.00,23950.00,215550.00,229920.00,-14370.00,12/1/2013,12,December,2013 +Small Business,France,Velo,853,300.00,255900.00,25590.00,230310.00,213250.00,17060.00,12/1/2014,12,December,2014 +Enterprise,Mexico,VTT,341,125.00,42625.00,4262.50,38362.50,40920.00,-2557.50,5/1/2014,5,May,2014 +Midmarket,Mexico,VTT,641,15.00,9615.00,961.50,8653.50,6410.00,2243.50,7/1/2014,7,July,2014 +Government,United States of America,VTT,2807,350.00,982450.00,98245.00,884205.00,729820.00,154385.00,8/1/2014,8,August,2014 +Small Business,Mexico,VTT,432,300.00,129600.00,12960.00,116640.00,108000.00,8640.00,9/1/2014,9,September,2014 +Small Business,United States of America,VTT,2294,300.00,688200.00,68820.00,619380.00,573500.00,45880.00,10/1/2013,10,October,2013 +Midmarket,France,VTT,2167,15.00,32505.00,3250.50,29254.50,21670.00,7584.50,10/1/2013,10,October,2013 +Enterprise,Canada,VTT,2529,125.00,316125.00,31612.50,284512.50,303480.00,-18967.50,11/1/2014,11,November,2014 +Government,Germany,VTT,1870,350.00,654500.00,65450.00,589050.00,486200.00,102850.00,12/1/2013,12,December,2013 +Enterprise,United States of America,Amarilla,579,125.00,72375.00,7237.50,65137.50,69480.00,-4342.50,1/1/2014,1,January,2014 +Government,Canada,Amarilla,2240,350.00,784000.00,78400.00,705600.00,582400.00,123200.00,2/1/2014,2,February,2014 +Small Business,United States of America,Amarilla,2993,300.00,897900.00,89790.00,808110.00,748250.00,59860.00,3/1/2014,3,March,2014 +Channel Partners,Canada,Amarilla,3520.5,12.00,42246.00,4224.60,38021.40,10561.50,27459.90,4/1/2014,4,April,2014 +Government,Mexico,Amarilla,2039,20.00,40780.00,4078.00,36702.00,20390.00,16312.00,5/1/2014,5,May,2014 +Channel Partners,Germany,Amarilla,2574,12.00,30888.00,3088.80,27799.20,7722.00,20077.20,8/1/2014,8,August,2014 +Government,Canada,Amarilla,707,350.00,247450.00,24745.00,222705.00,183820.00,38885.00,9/1/2014,9,September,2014 +Midmarket,France,Amarilla,2072,15.00,31080.00,3108.00,27972.00,20720.00,7252.00,12/1/2014,12,December,2014 +Small Business,France,Amarilla,853,300.00,255900.00,25590.00,230310.00,213250.00,17060.00,12/1/2014,12,December,2014 +Channel Partners,France,Carretera,1198,12.00,14376.00,1581.36,12794.64,3594.00,9200.64,10/1/2013,10,October,2013 +Government,France,Paseo,2532,7.00,17724.00,1949.64,15774.36,12660.00,3114.36,4/1/2014,4,April,2014 +Channel Partners,France,Paseo,1198,12.00,14376.00,1581.36,12794.64,3594.00,9200.64,10/1/2013,10,October,2013 +Midmarket,Canada,Velo,384,15.00,5760.00,633.60,5126.40,3840.00,1286.40,1/1/2014,1,January,2014 +Channel Partners,Germany,Velo,472,12.00,5664.00,623.04,5040.96,1416.00,3624.96,10/1/2014,10,October,2014 +Government,United States of America,VTT,1579,7.00,11053.00,1215.83,9837.17,7895.00,1942.17,3/1/2014,3,March,2014 +Channel Partners,Mexico,VTT,1005,12.00,12060.00,1326.60,10733.40,3015.00,7718.40,9/1/2013,9,September,2013 +Midmarket,United States of America,Amarilla,3199.5,15.00,47992.50,5279.18,42713.33,31995.00,10718.33,7/1/2014,7,July,2014 +Channel Partners,Germany,Amarilla,472,12.00,5664.00,623.04,5040.96,1416.00,3624.96,10/1/2014,10,October,2014 +Channel Partners,Canada,Carretera,1937,12.00,23244.00,2556.84,20687.16,5811.00,14876.16,2/1/2014,2,February,2014 +Government,Germany,Carretera,792,350.00,277200.00,30492.00,246708.00,205920.00,40788.00,3/1/2014,3,March,2014 +Small Business,Germany,Carretera,2811,300.00,843300.00,92763.00,750537.00,702750.00,47787.00,7/1/2014,7,July,2014 +Enterprise,France,Carretera,2441,125.00,305125.00,33563.75,271561.25,292920.00,-21358.75,10/1/2014,10,October,2014 +Midmarket,Canada,Carretera,1560,15.00,23400.00,2574.00,20826.00,15600.00,5226.00,11/1/2013,11,November,2013 +Government,Mexico,Carretera,2706,7.00,18942.00,2083.62,16858.38,13530.00,3328.38,11/1/2013,11,November,2013 +Government,Germany,Montana,766,350.00,268100.00,29491.00,238609.00,199160.00,39449.00,1/1/2014,1,January,2014 +Government,Germany,Montana,2992,20.00,59840.00,6582.40,53257.60,29920.00,23337.60,10/1/2013,10,October,2013 +Midmarket,Mexico,Montana,2157,15.00,32355.00,3559.05,28795.95,21570.00,7225.95,12/1/2014,12,December,2014 +Small Business,Canada,Paseo,873,300.00,261900.00,28809.00,233091.00,218250.00,14841.00,1/1/2014,1,January,2014 +Government,Mexico,Paseo,1122,20.00,22440.00,2468.40,19971.60,11220.00,8751.60,3/1/2014,3,March,2014 +Government,Canada,Paseo,2104.5,350.00,736575.00,81023.25,655551.75,547170.00,108381.75,7/1/2014,7,July,2014 +Channel Partners,Canada,Paseo,4026,12.00,48312.00,5314.32,42997.68,12078.00,30919.68,7/1/2014,7,July,2014 +Channel Partners,France,Paseo,2425.5,12.00,29106.00,3201.66,25904.34,7276.50,18627.84,7/1/2014,7,July,2014 +Government,Canada,Paseo,2394,20.00,47880.00,5266.80,42613.20,23940.00,18673.20,8/1/2014,8,August,2014 +Midmarket,Mexico,Paseo,1984,15.00,29760.00,3273.60,26486.40,19840.00,6646.40,8/1/2014,8,August,2014 +Enterprise,France,Paseo,2441,125.00,305125.00,33563.75,271561.25,292920.00,-21358.75,10/1/2014,10,October,2014 +Government,Germany,Paseo,2992,20.00,59840.00,6582.40,53257.60,29920.00,23337.60,10/1/2013,10,October,2013 +Small Business,Canada,Paseo,1366,300.00,409800.00,45078.00,364722.00,341500.00,23222.00,11/1/2014,11,November,2014 +Government,France,Velo,2805,20.00,56100.00,6171.00,49929.00,28050.00,21879.00,9/1/2013,9,September,2013 +Midmarket,Mexico,Velo,655,15.00,9825.00,1080.75,8744.25,6550.00,2194.25,9/1/2013,9,September,2013 +Government,Mexico,Velo,344,350.00,120400.00,13244.00,107156.00,89440.00,17716.00,10/1/2013,10,October,2013 +Government,Canada,Velo,1808,7.00,12656.00,1392.16,11263.84,9040.00,2223.84,11/1/2014,11,November,2014 +Channel Partners,France,VTT,1734,12.00,20808.00,2288.88,18519.12,5202.00,13317.12,1/1/2014,1,January,2014 +Enterprise,Mexico,VTT,554,125.00,69250.00,7617.50,61632.50,66480.00,-4847.50,1/1/2014,1,January,2014 +Government,Canada,VTT,2935,20.00,58700.00,6457.00,52243.00,29350.00,22893.00,11/1/2013,11,November,2013 +Enterprise,Germany,Amarilla,3165,125.00,395625.00,43518.75,352106.25,379800.00,-27693.75,1/1/2014,1,January,2014 +Government,Mexico,Amarilla,2629,20.00,52580.00,5783.80,46796.20,26290.00,20506.20,1/1/2014,1,January,2014 +Enterprise,France,Amarilla,1433,125.00,179125.00,19703.75,159421.25,171960.00,-12538.75,5/1/2014,5,May,2014 +Enterprise,Mexico,Amarilla,947,125.00,118375.00,13021.25,105353.75,113640.00,-8286.25,9/1/2013,9,September,2013 +Government,Mexico,Amarilla,344,350.00,120400.00,13244.00,107156.00,89440.00,17716.00,10/1/2013,10,October,2013 +Midmarket,Mexico,Amarilla,2157,15.00,32355.00,3559.05,28795.95,21570.00,7225.95,12/1/2014,12,December,2014 +Government,United States of America,Paseo,380,7.00,2660.00,292.60,2367.40,1900.00,467.40,9/1/2013,9,September,2013 +Government,Mexico,Carretera,886,350.00,310100.00,37212.00,272888.00,230360.00,42528.00,6/1/2014,6,June,2014 +Enterprise,Canada,Carretera,2416,125.00,302000.00,36240.00,265760.00,289920.00,-24160.00,9/1/2013,9,September,2013 +Enterprise,Mexico,Carretera,2156,125.00,269500.00,32340.00,237160.00,258720.00,-21560.00,10/1/2014,10,October,2014 +Midmarket,Canada,Carretera,2689,15.00,40335.00,4840.20,35494.80,26890.00,8604.80,11/1/2014,11,November,2014 +Midmarket,United States of America,Montana,677,15.00,10155.00,1218.60,8936.40,6770.00,2166.40,3/1/2014,3,March,2014 +Small Business,France,Montana,1773,300.00,531900.00,63828.00,468072.00,443250.00,24822.00,4/1/2014,4,April,2014 +Government,Mexico,Montana,2420,7.00,16940.00,2032.80,14907.20,12100.00,2807.20,9/1/2014,9,September,2014 +Government,Canada,Montana,2734,7.00,19138.00,2296.56,16841.44,13670.00,3171.44,10/1/2014,10,October,2014 +Government,Mexico,Montana,1715,20.00,34300.00,4116.00,30184.00,17150.00,13034.00,10/1/2013,10,October,2013 +Small Business,France,Montana,1186,300.00,355800.00,42696.00,313104.00,296500.00,16604.00,12/1/2013,12,December,2013 +Small Business,United States of America,Paseo,3495,300.00,1048500.00,125820.00,922680.00,873750.00,48930.00,1/1/2014,1,January,2014 +Government,Mexico,Paseo,886,350.00,310100.00,37212.00,272888.00,230360.00,42528.00,6/1/2014,6,June,2014 +Enterprise,Mexico,Paseo,2156,125.00,269500.00,32340.00,237160.00,258720.00,-21560.00,10/1/2014,10,October,2014 +Government,Mexico,Paseo,905,20.00,18100.00,2172.00,15928.00,9050.00,6878.00,10/1/2014,10,October,2014 +Government,Mexico,Paseo,1715,20.00,34300.00,4116.00,30184.00,17150.00,13034.00,10/1/2013,10,October,2013 +Government,France,Paseo,1594,350.00,557900.00,66948.00,490952.00,414440.00,76512.00,11/1/2014,11,November,2014 +Small Business,Germany,Paseo,1359,300.00,407700.00,48924.00,358776.00,339750.00,19026.00,11/1/2014,11,November,2014 +Small Business,Mexico,Paseo,2150,300.00,645000.00,77400.00,567600.00,537500.00,30100.00,11/1/2014,11,November,2014 +Government,Mexico,Paseo,1197,350.00,418950.00,50274.00,368676.00,311220.00,57456.00,11/1/2014,11,November,2014 +Midmarket,Mexico,Paseo,380,15.00,5700.00,684.00,5016.00,3800.00,1216.00,12/1/2013,12,December,2013 +Government,Mexico,Paseo,1233,20.00,24660.00,2959.20,21700.80,12330.00,9370.80,12/1/2014,12,December,2014 +Government,Mexico,Velo,1395,350.00,488250.00,58590.00,429660.00,362700.00,66960.00,7/1/2014,7,July,2014 +Government,United States of America,Velo,986,350.00,345100.00,41412.00,303688.00,256360.00,47328.00,10/1/2014,10,October,2014 +Government,Mexico,Velo,905,20.00,18100.00,2172.00,15928.00,9050.00,6878.00,10/1/2014,10,October,2014 +Channel Partners,Canada,VTT,2109,12.00,25308.00,3036.96,22271.04,6327.00,15944.04,5/1/2014,5,May,2014 +Midmarket,France,VTT,3874.5,15.00,58117.50,6974.10,51143.40,38745.00,12398.40,7/1/2014,7,July,2014 +Government,Canada,VTT,623,350.00,218050.00,26166.00,191884.00,161980.00,29904.00,9/1/2013,9,September,2013 +Government,United States of America,VTT,986,350.00,345100.00,41412.00,303688.00,256360.00,47328.00,10/1/2014,10,October,2014 +Enterprise,United States of America,VTT,2387,125.00,298375.00,35805.00,262570.00,286440.00,-23870.00,11/1/2014,11,November,2014 +Government,Mexico,VTT,1233,20.00,24660.00,2959.20,21700.80,12330.00,9370.80,12/1/2014,12,December,2014 +Government,United States of America,Amarilla,270,350.00,94500.00,11340.00,83160.00,70200.00,12960.00,2/1/2014,2,February,2014 +Government,France,Amarilla,3421.5,7.00,23950.50,2874.06,21076.44,17107.50,3968.94,7/1/2014,7,July,2014 +Government,Canada,Amarilla,2734,7.00,19138.00,2296.56,16841.44,13670.00,3171.44,10/1/2014,10,October,2014 +Midmarket,United States of America,Amarilla,2548,15.00,38220.00,4586.40,33633.60,25480.00,8153.60,11/1/2013,11,November,2013 +Government,France,Carretera,2521.5,20.00,50430.00,6051.60,44378.40,25215.00,19163.40,1/1/2014,1,January,2014 +Channel Partners,Mexico,Montana,2661,12.00,31932.00,3831.84,28100.16,7983.00,20117.16,5/1/2014,5,May,2014 +Government,Germany,Paseo,1531,20.00,30620.00,3674.40,26945.60,15310.00,11635.60,12/1/2014,12,December,2014 +Government,France,VTT,1491,7.00,10437.00,1252.44,9184.56,7455.00,1729.56,3/1/2014,3,March,2014 +Government,Germany,VTT,1531,20.00,30620.00,3674.40,26945.60,15310.00,11635.60,12/1/2014,12,December,2014 +Channel Partners,Canada,Amarilla,2761,12.00,33132.00,3975.84,29156.16,8283.00,20873.16,9/1/2013,9,September,2013 +Midmarket,United States of America,Carretera,2567,15.00,38505.00,5005.65,33499.35,25670.00,7829.35,6/1/2014,6,June,2014 +Midmarket,United States of America,VTT,2567,15.00,38505.00,5005.65,33499.35,25670.00,7829.35,6/1/2014,6,June,2014 +Government,Canada,Carretera,923,350.00,323050.00,41996.50,281053.50,239980.00,41073.50,3/1/2014,3,March,2014 +Government,France,Carretera,1790,350.00,626500.00,81445.00,545055.00,465400.00,79655.00,3/1/2014,3,March,2014 +Government,Germany,Carretera,442,20.00,8840.00,1149.20,7690.80,4420.00,3270.80,9/1/2013,9,September,2013 +Government,United States of America,Montana,982.5,350.00,343875.00,44703.75,299171.25,255450.00,43721.25,1/1/2014,1,January,2014 +Government,United States of America,Montana,1298,7.00,9086.00,1181.18,7904.82,6490.00,1414.82,2/1/2014,2,February,2014 +Channel Partners,Mexico,Montana,604,12.00,7248.00,942.24,6305.76,1812.00,4493.76,6/1/2014,6,June,2014 +Government,Mexico,Montana,2255,20.00,45100.00,5863.00,39237.00,22550.00,16687.00,7/1/2014,7,July,2014 +Government,Canada,Montana,1249,20.00,24980.00,3247.40,21732.60,12490.00,9242.60,10/1/2014,10,October,2014 +Government,United States of America,Paseo,1438.5,7.00,10069.50,1309.04,8760.47,7192.50,1567.97,1/1/2014,1,January,2014 +Small Business,Germany,Paseo,807,300.00,242100.00,31473.00,210627.00,201750.00,8877.00,1/1/2014,1,January,2014 +Government,United States of America,Paseo,2641,20.00,52820.00,6866.60,45953.40,26410.00,19543.40,2/1/2014,2,February,2014 +Government,Germany,Paseo,2708,20.00,54160.00,7040.80,47119.20,27080.00,20039.20,2/1/2014,2,February,2014 +Government,Canada,Paseo,2632,350.00,921200.00,119756.00,801444.00,684320.00,117124.00,6/1/2014,6,June,2014 +Enterprise,Canada,Paseo,1583,125.00,197875.00,25723.75,172151.25,189960.00,-17808.75,6/1/2014,6,June,2014 +Channel Partners,Mexico,Paseo,571,12.00,6852.00,890.76,5961.24,1713.00,4248.24,7/1/2014,7,July,2014 +Government,France,Paseo,2696,7.00,18872.00,2453.36,16418.64,13480.00,2938.64,8/1/2014,8,August,2014 +Midmarket,Canada,Paseo,1565,15.00,23475.00,3051.75,20423.25,15650.00,4773.25,10/1/2014,10,October,2014 +Government,Canada,Paseo,1249,20.00,24980.00,3247.40,21732.60,12490.00,9242.60,10/1/2014,10,October,2014 +Government,Germany,Paseo,357,350.00,124950.00,16243.50,108706.50,92820.00,15886.50,11/1/2014,11,November,2014 +Channel Partners,Germany,Paseo,1013,12.00,12156.00,1580.28,10575.72,3039.00,7536.72,12/1/2014,12,December,2014 +Midmarket,France,Velo,3997.5,15.00,59962.50,7795.13,52167.38,39975.00,12192.38,1/1/2014,1,January,2014 +Government,Canada,Velo,2632,350.00,921200.00,119756.00,801444.00,684320.00,117124.00,6/1/2014,6,June,2014 +Government,France,Velo,1190,7.00,8330.00,1082.90,7247.10,5950.00,1297.10,6/1/2014,6,June,2014 +Channel Partners,Mexico,Velo,604,12.00,7248.00,942.24,6305.76,1812.00,4493.76,6/1/2014,6,June,2014 +Midmarket,Germany,Velo,660,15.00,9900.00,1287.00,8613.00,6600.00,2013.00,9/1/2013,9,September,2013 +Channel Partners,Mexico,Velo,410,12.00,4920.00,639.60,4280.40,1230.00,3050.40,10/1/2014,10,October,2014 +Small Business,Mexico,Velo,2605,300.00,781500.00,101595.00,679905.00,651250.00,28655.00,11/1/2013,11,November,2013 +Channel Partners,Germany,Velo,1013,12.00,12156.00,1580.28,10575.72,3039.00,7536.72,12/1/2014,12,December,2014 +Enterprise,Canada,VTT,1583,125.00,197875.00,25723.75,172151.25,189960.00,-17808.75,6/1/2014,6,June,2014 +Midmarket,Canada,VTT,1565,15.00,23475.00,3051.75,20423.25,15650.00,4773.25,10/1/2014,10,October,2014 +Enterprise,Canada,Amarilla,1659,125.00,207375.00,26958.75,180416.25,199080.00,-18663.75,1/1/2014,1,January,2014 +Government,France,Amarilla,1190,7.00,8330.00,1082.90,7247.10,5950.00,1297.10,6/1/2014,6,June,2014 +Channel Partners,Mexico,Amarilla,410,12.00,4920.00,639.60,4280.40,1230.00,3050.40,10/1/2014,10,October,2014 +Channel Partners,Germany,Amarilla,1770,12.00,21240.00,2761.20,18478.80,5310.00,13168.80,12/1/2013,12,December,2013 +Government,Mexico,Carretera,2579,20.00,51580.00,7221.20,44358.80,25790.00,18568.80,4/1/2014,4,April,2014 +Government,United States of America,Carretera,1743,20.00,34860.00,4880.40,29979.60,17430.00,12549.60,5/1/2014,5,May,2014 +Government,United States of America,Carretera,2996,7.00,20972.00,2936.08,18035.92,14980.00,3055.92,10/1/2013,10,October,2013 +Government,Germany,Carretera,280,7.00,1960.00,274.40,1685.60,1400.00,285.60,12/1/2014,12,December,2014 +Government,France,Montana,293,7.00,2051.00,287.14,1763.86,1465.00,298.86,2/1/2014,2,February,2014 +Government,United States of America,Montana,2996,7.00,20972.00,2936.08,18035.92,14980.00,3055.92,10/1/2013,10,October,2013 +Midmarket,Germany,Paseo,278,15.00,4170.00,583.80,3586.20,2780.00,806.20,2/1/2014,2,February,2014 +Government,Canada,Paseo,2428,20.00,48560.00,6798.40,41761.60,24280.00,17481.60,3/1/2014,3,March,2014 +Midmarket,United States of America,Paseo,1767,15.00,26505.00,3710.70,22794.30,17670.00,5124.30,9/1/2014,9,September,2014 +Channel Partners,France,Paseo,1393,12.00,16716.00,2340.24,14375.76,4179.00,10196.76,10/1/2014,10,October,2014 +Government,Germany,VTT,280,7.00,1960.00,274.40,1685.60,1400.00,285.60,12/1/2014,12,December,2014 +Channel Partners,France,Amarilla,1393,12.00,16716.00,2340.24,14375.76,4179.00,10196.76,10/1/2014,10,October,2014 +Channel Partners,United States of America,Amarilla,2015,12.00,24180.00,3385.20,20794.80,6045.00,14749.80,12/1/2013,12,December,2013 +Small Business,Mexico,Carretera,801,300.00,240300.00,33642.00,206658.00,200250.00,6408.00,7/1/2014,7,July,2014 +Enterprise,France,Carretera,1023,125.00,127875.00,17902.50,109972.50,122760.00,-12787.50,9/1/2013,9,September,2013 +Small Business,Canada,Carretera,1496,300.00,448800.00,62832.00,385968.00,374000.00,11968.00,10/1/2014,10,October,2014 +Small Business,United States of America,Carretera,1010,300.00,303000.00,42420.00,260580.00,252500.00,8080.00,10/1/2014,10,October,2014 +Midmarket,Germany,Carretera,1513,15.00,22695.00,3177.30,19517.70,15130.00,4387.70,11/1/2014,11,November,2014 +Midmarket,Canada,Carretera,2300,15.00,34500.00,4830.00,29670.00,23000.00,6670.00,12/1/2014,12,December,2014 +Enterprise,Mexico,Carretera,2821,125.00,352625.00,49367.50,303257.50,338520.00,-35262.50,12/1/2013,12,December,2013 +Government,Canada,Montana,2227.5,350.00,779625.00,109147.50,670477.50,579150.00,91327.50,1/1/2014,1,January,2014 +Government,Germany,Montana,1199,350.00,419650.00,58751.00,360899.00,311740.00,49159.00,4/1/2014,4,April,2014 +Government,Canada,Montana,200,350.00,70000.00,9800.00,60200.00,52000.00,8200.00,5/1/2014,5,May,2014 +Government,Canada,Montana,388,7.00,2716.00,380.24,2335.76,1940.00,395.76,9/1/2014,9,September,2014 +Government,Mexico,Montana,1727,7.00,12089.00,1692.46,10396.54,8635.00,1761.54,10/1/2013,10,October,2013 +Midmarket,Canada,Montana,2300,15.00,34500.00,4830.00,29670.00,23000.00,6670.00,12/1/2014,12,December,2014 +Government,Mexico,Paseo,260,20.00,5200.00,728.00,4472.00,2600.00,1872.00,2/1/2014,2,February,2014 +Midmarket,Canada,Paseo,2470,15.00,37050.00,5187.00,31863.00,24700.00,7163.00,9/1/2013,9,September,2013 +Midmarket,Canada,Paseo,1743,15.00,26145.00,3660.30,22484.70,17430.00,5054.70,10/1/2013,10,October,2013 +Channel Partners,United States of America,Paseo,2914,12.00,34968.00,4895.52,30072.48,8742.00,21330.48,10/1/2014,10,October,2014 +Government,France,Paseo,1731,7.00,12117.00,1696.38,10420.62,8655.00,1765.62,10/1/2014,10,October,2014 +Government,Canada,Paseo,700,350.00,245000.00,34300.00,210700.00,182000.00,28700.00,11/1/2014,11,November,2014 +Channel Partners,Canada,Paseo,2222,12.00,26664.00,3732.96,22931.04,6666.00,16265.04,11/1/2013,11,November,2013 +Government,United States of America,Paseo,1177,350.00,411950.00,57673.00,354277.00,306020.00,48257.00,11/1/2014,11,November,2014 +Government,France,Paseo,1922,350.00,672700.00,94178.00,578522.00,499720.00,78802.00,11/1/2013,11,November,2013 +Enterprise,Mexico,Velo,1575,125.00,196875.00,27562.50,169312.50,189000.00,-19687.50,2/1/2014,2,February,2014 +Government,United States of America,Velo,606,20.00,12120.00,1696.80,10423.20,6060.00,4363.20,4/1/2014,4,April,2014 +Small Business,United States of America,Velo,2460,300.00,738000.00,103320.00,634680.00,615000.00,19680.00,7/1/2014,7,July,2014 +Small Business,Canada,Velo,269,300.00,80700.00,11298.00,69402.00,67250.00,2152.00,10/1/2013,10,October,2013 +Small Business,Germany,Velo,2536,300.00,760800.00,106512.00,654288.00,634000.00,20288.00,11/1/2013,11,November,2013 +Government,Mexico,VTT,2903,7.00,20321.00,2844.94,17476.06,14515.00,2961.06,3/1/2014,3,March,2014 +Small Business,United States of America,VTT,2541,300.00,762300.00,106722.00,655578.00,635250.00,20328.00,8/1/2014,8,August,2014 +Small Business,Canada,VTT,269,300.00,80700.00,11298.00,69402.00,67250.00,2152.00,10/1/2013,10,October,2013 +Small Business,Canada,VTT,1496,300.00,448800.00,62832.00,385968.00,374000.00,11968.00,10/1/2014,10,October,2014 +Small Business,United States of America,VTT,1010,300.00,303000.00,42420.00,260580.00,252500.00,8080.00,10/1/2014,10,October,2014 +Government,France,VTT,1281,350.00,448350.00,62769.00,385581.00,333060.00,52521.00,12/1/2013,12,December,2013 +Small Business,Canada,Amarilla,888,300.00,266400.00,37296.00,229104.00,222000.00,7104.00,3/1/2014,3,March,2014 +Enterprise,United States of America,Amarilla,2844,125.00,355500.00,49770.00,305730.00,341280.00,-35550.00,5/1/2014,5,May,2014 +Channel Partners,France,Amarilla,2475,12.00,29700.00,4158.00,25542.00,7425.00,18117.00,8/1/2014,8,August,2014 +Midmarket,Canada,Amarilla,1743,15.00,26145.00,3660.30,22484.70,17430.00,5054.70,10/1/2013,10,October,2013 +Channel Partners,United States of America,Amarilla,2914,12.00,34968.00,4895.52,30072.48,8742.00,21330.48,10/1/2014,10,October,2014 +Government,France,Amarilla,1731,7.00,12117.00,1696.38,10420.62,8655.00,1765.62,10/1/2014,10,October,2014 +Government,Mexico,Amarilla,1727,7.00,12089.00,1692.46,10396.54,8635.00,1761.54,10/1/2013,10,October,2013 +Midmarket,Mexico,Amarilla,1870,15.00,28050.00,3927.00,24123.00,18700.00,5423.00,11/1/2013,11,November,2013 +Enterprise,France,Carretera,1174,125.00,146750.00,22012.50,124737.50,140880.00,-16142.50,8/1/2014,8,August,2014 +Enterprise,Germany,Carretera,2767,125.00,345875.00,51881.25,293993.75,332040.00,-38046.25,8/1/2014,8,August,2014 +Enterprise,Germany,Carretera,1085,125.00,135625.00,20343.75,115281.25,130200.00,-14918.75,10/1/2014,10,October,2014 +Small Business,Mexico,Montana,546,300.00,163800.00,24570.00,139230.00,136500.00,2730.00,10/1/2014,10,October,2014 +Government,Germany,Paseo,1158,20.00,23160.00,3474.00,19686.00,11580.00,8106.00,3/1/2014,3,March,2014 +Midmarket,Canada,Paseo,1614,15.00,24210.00,3631.50,20578.50,16140.00,4438.50,4/1/2014,4,April,2014 +Government,Mexico,Paseo,2535,7.00,17745.00,2661.75,15083.25,12675.00,2408.25,4/1/2014,4,April,2014 +Government,Mexico,Paseo,2851,350.00,997850.00,149677.50,848172.50,741260.00,106912.50,5/1/2014,5,May,2014 +Midmarket,Canada,Paseo,2559,15.00,38385.00,5757.75,32627.25,25590.00,7037.25,8/1/2014,8,August,2014 +Government,United States of America,Paseo,267,20.00,5340.00,801.00,4539.00,2670.00,1869.00,10/1/2013,10,October,2013 +Enterprise,Germany,Paseo,1085,125.00,135625.00,20343.75,115281.25,130200.00,-14918.75,10/1/2014,10,October,2014 +Midmarket,Germany,Paseo,1175,15.00,17625.00,2643.75,14981.25,11750.00,3231.25,10/1/2014,10,October,2014 +Government,United States of America,Paseo,2007,350.00,702450.00,105367.50,597082.50,521820.00,75262.50,11/1/2013,11,November,2013 +Government,Mexico,Paseo,2151,350.00,752850.00,112927.50,639922.50,559260.00,80662.50,11/1/2013,11,November,2013 +Channel Partners,United States of America,Paseo,914,12.00,10968.00,1645.20,9322.80,2742.00,6580.80,12/1/2014,12,December,2014 +Government,France,Paseo,293,20.00,5860.00,879.00,4981.00,2930.00,2051.00,12/1/2014,12,December,2014 +Channel Partners,Mexico,Velo,500,12.00,6000.00,900.00,5100.00,1500.00,3600.00,3/1/2014,3,March,2014 +Midmarket,France,Velo,2826,15.00,42390.00,6358.50,36031.50,28260.00,7771.50,5/1/2014,5,May,2014 +Enterprise,France,Velo,663,125.00,82875.00,12431.25,70443.75,79560.00,-9116.25,9/1/2014,9,September,2014 +Small Business,United States of America,Velo,2574,300.00,772200.00,115830.00,656370.00,643500.00,12870.00,11/1/2013,11,November,2013 +Enterprise,United States of America,Velo,2438,125.00,304750.00,45712.50,259037.50,292560.00,-33522.50,12/1/2013,12,December,2013 +Channel Partners,United States of America,Velo,914,12.00,10968.00,1645.20,9322.80,2742.00,6580.80,12/1/2014,12,December,2014 +Government,Canada,VTT,865.5,20.00,17310.00,2596.50,14713.50,8655.00,6058.50,7/1/2014,7,July,2014 +Midmarket,Germany,VTT,492,15.00,7380.00,1107.00,6273.00,4920.00,1353.00,7/1/2014,7,July,2014 +Government,United States of America,VTT,267,20.00,5340.00,801.00,4539.00,2670.00,1869.00,10/1/2013,10,October,2013 +Midmarket,Germany,VTT,1175,15.00,17625.00,2643.75,14981.25,11750.00,3231.25,10/1/2014,10,October,2014 +Enterprise,Canada,VTT,2954,125.00,369250.00,55387.50,313862.50,354480.00,-40617.50,11/1/2013,11,November,2013 +Enterprise,Germany,VTT,552,125.00,69000.00,10350.00,58650.00,66240.00,-7590.00,11/1/2014,11,November,2014 +Government,France,VTT,293,20.00,5860.00,879.00,4981.00,2930.00,2051.00,12/1/2014,12,December,2014 +Small Business,France,Amarilla,2475,300.00,742500.00,111375.00,631125.00,618750.00,12375.00,3/1/2014,3,March,2014 +Small Business,Mexico,Amarilla,546,300.00,163800.00,24570.00,139230.00,136500.00,2730.00,10/1/2014,10,October,2014 +Government,Mexico,Montana,1368,7.00,9576.00,1436.40,8139.60,6840.00,1299.60,2/1/2014,2,February,2014 +Government,Canada,Paseo,723,7.00,5061.00,759.15,4301.85,3615.00,686.85,4/1/2014,4,April,2014 +Channel Partners,United States of America,VTT,1806,12.00,21672.00,3250.80,18421.20,5418.00,13003.20,5/1/2014,5,May,2014 diff --git a/dotnet/samples/ConceptsV2/Resources/travelinfo.txt b/dotnet/samples/ConceptsV2/Resources/travelinfo.txt new file mode 100644 index 000000000000..21665c82198e --- /dev/null +++ b/dotnet/samples/ConceptsV2/Resources/travelinfo.txt @@ -0,0 +1,217 @@ +Invoice Booking Reference LMNOPQ Trip ID - 11110011111 +Passenger Name(s) +MARKS/SAM ALBERT Agent W2 + + +MICROSOFT CORPORATION 14820 NE 36TH STREET REDMOND WA US 98052 + +American Express Global Business Travel Microsoft Travel +14711 NE 29th Place, Suite 215 +Bellevue, WA 98007 +Phone: +1 (669) 210-8041 + + + + +BILLING CODE : 1010-10010110 +Invoice Information + + + + + + +Invoice Details +Ticket Number + + + + + + + +0277993883295 + + + + + + +Charges +Ticket Base Fare + + + + + + + +306.29 + +Airline Name + +ALASKA AIRLINES + +Ticket Tax Fare 62.01 + +Passenger Name Flight Details + +MARKS/SAM ALBERT +11 Sep 2023 ALASKA AIRLINES +0572 H Class +SEATTLE-TACOMA,WA/RALEIGH DURHAM,NC +13 Sep 2023 ALASKA AIRLINES +0491 M Class +RALEIGH DURHAM,NC/SEATTLE- TACOMA,WA + +Total (USD) Ticket Amount + +368.30 + +Credit Card Information +Charged to Card + + + +AX XXXXXXXXXXX4321 + + + +368.30 + + + + +Payment Details + + + +Charged by Airline +Total Invoice Charge + + + +USD + + + +368.30 +368.30 + +Monday 11 September 2023 + +10:05 AM + +Seattle (SEA) to Durham (RDU) +Airline Booking Ref: ABCXYZ + +Carrier: ALASKA AIRLINES + +Flight: AS 572 + +Status: Confirmed + +Operated By: ALASKA AIRLINES +Origin: Seattle, WA, Seattle-Tacoma International Apt (SEA) + +Departing: Monday 11 September 2023 at 10:05 AM Destination: Durham, Raleigh, Raleigh (RDU) Arriving: Monday 11 September 2023 at 06:15 PM +Additional Information + +Departure Terminal: Not Applicable + +Arrival Terminal: TERMINAL 2 + + +Class: ECONOMY +Aircraft Type: Boeing 737-900 +Meal Service: Not Applicable +Frequent Flyer Number: Not Applicable +Number of Stops: 0 +Greenhouse Gas Emissions: 560 kg CO2e / person + + +Distance: 2354 Miles Estimated Time: 05 hours 10 minutes +Seat: 24A + + +THE WESTIN RALEIGH DURHAM AP +Address: 3931 Macaw Street, Raleigh, NC, 27617, US +Phone: (1) 919-224-1400 Fax: (1) 919-224-1401 +Check In Date: Monday 11 September 2023 Check Out Date: Wednesday 13 September 2023 Number Of Nights: 2 +Rate: USD 280.00 per night may be subject to local taxes and service charges +Guaranteed to: AX XXXXXXXXXXX4321 + +Reference Number: 987654 +Additional Information +Membership ID: 123456789 +CANCEL PERMITTED UP TO 1 DAYS BEFORE CHECKIN + +Status: Confirmed + + +Corporate Id: Not Applicable + +Number Of Rooms: 1 + +Wednesday 13 September 2023 + +07:15 PM + +Durham (RDU) to Seattle (SEA) +Airline Booking Ref: ABCXYZ + +Carrier: ALASKA AIRLINES + +Flight: AS 491 + +Status: Confirmed + +Operated By: ALASKA AIRLINES +Origin: Durham, Raleigh, Raleigh (RDU) +Departing: Wednesday 13 September 2023 at 07:15 PM + + + +Departure Terminal: TERMINAL 2 + +Destination: Seattle, WA, Seattle-Tacoma International Apt (SEA) +Arriving: Wednesday 13 September 2023 at 09:59 PM Arrival Terminal: Not Applicable +Additional Information + + +Class: ECONOMY +Aircraft Type: Boeing 737-900 +Meal Service: Not Applicable +Frequent Flyer Number: Not Applicable +Number of Stops: 0 +Greenhouse Gas Emissions: 560 kg CO2e / person + + +Distance: 2354 Miles Estimated Time: 05 hours 44 minutes +Seat: 16A + + + +Greenhouse Gas Emissions +Total Greenhouse Gas Emissions for this trip is: 1120 kg CO2e / person +Air Fare Information + +Routing : ONLINE RESERVATION +Total Fare : USD 368.30 +Additional Messages +FOR 24X7 Travel Reservations Please Call 1-669-210-8041 Unable To Use Requested As Frequent Flyer Program Invalid Use Of Frequent Flyer Number 0123XYZ Please Contact Corresponding Frequent Travel Program Support Desk For Assistance +Trip Name-Trip From Seattle To Raleigh/Durham +This Ticket Is Nonrefundable. Changes Or Cancellations Must Be Made Prior To Scheduled Flight Departure +All Changes Must Be Made On Same Carrier And Will Be Subject To Service Fee And Difference In Airfare +******************************************************* +Please Be Advised That Certain Mandatory Hotel-Imposed Charges Including But Not Limited To Daily Resort Or Facility Fees May Be Applicable To Your Stay And Payable To The Hotel Operator At Check-Out From The Property. You May Wish To Inquire With The Hotel Before Your Trip Regarding The Existence And Amount Of Such Charges. +******************************************************* +Hotel Cancel Policies Vary Depending On The Property And Date. If You Have Questions Regarding Cancellation Fees Please Call The Travel Office. +Important Information +COVID-19 Updates: Click here to access Travel Vitals https://travelvitals.amexgbt.com for the latest information and advisories compiled by American Express Global Business Travel. + +Carbon Emissions: The total emissions value for this itinerary includes air travel only. Emissions for each individual flight are displayed in the flight details section. For more information on carbon emissions please refer to https://www.amexglobalbusinesstravel.com/sustainable-products-and-platforms. + +For important information regarding your booking in relation to the conditions applying to your booking, managing your booking and travel advisory, please refer to www.amexglobalbusinesstravel.com/booking-info. + +GBT Travel Services UK Limited (GBT UK) and its authorized sublicensees (including Ovation Travel Group and Egencia) use certain trademarks and service marks of American Express Company or its subsidiaries (American Express) in the American Express Global Business Travel and American Express Meetings & Events brands and in connection with its business for permitted uses only under a limited license from American Express (Licensed Marks). The Licensed Marks are trademarks or service marks of, and the property of, American Express. GBT UK is a subsidiary of Global Business Travel Group, Inc. (NYSE: GBTG). American Express holds a minority interest in GBTG, which operates as a separate company from American Express. diff --git a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs index f282a1ee4c88..8511387b85da 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs @@ -31,7 +31,7 @@ await OpenAIAssistantAgent.CreateAsync( { Instructions = HostInstructions, Name = HostName, - Model = this.Model, + ModelName = this.Model, }); // Initialize plugin and add to the agent's Kernel (same as direct Kernel usage). diff --git a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj index 628fdf2aa171..f8efab0ea0fd 100644 --- a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj +++ b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj @@ -38,6 +38,7 @@ + diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 50d7d9d3b351..4738f5bbe528 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -1,4 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -54,7 +55,7 @@ public static async Task CreateMessageAsync(AssistantClient client, string threa throw new KernelException($"Invalid message role: {message.Role}"); } - if (string.IsNullOrWhiteSpace(message.Content)) + if (message.Items.Count == 0) { return; } @@ -62,14 +63,44 @@ public static async Task CreateMessageAsync(AssistantClient client, string threa MessageCreationOptions options = new() { - //Role = message.Role.ToMessageRole(), // %%% BUG: ASSIGNABLE + //Role = message.Role.ToMessageRole(), // %%% BUG: ASSIGNABLE (Allow assistant or user) }; + if (message.Metadata != null) + { + foreach (var metadata in message.Metadata) + { + options.Metadata.Add(metadata.Key, metadata.Value?.ToString() ?? string.Empty); + } + } + await client.CreateMessageAsync( threadId, - [message.Content], // %%% + GetMessageContents(), options, cancellationToken).ConfigureAwait(false); + + IEnumerable GetMessageContents() + { + foreach (KernelContent content in message.Items) + { + if (content is TextContent textContent) + { + yield return MessageContent.FromText(content.ToString()); + } + else if (content is ImageContent imageContent) + { + yield return MessageContent.FromImageUrl( + imageContent.Uri != null ? + imageContent.Uri : + new Uri(Convert.ToBase64String(imageContent.Data?.ToArray() ?? []))); // %%% WUT A MESS - API BUG? + } + else if (content is FileReferenceContent fileContent) + { + options.Attachments.Add(new MessageCreationAttachment(fileContent.FileId, [new CodeInterpreterToolDefinition()])); // %%% WUT A MESS - TOOLS? + } + } + } } /// @@ -91,7 +122,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist if (!string.IsNullOrWhiteSpace(message.AssistantId) && !agentNames.TryGetValue(message.AssistantId, out assistantName)) { - Assistant assistant = await client.GetAssistantAsync(message.AssistantId).ConfigureAwait(false); // %%% CANCEL TOKEN + Assistant assistant = await client.GetAssistantAsync(message.AssistantId).ConfigureAwait(false); // %%% BUG CANCEL TOKEN if (!string.IsNullOrWhiteSpace(assistant.Name)) { agentNames.Add(assistant.Id, assistant.Name); @@ -148,9 +179,19 @@ public static async IAsyncEnumerable InvokeAsync( RunCreationOptions options = new() { - //InstructionsOverride = agent.Instructions, - //ParallelToolCallsEnabled = true, // %%% - //ResponseFormat = %%% + //AdditionalInstructions, // %%% NO ??? + //AdditionalMessages // %%% NO ??? + //InstructionsOverride = agent.Instructions, // %%% RUN OVERRIDE + //MaxCompletionTokens // %%% RUN OVERRIDE + //MaxPromptTokens // %%% RUN OVERRIDE + //ModelOverride, // %%% RUN OVERRIDE + //NucleusSamplingFactor // %%% RUN OVERRIDE + //ParallelToolCallsEnabled = true, // %%% RUN OVERRIDE + AGENT + //ResponseFormat = // %%% RUN OVERRIDE + //ToolConstraint // %%% RUN OVERRIDE + AGENT + //ToolsOverride // %%% RUN OVERRIDE + //Temperature = agent.Definition.Temperature, // %%% RUN OVERRIDE + //TruncationStrategy // %%% RUN OVERRIDE + AGENT }; options.ToolsOverride.AddRange(agent.Tools); @@ -198,7 +239,7 @@ public static async IAsyncEnumerable InvokeAsync( // Process tool output ToolOutput[] toolOutputs = GenerateToolOutputs(functionResults); - await client.SubmitToolOutputsToRunAsync(run, toolOutputs).ConfigureAwait(false); // %%% CANCEL TOKEN + await client.SubmitToolOutputsToRunAsync(run, toolOutputs).ConfigureAwait(false); // %%% BUG CANCEL TOKEN } if (logger.IsEnabled(LogLevel.Information)) // Avoid boxing if not enabled @@ -255,7 +296,7 @@ public static async IAsyncEnumerable InvokeAsync( foreach (MessageContent itemContent in message.Content) { - ChatMessageContent? content = null; + ChatMessageContent? content = null; // %%% ITEMS // Process text content if (!string.IsNullOrEmpty(itemContent.Text)) @@ -384,11 +425,11 @@ private static AnnotationContent GenerateAnnotationContent(TextAnnotation annota { string? fileId = null; - if (string.IsNullOrEmpty(annotation.OutputFileId)) + if (!string.IsNullOrEmpty(annotation.OutputFileId)) { fileId = annotation.OutputFileId; } - else if (string.IsNullOrEmpty(annotation.InputFileId)) + else if (!string.IsNullOrEmpty(annotation.InputFileId)) { fileId = annotation.InputFileId; } diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs index bce45b415321..45e78cb6dd34 100644 --- a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs @@ -22,34 +22,32 @@ internal static class OpenAIClientFactory /// public static OpenAIClient CreateClient(OpenAIConfiguration config) { - OpenAIClient client; - // Inspect options switch (config.Type) { - case OpenAIConfiguration.OpenAIConfigurationType.AzureOpenAIKey: - { - AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(config); - client = new AzureOpenAIClient(config.Endpoint, config.ApiKey!, clientOptions); - break; - } - case OpenAIConfiguration.OpenAIConfigurationType.AzureOpenAICredential: + case OpenAIConfiguration.OpenAIConfigurationType.AzureOpenAI: { AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(config); - client = new AzureOpenAIClient(config.Endpoint, config.Credentials!, clientOptions); - break; + + if (config.Credential is not null) + { + return new AzureOpenAIClient(config.Endpoint, config.Credential, clientOptions); + } + if (!string.IsNullOrEmpty(config.ApiKey)) + { + return new AzureOpenAIClient(config.Endpoint, config.ApiKey!, clientOptions); + } + + throw new KernelException($"Unsupported configuration type: {config.Type}"); } case OpenAIConfiguration.OpenAIConfigurationType.OpenAI: { OpenAIClientOptions clientOptions = CreateOpenAIClientOptions(config); - client = new OpenAIClient(config.ApiKey ?? SingleSpaceKey, clientOptions); - break; + return new OpenAIClient(config.ApiKey ?? SingleSpaceKey, clientOptions); } default: - throw new KernelException($"Unsupported configuration type: {config.Type}"); + throw new KernelException($"Unsupported configuration state: {config.Type}"); } - - return client; } private static AzureOpenAIClientOptions CreateAzureClientOptions(OpenAIConfiguration config) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 129f99642465..ca7fe42ae8e1 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -64,7 +64,7 @@ public static async Task CreateAsync( // Create the assistant AssistantCreationOptions assistantCreationOptions = CreateAssistantCreationOptions(definition); - Assistant model = await client.CreateAssistantAsync(definition.Model, assistantCreationOptions, cancellationToken).ConfigureAwait(false); + Assistant model = await client.CreateAssistantAsync(definition.ModelName, assistantCreationOptions, cancellationToken).ConfigureAwait(false); // Instantiate the agent return @@ -112,7 +112,7 @@ public static async Task RetrieveAsync( AssistantClient client = CreateClient(config); // Retrieve the assistant - Assistant model = await client.GetAssistantAsync(id).ConfigureAwait(false); // %%% CANCEL TOKEN + Assistant model = await client.GetAssistantAsync(id).ConfigureAwait(false); // %%% BUG CANCEL TOKEN // Instantiate the agent return @@ -127,7 +127,7 @@ public static async Task RetrieveAsync( /// /// The to monitor for cancellation requests. The default is . /// The thread identifier - public async Task CreateThreadAsync(CancellationToken cancellationToken = default) + public async Task CreateThreadAsync(CancellationToken cancellationToken = default) // %%% OPTIONS: MESSAGES / TOOL_RESOURCES { ThreadCreationOptions options = new(); // %%% AssistantThread thread = await this._client.CreateThreadAsync(options, cancellationToken).ConfigureAwait(false); @@ -201,7 +201,7 @@ public async Task DeleteAsync(CancellationToken cancellationToken = defaul /// The thread identifier /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. - public IAsyncEnumerable InvokeAsync( + public IAsyncEnumerable InvokeAsync( // %%% OPTIONS string threadId, CancellationToken cancellationToken = default) { @@ -266,9 +266,12 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod Description = model.Description, Instructions = model.Instructions, EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), - VectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.Single(), Metadata = model.Metadata, - Model = model.Model, + ModelName = model.Model, + EnableJsonResponse = model.ResponseFormat == AssistantResponseFormat.JsonObject, + NucleusSamplingFactor = model.NucleusSamplingFactor, + Temperature = model.Temperature, + VectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.Single(), }; private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAssistantDefinition definition) @@ -296,9 +299,9 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss Instructions = definition.Instructions, Name = definition.Name, ToolResources = toolResources, - // %%% ResponseFormat = - // %%% Temperature = - // %%% NucleusSamplingFactor = + ResponseFormat = definition.EnableJsonResponse ? AssistantResponseFormat.JsonObject : AssistantResponseFormat.Auto, + Temperature = definition.Temperature, + NucleusSamplingFactor = definition.NucleusSamplingFactor, }; if (definition.Metadata != null) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index 47fd333d348b..a656cd1e6dc2 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -11,7 +11,7 @@ public sealed class OpenAIAssistantDefinition /// /// Identifies the AI model targeted by the agent. /// - public string? Model { get; init; } + public string? ModelName { get; init; } /// /// The description of the assistant. @@ -38,14 +38,27 @@ public sealed class OpenAIAssistantDefinition /// public bool EnableCodeInterpreter { get; init; } + /// + /// Set if json response-format is enabled. + /// + public bool EnableJsonResponse { get; init; } + + /// + /// %%% + /// + public float? NucleusSamplingFactor { get; init; } + + /// + /// %%% + /// + public float? Temperature { get; init; } + /// /// Enables file-serach if specified. /// public string? VectorStoreId { get; init; } - // %%% ResponseFormat - // %%% Temperature - // %%% NucleusSamplingFactor + // %%% CODE INTERPRETER FILEIDS /// /// A set of up to 16 key/value pairs that can be attached to an agent, used for diff --git a/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs index 26bdefff975d..f03079f73a74 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs @@ -12,8 +12,7 @@ public sealed class OpenAIConfiguration { internal enum OpenAIConfigurationType { - AzureOpenAIKey, - AzureOpenAICredential, + AzureOpenAI, OpenAI, } @@ -31,24 +30,24 @@ public static OpenAIConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, Ht ApiKey = apiKey, Endpoint = endpoint, HttpClient = httpClient, - Type = OpenAIConfigurationType.AzureOpenAIKey, + Type = OpenAIConfigurationType.AzureOpenAI, }; /// /// %%% /// - /// + /// /// /// /// - public static OpenAIConfiguration ForAzureOpenAI(TokenCredential credentials, Uri endpoint, HttpClient? httpClient = null) => + public static OpenAIConfiguration ForAzureOpenAI(TokenCredential credential, Uri endpoint, HttpClient? httpClient = null) => // %%% VERIFY new() { - Credentials = credentials, + Credential = credential, Endpoint = endpoint, HttpClient = httpClient, - Type = OpenAIConfigurationType.AzureOpenAICredential, + Type = OpenAIConfigurationType.AzureOpenAI, }; /// @@ -88,7 +87,7 @@ public static OpenAIConfiguration ForOpenAI(string apiKey, string organizationId }; internal string? ApiKey { get; init; } - internal TokenCredential? Credentials { get; init; } + internal TokenCredential? Credential { get; init; } internal Uri? Endpoint { get; init; } internal HttpClient? HttpClient { get; init; } internal string? OrganizationId { get; init; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs index 555f3adfb7f3..cefb8d05a855 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs @@ -115,7 +115,7 @@ public OpenAIVectorStoreBuilder WithName(string name) /// /// /// - public async Task CreateAsync(CancellationToken cancellationToken) + public async Task CreateAsync(CancellationToken cancellationToken = default) { OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); VectorStoreClient client = openAIClient.GetVectorStoreClient(); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index a196d74dd74c..527fdc555135 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -33,7 +33,7 @@ public async Task VerifyOpenAIAssistantAgentCreationEmptyAsync() OpenAIAssistantDefinition definition = new() { - Model = "testmodel", + ModelName = "testmodel", }; this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentSimple); @@ -62,7 +62,7 @@ public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() OpenAIAssistantDefinition definition = new() { - Model = "testmodel", + ModelName = "testmodel", Name = "testname", Description = "testdescription", Instructions = "testinstructions", @@ -94,7 +94,7 @@ public async Task VerifyOpenAIAssistantAgentCreationEverythingAsync() OpenAIAssistantDefinition definition = new() { - Model = "testmodel", + ModelName = "testmodel", EnableCodeInterpreter = true, VectorStoreId = "#vs", Metadata = new Dictionary() { { "a", "1" } }, @@ -369,7 +369,7 @@ private Task CreateAgentAsync() OpenAIAssistantDefinition definition = new() { - Model = "testmodel", + ModelName = "testmodel", }; this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentSimple); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index 9c19372188ff..602eddd07704 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -20,7 +20,7 @@ public void VerifyOpenAIAssistantDefinitionInitialState() Assert.Null(definition.Id); Assert.Null(definition.Name); - Assert.Null(definition.Model); + Assert.Null(definition.ModelName); Assert.Null(definition.Instructions); Assert.Null(definition.Description); Assert.Null(definition.Metadata); @@ -39,7 +39,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() { Id = "testid", Name = "testname", - Model = "testmodel", + ModelName = "testmodel", Instructions = "testinstructions", Description = "testdescription", VectorStoreId = "#vs", @@ -49,7 +49,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Assert.Equal("testid", definition.Id); Assert.Equal("testname", definition.Name); - Assert.Equal("testmodel", definition.Model); + Assert.Equal("testmodel", definition.ModelName); Assert.Equal("testinstructions", definition.Instructions); Assert.Equal("testdescription", definition.Description); Assert.Equal("#vs", definition.VectorStoreId); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs similarity index 91% rename from dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs rename to dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs index 67672c17cd4e..ac7caad578ac 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs @@ -7,9 +7,9 @@ namespace SemanticKernel.Agents.UnitTests.OpenAI; /// -/// Unit testing of . +/// Unit testing of . /// -public class OpenAIAssistantConfigurationTests +public class OpenAIConfigurationTests { /// /// Verify initial state. @@ -39,4 +39,6 @@ public void VerifyOpenAIAssistantConfigurationAssignment() Assert.Equal("https://localhost/", config.Endpoint.ToString()); Assert.NotNull(config.HttpClient); } + + // %%% MORE } diff --git a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs index d274ac7706d7..fbea0341810c 100644 --- a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs @@ -85,7 +85,7 @@ await OpenAIAssistantAgent.CreateAsync( new() { Instructions = "Answer questions about the menu.", - Model = modelName, + ModelName = modelName, }); AgentGroupChat chat = new(); diff --git a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs index 6c4a85104d54..4e2ecf22537e 100644 --- a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs @@ -82,7 +82,7 @@ await OpenAIAssistantAgent.CreateAsync( new() { Instructions = "Answer questions about the menu.", - Model = modelName, + ModelName = modelName, }); AgentGroupChat chat = new(); From b417af03eb0c678c299c8d5653e4d6b7e0043ac6 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sat, 6 Jul 2024 15:53:22 -0700 Subject: [PATCH 009/121] Checkpoint --- .../OpenAI/Internal/AssistantThreadActions.cs | 66 ++++++++++++------- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 44 +++++++++++-- .../Agents/OpenAI/OpenAIAssistantChannel.cs | 2 +- .../OpenAI/OpenAIAssistantDefinition.cs | 19 ++++-- .../OpenAIAssistantExecutionSettings.cs | 30 +++++++++ .../OpenAIAssistantInvocationSettings.cs | 47 +++++++++++++ 6 files changed, 170 insertions(+), 38 deletions(-) create mode 100644 dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs create mode 100644 dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 4738f5bbe528..01da0ae44e09 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -91,9 +91,7 @@ IEnumerable GetMessageContents() else if (content is ImageContent imageContent) { yield return MessageContent.FromImageUrl( - imageContent.Uri != null ? - imageContent.Uri : - new Uri(Convert.ToBase64String(imageContent.Data?.ToArray() ?? []))); // %%% WUT A MESS - API BUG? + imageContent.Uri ?? new Uri(Convert.ToBase64String(imageContent.Data?.ToArray() ?? []))); // %%% WUT A MESS - API BUG? } else if (content is FileReferenceContent fileContent) { @@ -159,6 +157,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist /// The assistant agent to interact with the thread. /// The assistant client /// The thread identifier + /// Optional settings to utilize for the invocation /// The logger to utilize (might be agent or channel scoped) /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. @@ -166,6 +165,7 @@ public static async IAsyncEnumerable InvokeAsync( OpenAIAssistantAgent agent, AssistantClient client, string threadId, + OpenAIAssistantInvocationSettings? invocationSettings, ILogger logger, [EnumeratorCancellation] CancellationToken cancellationToken) { @@ -174,29 +174,10 @@ public static async IAsyncEnumerable InvokeAsync( throw new KernelException($"Agent Failure - {nameof(OpenAIAssistantAgent)} agent is deleted: {agent.Id}."); } + // Create run logger.LogDebug("[{MethodName}] Creating run for agent/thrad: {AgentId}/{ThreadId}", nameof(InvokeAsync), agent.Id, threadId); - RunCreationOptions options = - new() - { - //AdditionalInstructions, // %%% NO ??? - //AdditionalMessages // %%% NO ??? - //InstructionsOverride = agent.Instructions, // %%% RUN OVERRIDE - //MaxCompletionTokens // %%% RUN OVERRIDE - //MaxPromptTokens // %%% RUN OVERRIDE - //ModelOverride, // %%% RUN OVERRIDE - //NucleusSamplingFactor // %%% RUN OVERRIDE - //ParallelToolCallsEnabled = true, // %%% RUN OVERRIDE + AGENT - //ResponseFormat = // %%% RUN OVERRIDE - //ToolConstraint // %%% RUN OVERRIDE + AGENT - //ToolsOverride // %%% RUN OVERRIDE - //Temperature = agent.Definition.Temperature, // %%% RUN OVERRIDE - //TruncationStrategy // %%% RUN OVERRIDE + AGENT - }; - - options.ToolsOverride.AddRange(agent.Tools); - - // Create run + RunCreationOptions options = GenerateRunCreationOptions(agent, invocationSettings); ThreadRun run = await client.CreateRunAsync(threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); logger.LogInformation("[{MethodName}] Created run: {RunId}", nameof(InvokeAsync), run.Id); @@ -554,4 +535,41 @@ private static ToolOutput[] GenerateToolOutputs(FunctionResultContent[] function return toolOutputs; } + + private static RunCreationOptions GenerateRunCreationOptions(OpenAIAssistantAgent agent, OpenAIAssistantInvocationSettings? invocationSettings) + { + int? truncationMessageCount = ResolveExecutionSetting(invocationSettings?.TruncationMessageCount, agent.Definition.ExecutionSettings?.TruncationMessageCount); + + RunCreationOptions options = + new() + { + MaxCompletionTokens = ResolveExecutionSetting(invocationSettings?.MaxCompletionTokens, agent.Definition.ExecutionSettings?.MaxCompletionTokens), + MaxPromptTokens = ResolveExecutionSetting(invocationSettings?.MaxPromptTokens, agent.Definition.ExecutionSettings?.MaxPromptTokens), + ModelOverride = invocationSettings?.ModelName, + NucleusSamplingFactor = ResolveExecutionSetting(invocationSettings?.TopP, agent.Definition.TopP), + ParallelToolCallsEnabled = ResolveExecutionSetting(invocationSettings?.ParallelToolCallsEnabled, agent.Definition.ExecutionSettings?.ParallelToolCallsEnabled), + ResponseFormat = ResolveExecutionSetting(invocationSettings?.EnableJsonResponse, agent.Definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, + Temperature = ResolveExecutionSetting(invocationSettings?.Temperature, agent.Definition.Temperature), + //ToolConstraint // %%% RUN OVERRIDE + AGENT + TruncationStrategy = truncationMessageCount.HasValue ? RunTruncationStrategy.CreateLastMessagesStrategy(truncationMessageCount.Value) : null, + }; + + options.ToolsOverride.AddRange(agent.Tools); // %%% + + if (invocationSettings?.Metadata != null) + { + foreach (var metadata in invocationSettings.Metadata) + { + options.Metadata.Add(metadata.Key, metadata.Value ?? string.Empty); + } + } + + return options; + } + + private static TValue? ResolveExecutionSetting(TValue? setting, TValue? agentSetting) where TValue : struct + => + setting.HasValue && (!agentSetting.HasValue || !EqualityComparer.Default.Equals(setting.Value, agentSetting.Value)) ? + setting.Value : + null; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index ca7fe42ae8e1..6258bb6eed37 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -15,6 +16,8 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// public sealed class OpenAIAssistantAgent : KernelAgent { + private const string SettingsMetadataKey = "__settings"; + private readonly Assistant _assistant; private readonly AssistantClient _client; private readonly string[] _channelKeys; @@ -201,13 +204,26 @@ public async Task DeleteAsync(CancellationToken cancellationToken = defaul /// The thread identifier /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. - public IAsyncEnumerable InvokeAsync( // %%% OPTIONS + public IAsyncEnumerable InvokeAsync( + string threadId, + CancellationToken cancellationToken = default) + => this.InvokeAsync(threadId, settings: null, cancellationToken); + + /// + /// Invoke the assistant on the specified thread. + /// + /// The thread identifier + /// %%% + /// The to monitor for cancellation requests. The default is . + /// Asynchronous enumeration of messages. + public IAsyncEnumerable InvokeAsync( string threadId, + OpenAIAssistantInvocationSettings? settings, CancellationToken cancellationToken = default) { this.ThrowIfDeleted(); - return AssistantThreadActions.InvokeAsync(this, this._client, threadId, this.Logger, cancellationToken); + return AssistantThreadActions.InvokeAsync(this, this._client, threadId, settings, this.Logger, cancellationToken); } /// @@ -258,7 +274,15 @@ private OpenAIAssistantAgent( } private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant model) - => + { + OpenAIAssistantExecutionSettings? settings = null; + + if (model.Metadata.TryGetValue(SettingsMetadataKey, out string? settingsJson)) + { + settings = JsonSerializer.Deserialize(settingsJson); + } + + return new() { Id = model.Id, @@ -268,11 +292,13 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), Metadata = model.Metadata, ModelName = model.Model, - EnableJsonResponse = model.ResponseFormat == AssistantResponseFormat.JsonObject, - NucleusSamplingFactor = model.NucleusSamplingFactor, + EnableJsonResponse = model.ResponseFormat is not null && model.ResponseFormat == AssistantResponseFormat.JsonObject, + TopP = model.NucleusSamplingFactor, Temperature = model.Temperature, VectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.Single(), + ExecutionSettings = settings, }; + } private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAssistantDefinition definition) { @@ -301,7 +327,7 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss ToolResources = toolResources, ResponseFormat = definition.EnableJsonResponse ? AssistantResponseFormat.JsonObject : AssistantResponseFormat.Auto, Temperature = definition.Temperature, - NucleusSamplingFactor = definition.NucleusSamplingFactor, + NucleusSamplingFactor = definition.TopP, }; if (definition.Metadata != null) @@ -312,6 +338,12 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss } } + if (definition.ExecutionSettings != null) + { + string settingsJson = JsonSerializer.Serialize(definition.ExecutionSettings); + assistantCreationOptions.Metadata[SettingsMetadataKey] = settingsJson; + } + if (definition.EnableCodeInterpreter) { assistantCreationOptions.Tools.Add(new CodeInterpreterToolDefinition()); diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs index 19bbf4eb3294..17b47bc404a2 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs @@ -31,7 +31,7 @@ protected override IAsyncEnumerable InvokeAsync( { agent.ThrowIfDeleted(); - return AssistantThreadActions.InvokeAsync(agent, this._client, this._threadId, this.Logger, cancellationToken); + return AssistantThreadActions.InvokeAsync(agent, this._client, this._threadId, invocationSettings: null, this.Logger, cancellationToken); } /// diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index a656cd1e6dc2..72de12548365 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -4,7 +4,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// The data associated with an assistant's definition. +/// Defines an assistant. /// public sealed class OpenAIAssistantDefinition { @@ -44,15 +44,22 @@ public sealed class OpenAIAssistantDefinition public bool EnableJsonResponse { get; init; } /// - /// %%% + /// A set of up to 16 key/value pairs that can be attached to an agent, used for + /// storing additional information about that object in a structured format.Keys + /// may be up to 64 characters in length and values may be up to 512 characters in length. /// - public float? NucleusSamplingFactor { get; init; } + public IReadOnlyDictionary? Metadata { get; init; } /// /// %%% /// public float? Temperature { get; init; } + /// + /// %%% + /// + public float? TopP { get; init; } + /// /// Enables file-serach if specified. /// @@ -61,9 +68,7 @@ public sealed class OpenAIAssistantDefinition // %%% CODE INTERPRETER FILEIDS /// - /// A set of up to 16 key/value pairs that can be attached to an agent, used for - /// storing additional information about that object in a structured format.Keys - /// may be up to 64 characters in length and values may be up to 512 characters in length. + /// %%% /// - public IReadOnlyDictionary? Metadata { get; init; } + public OpenAIAssistantExecutionSettings? ExecutionSettings { get; init; } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs new file mode 100644 index 000000000000..c01996cd5f76 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// Defines agent execution settings. +/// +public class OpenAIAssistantExecutionSettings +{ + /// + /// %%% + /// + public int? MaxCompletionTokens { get; init; } + + /// + /// %%% + /// + public int? MaxPromptTokens { get; init; } + + /// + /// %%% + /// + public bool? ParallelToolCallsEnabled { get; init; } + + //ToolConstraint // %%% + + /// + /// %%% + /// + public int? TruncationMessageCount { get; init; } +} diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs new file mode 100644 index 000000000000..6183082f2a43 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// Defines per invocation execution settings. +/// +public sealed class OpenAIAssistantInvocationSettings : OpenAIAssistantExecutionSettings +{ + /// + /// Identifies the AI model targeted by the agent. + /// + public string? ModelName { get; init; } + + /// + /// Set if code_interpreter tool is enabled. + /// + public bool EnableCodeInterpreter { get; init; } + + /// + /// Set if file_search tool is enabled. + /// + public bool EnableFileSearch { get; init; } + + /// + /// Set if json response-format is enabled. + /// + public bool? EnableJsonResponse { get; init; } + + /// + /// %%% + /// + public float? Temperature { get; init; } + + /// + /// %%% + /// + public float? TopP { get; init; } + + /// + /// A set of up to 16 key/value pairs that can be attached to an agent, used for + /// storing additional information about that object in a structured format.Keys + /// may be up to 64 characters in length and values may be up to 512 characters in length. + /// + public IReadOnlyDictionary? Metadata { get; init; } +} From 5a3bb302f87607e6932db000d045d1b1b1194f4b Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sat, 6 Jul 2024 15:59:40 -0700 Subject: [PATCH 010/121] Unit-test fix --- dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index 527fdc555135..e9873085c79d 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -310,7 +310,7 @@ await OpenAIAssistantAgent.ListDefinitionsAsync( this.SetupResponses( HttpStatusCode.OK, ResponseContent.ListAgentsPageMore, - ResponseContent.ListAgentsPageMore); + ResponseContent.ListAgentsPageFinal); messages = await OpenAIAssistantAgent.ListDefinitionsAsync( From 366d3421ca3660e44abc1172fafb9305ac6ce71f Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sun, 7 Jul 2024 13:16:54 -0700 Subject: [PATCH 011/121] Settings update --- .../OpenAIAssistantExecutionSettings.cs | 8 +++++-- .../OpenAIAssistantInvocationSettings.cs | 24 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs index c01996cd5f76..d9352d9f91e0 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs @@ -4,7 +4,10 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Defines agent execution settings. /// -public class OpenAIAssistantExecutionSettings +/// +/// %%% +/// +public sealed class OpenAIAssistantExecutionSettings { /// /// %%% @@ -21,7 +24,8 @@ public class OpenAIAssistantExecutionSettings /// public bool? ParallelToolCallsEnabled { get; init; } - //ToolConstraint // %%% + //public ToolConstraint? RequiredTool { get; init; } %%% ENUM ??? + //public KernelFunction? RequiredToolFunction { get; init; } %%% PLUGIN ??? /// /// %%% diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs index 6183082f2a43..e684c07e6a57 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs @@ -6,7 +6,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Defines per invocation execution settings. /// -public sealed class OpenAIAssistantInvocationSettings : OpenAIAssistantExecutionSettings +public sealed class OpenAIAssistantInvocationSettings { /// /// Identifies the AI model targeted by the agent. @@ -28,6 +28,28 @@ public sealed class OpenAIAssistantInvocationSettings : OpenAIAssistantExecution /// public bool? EnableJsonResponse { get; init; } + /// + /// %%% + /// + public int? MaxCompletionTokens { get; init; } + + /// + /// %%% + /// + public int? MaxPromptTokens { get; init; } + + /// + /// %%% + /// + public bool? ParallelToolCallsEnabled { get; init; } + + //public ToolConstraint? RequiredTool { get; init; } %%% ENUM ??? + //public KernelFunction? RequiredToolFunction { get; init; } %%% PLUGIN ??? + + /// + /// %%% + /// + public int? TruncationMessageCount { get; init; } /// /// %%% /// From 0cdb397c0feb0427ac2034d7563646970d961bf7 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sun, 7 Jul 2024 13:53:08 -0700 Subject: [PATCH 012/121] Intermediate cleanup --- .../OpenAIAssistant_FileManipulation.cs | 12 +-- .../Agents/OpenAIAssistant_FileSearch.cs | 6 +- .../Step6_DependencyInjection.cs | 2 +- .../Step8_OpenAIAssistant.cs | 8 +- .../OpenAI/Internal/AssistantThreadActions.cs | 4 +- .../OpenAI/Internal/OpenAIClientFactory.cs | 6 +- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 6 +- .../OpenAI/OpenAIAssistantDefinition.cs | 11 ++- .../OpenAIAssistantExecutionSettings.cs | 17 ++-- .../OpenAIAssistantInvocationSettings.cs | 30 ++++-- .../src/Agents/OpenAI/OpenAIConfiguration.cs | 95 +++++++++++-------- dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs | 2 +- .../IntegrationTests/IntegrationTests.csproj | 5 + .../Agents/OpenAIAssistantAgentTests.cs | 2 +- .../samples/InternalUtilities/BaseTest.cs | 2 +- 15 files changed, 127 insertions(+), 81 deletions(-) diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs index 1124f385709d..e0fbb920e2e9 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs @@ -23,7 +23,7 @@ public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTe [Fact] public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() { - OpenAIClient rootClient = OpenAIClientFactory.CreateClient(GetOpenAIConfiguration()); + OpenAIClient rootClient = OpenAIClientFactory.CreateClient(GetOpenAIConfiguration()); // %%% HACK FileClient fileClient = rootClient.GetFileClient(); await using Stream fileStream = EmbeddedResource.ReadStream("sales.csv")!; @@ -33,7 +33,7 @@ await fileClient.UploadFileAsync( "sales.csv", FileUploadPurpose.Assistants); - //OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); %%% + //OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); // %%% USE THIS //OpenAIFileReference uploadFile = // await fileService.UploadContentAsync( // new BinaryContent(await EmbeddedResource.ReadAllAsync("sales.csv"), mimeType: "text/plain"), @@ -63,8 +63,8 @@ await OpenAIAssistantAgent.CreateAsync( finally { await agent.DeleteAsync(); - //await fileService.DeleteFileAsync(uploadFile.Id); %%% - await fileClient.DeleteFileAsync(fileInfo.Id); + //await fileService.DeleteFileAsync(uploadFile.Id); // %%% USE THIS + await fileClient.DeleteFileAsync(fileInfo.Id); // %%% HACK } // Local function to invoke agent and display the conversation messages. @@ -84,10 +84,10 @@ async Task InvokeAgentAsync(string input) foreach (AnnotationContent annotation in message.Items.OfType()) { - Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}"); + Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}"); // %%% HACK BinaryData fileData = await fileClient.DownloadFileAsync(annotation.FileId!); Console.WriteLine(Encoding.Default.GetString(fileData.ToArray())); - //BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!); %%% + //BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!); // %%% USE THIS //byte[] byteContent = fileContent.Data?.ToArray() ?? []; //Console.WriteLine(Encoding.Default.GetString(byteContent)); } diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs index e73ad5aa9378..aeb237984a83 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs @@ -23,7 +23,7 @@ public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseTest(out [Fact] public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync() { - OpenAIClient rootClient = OpenAIClientFactory.CreateClient(GetOpenAIConfiguration()); + OpenAIClient rootClient = OpenAIClientFactory.CreateClient(GetOpenAIConfiguration()); // %%% HACK FileClient fileClient = rootClient.GetFileClient(); Stream fileStream = EmbeddedResource.ReadStream("travelinfo.txt")!; // %%% USING @@ -40,7 +40,7 @@ await fileClient.UploadFileAsync( OpenAIVectorStore openAIStore = new(vectorStore.Id, GetOpenAIConfiguration()); - //OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); %%% + //OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); // %%% USE THIS //OpenAIFileReference uploadFile = // await fileService.UploadContentAsync(new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"), // new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants)); @@ -53,7 +53,7 @@ await OpenAIAssistantAgent.CreateAsync( new() { ModelName = this.Model, - VectorStoreId = vectorStore.Id, // %%% + VectorStoreId = vectorStore.Id, }); // Create a chat for agent interaction. diff --git a/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs b/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs index 24bcf8bd7c6b..2394686077b9 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs @@ -38,7 +38,7 @@ public async Task UseDependencyInjectionToCreateAgentAsync() if (this.UseOpenAIConfig) { - //serviceContainer.AddOpenAIChatCompletion( %%% + //serviceContainer.AddOpenAIChatCompletion( // %%% CONNECTOR IMPL // TestConfiguration.OpenAI.ChatModelId, // TestConfiguration.OpenAI.ApiKey); } diff --git a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs index 8511387b85da..458c79fd5347 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs @@ -26,7 +26,7 @@ public async Task UseSingleOpenAIAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)), // %%% MODES + config: GetOpenAIConfiguration(), new() { Instructions = HostInstructions, @@ -72,6 +72,12 @@ async Task InvokeAgentAsync(string input) } } + private OpenAIConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIConfiguration.ForOpenAI(this.ApiKey) : + OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + private sealed class MenuPlugin { public const string CorrelationIdArgument = "correlationId"; diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 01da0ae44e09..c2516fb3b494 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -550,11 +550,11 @@ private static RunCreationOptions GenerateRunCreationOptions(OpenAIAssistantAgen ParallelToolCallsEnabled = ResolveExecutionSetting(invocationSettings?.ParallelToolCallsEnabled, agent.Definition.ExecutionSettings?.ParallelToolCallsEnabled), ResponseFormat = ResolveExecutionSetting(invocationSettings?.EnableJsonResponse, agent.Definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, Temperature = ResolveExecutionSetting(invocationSettings?.Temperature, agent.Definition.Temperature), - //ToolConstraint // %%% RUN OVERRIDE + AGENT + //ToolConstraint // %%% TODO TruncationStrategy = truncationMessageCount.HasValue ? RunTruncationStrategy.CreateLastMessagesStrategy(truncationMessageCount.Value) : null, }; - options.ToolsOverride.AddRange(agent.Tools); // %%% + options.ToolsOverride.AddRange(agent.Tools); if (invocationSettings?.Metadata != null) { diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs index 45e78cb6dd34..0f6b88c5dfa1 100644 --- a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs @@ -16,10 +16,10 @@ internal static class OpenAIClientFactory private const string SingleSpaceKey = " "; /// - /// %%% + /// Creates an OpenAI client based on the provided configuration. /// - /// - /// + /// Configuration required to target a specific Open AI service + /// An initialized Open AI client public static OpenAIClient CreateClient(OpenAIConfiguration config) { // Inspect options diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 6258bb6eed37..5be170398163 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -23,7 +23,7 @@ public sealed class OpenAIAssistantAgent : KernelAgent private readonly string[] _channelKeys; /// - /// %%% + /// The assistant definition. /// public OpenAIAssistantDefinition Definition { get; private init; } @@ -34,7 +34,7 @@ public sealed class OpenAIAssistantAgent : KernelAgent public bool IsDeleted { get; private set; } /// - /// %%% + /// Defines polling behavior for run processing /// public RunPollingConfiguration Polling { get; } = new(); @@ -213,7 +213,7 @@ public IAsyncEnumerable InvokeAsync( /// Invoke the assistant on the specified thread. /// /// The thread identifier - /// %%% + /// Optional invocation settings /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. public IAsyncEnumerable InvokeAsync( diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index 72de12548365..7a206685a3ec 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -51,13 +51,18 @@ public sealed class OpenAIAssistantDefinition public IReadOnlyDictionary? Metadata { get; init; } /// - /// %%% + /// The sampling temperature to use, between 0 and 2. /// public float? Temperature { get; init; } /// - /// %%% + /// An alternative to sampling with temperature, called nucleus sampling, where the model + /// considers the results of the tokens with top_p probability mass. + /// So 0.1 means only the tokens comprising the top 10% probability mass are considered. /// + /// + /// Recommended to set this or temperature but not both. + /// public float? TopP { get; init; } /// @@ -68,7 +73,7 @@ public sealed class OpenAIAssistantDefinition // %%% CODE INTERPRETER FILEIDS /// - /// %%% + /// Default execution settings for each agent invocation. /// public OpenAIAssistantExecutionSettings? ExecutionSettings { get; init; } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs index d9352d9f91e0..6969310ad83c 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs @@ -2,33 +2,34 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// Defines agent execution settings. +/// Defines agent execution settings for each invocation. /// /// -/// %%% +/// These settings are persisted as a single entry of the agent's metadata with key: "__settings" /// public sealed class OpenAIAssistantExecutionSettings { /// - /// %%% + /// The maximum number of completion tokens that may be used over the course of the run. /// public int? MaxCompletionTokens { get; init; } /// - /// %%% + /// The maximum number of prompt tokens that may be used over the course of the run. /// public int? MaxPromptTokens { get; init; } /// - /// %%% + /// Enables parallel function calling during tool use. Enabled by default. + /// Use this property to disable. /// public bool? ParallelToolCallsEnabled { get; init; } - //public ToolConstraint? RequiredTool { get; init; } %%% ENUM ??? - //public KernelFunction? RequiredToolFunction { get; init; } %%% PLUGIN ??? + //public ToolConstraint? RequiredTool { get; init; } // %%% ENUM ??? + //public KernelFunction? RequiredToolFunction { get; init; } // %%% PLUGIN ??? /// - /// %%% + /// When set, the thread will be truncated to the N most recent messages in the thread. /// public int? TruncationMessageCount { get; init; } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs index e684c07e6a57..2e9c61eb05e3 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs @@ -4,12 +4,15 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// Defines per invocation execution settings. +/// Defines per invocation execution settings that override the assistant's default settings. /// +/// +/// Not applicable to usage. +/// public sealed class OpenAIAssistantInvocationSettings { /// - /// Identifies the AI model targeted by the agent. + /// Override the AI model targeted by the agent. /// public string? ModelName { get; init; } @@ -29,35 +32,42 @@ public sealed class OpenAIAssistantInvocationSettings public bool? EnableJsonResponse { get; init; } /// - /// %%% + /// The maximum number of completion tokens that may be used over the course of the run. /// public int? MaxCompletionTokens { get; init; } /// - /// %%% + /// The maximum number of prompt tokens that may be used over the course of the run. /// public int? MaxPromptTokens { get; init; } /// - /// %%% + /// Enables parallel function calling during tool use. Enabled by default. + /// Use this property to disable. /// public bool? ParallelToolCallsEnabled { get; init; } - //public ToolConstraint? RequiredTool { get; init; } %%% ENUM ??? - //public KernelFunction? RequiredToolFunction { get; init; } %%% PLUGIN ??? + //public ToolConstraint? RequiredTool { get; init; } // %%% ENUM ??? + //public KernelFunction? RequiredToolFunction { get; init; } // %%% PLUGIN ??? /// - /// %%% + /// When set, the thread will be truncated to the N most recent messages in the thread. /// public int? TruncationMessageCount { get; init; } + /// - /// %%% + /// The sampling temperature to use, between 0 and 2. /// public float? Temperature { get; init; } /// - /// %%% + /// An alternative to sampling with temperature, called nucleus sampling, where the model + /// considers the results of the tokens with top_p probability mass. + /// So 0.1 means only the tokens comprising the top 10% probability mass are considered. /// + /// + /// Recommended to set this or temperature but not both. + /// public float? TopP { get; init; } /// diff --git a/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs index f03079f73a74..54b7eed768aa 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs @@ -6,7 +6,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// Configuration for OpenAI services. +/// Configuration to target a specific Open AI service. /// public sealed class OpenAIConfiguration { @@ -23,15 +23,20 @@ internal enum OpenAIConfigurationType /// /// /// - public static OpenAIConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, HttpClient? httpClient = null) => - // %%% VERIFY - new() - { - ApiKey = apiKey, - Endpoint = endpoint, - HttpClient = httpClient, - Type = OpenAIConfigurationType.AzureOpenAI, - }; + public static OpenAIConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, HttpClient? httpClient = null) + { + Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); + Verify.NotNull(endpoint, nameof(endpoint)); + + return + new() + { + ApiKey = apiKey, + Endpoint = endpoint, + HttpClient = httpClient, + Type = OpenAIConfigurationType.AzureOpenAI, + }; + } /// /// %%% @@ -40,15 +45,20 @@ public static OpenAIConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, Ht /// /// /// - public static OpenAIConfiguration ForAzureOpenAI(TokenCredential credential, Uri endpoint, HttpClient? httpClient = null) => - // %%% VERIFY - new() - { - Credential = credential, - Endpoint = endpoint, - HttpClient = httpClient, - Type = OpenAIConfigurationType.AzureOpenAI, - }; + public static OpenAIConfiguration ForAzureOpenAI(TokenCredential credential, Uri endpoint, HttpClient? httpClient = null) + { + Verify.NotNull(credential, nameof(credential)); + Verify.NotNull(endpoint, nameof(endpoint)); + + return + new() + { + Credential = credential, + Endpoint = endpoint, + HttpClient = httpClient, + Type = OpenAIConfigurationType.AzureOpenAI, + }; + } /// /// %%% @@ -57,16 +67,19 @@ public static OpenAIConfiguration ForAzureOpenAI(TokenCredential credential, Uri /// /// /// - public static OpenAIConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, HttpClient? httpClient = null) => - // %%% VERIFY - new() - { - ApiKey = apiKey, - Endpoint = endpoint, - HttpClient = httpClient, - Type = OpenAIConfigurationType.OpenAI, - }; + public static OpenAIConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, HttpClient? httpClient = null) + { + Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); + return + new() + { + ApiKey = apiKey, + Endpoint = endpoint, + HttpClient = httpClient, + Type = OpenAIConfigurationType.OpenAI, + }; + } /// /// %%% /// @@ -75,16 +88,22 @@ public static OpenAIConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, /// /// /// - public static OpenAIConfiguration ForOpenAI(string apiKey, string organizationId, Uri? endpoint = null, HttpClient? httpClient = null) => - // %%% VERIFY - new() - { - ApiKey = apiKey, - Endpoint = endpoint, - HttpClient = httpClient, - OrganizationId = organizationId, - Type = OpenAIConfigurationType.OpenAI, - }; + public static OpenAIConfiguration ForOpenAI(string apiKey, string organizationId, Uri? endpoint = null, HttpClient? httpClient = null) + { + Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); + Verify.NotNullOrWhiteSpace(organizationId, nameof(organizationId)); + Verify.NotNull(endpoint, nameof(endpoint)); + + return + new() + { + ApiKey = apiKey, + Endpoint = endpoint, + HttpClient = httpClient, + OrganizationId = organizationId, + Type = OpenAIConfigurationType.OpenAI, + }; + } internal string? ApiKey { get; init; } internal TokenCredential? Credential { get; init; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs index 94d59cb88e7a..9fc092d5e29a 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs @@ -73,7 +73,7 @@ public async Task DeleteAsync(CancellationToken cancellationToken = defaul /// public async IAsyncEnumerable GetFilesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) { - await foreach (VectorStoreFileAssociation file in this._client.GetFileAssociationsAsync(this.VectorStoreId, ListOrder.NewestFirst, filter: null, cancellationToken).ConfigureAwait(false)) // %%% FILTER + await foreach (VectorStoreFileAssociation file in this._client.GetFileAssociationsAsync(this.VectorStoreId, ListOrder.NewestFirst, filter: null, cancellationToken).ConfigureAwait(false)) { yield return file.FileId; } diff --git a/dotnet/src/IntegrationTests/IntegrationTests.csproj b/dotnet/src/IntegrationTests/IntegrationTests.csproj index df5afa473ce7..3d1b93a71253 100644 --- a/dotnet/src/IntegrationTests/IntegrationTests.csproj +++ b/dotnet/src/IntegrationTests/IntegrationTests.csproj @@ -17,6 +17,7 @@ + @@ -156,4 +157,8 @@ Always + + + + \ No newline at end of file diff --git a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs index 4e2ecf22537e..d084b0fd0ed5 100644 --- a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs @@ -35,7 +35,7 @@ public sealed class OpenAIAssistantAgentTests [InlineData("What is the special soup?", "Clam Chowder")] public async Task OpenAIAssistantAgentTestAsync(string input, string expectedAnswerContains) { - OpenAISettings openAISettings = this._configuration.GetSection("OpenAI").Get(); + OpenAISettings openAISettings = this._configuration.GetSection("OpenAI").Get()!; Assert.NotNull(openAISettings); await this.ExecuteAgentAsync( diff --git a/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs b/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs index 671524865d33..56d6d147549b 100644 --- a/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs +++ b/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs @@ -42,7 +42,7 @@ protected Kernel CreateKernelWithChatCompletion() if (this.UseOpenAIConfig) { - //builder.AddOpenAIChatCompletion( // %%% + //builder.AddOpenAIChatCompletion( // %%% CONNECTOR // TestConfiguration.OpenAI.ChatModelId, // TestConfiguration.OpenAI.ApiKey); } From 192d59908900a1b8c0c294c82de5e03c6f877cfe Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sun, 7 Jul 2024 14:01:44 -0700 Subject: [PATCH 013/121] More file support --- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 22 ++++++++++++++----- .../OpenAI/OpenAIAssistantDefinition.cs | 7 ++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 5be170398163..1bd9c9080288 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -289,6 +289,7 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod Name = model.Name, Description = model.Description, Instructions = model.Instructions, + CodeInterpterFileIds = (IReadOnlyList?)(model.ToolResources?.CodeInterpreter?.FileIds), EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), Metadata = model.Metadata, ModelName = model.Model, @@ -303,18 +304,29 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAssistantDefinition definition) { bool enableFileSearch = !string.IsNullOrWhiteSpace(definition.VectorStoreId); + bool hasCodeInterpreterFiles = (definition.CodeInterpterFileIds?.Count ?? 0) > 0; ToolResources? toolResources = null; - if (enableFileSearch) + if (enableFileSearch || hasCodeInterpreterFiles) { toolResources = new ToolResources() { - FileSearch = new FileSearchToolResources() - { - VectorStoreIds = [definition.VectorStoreId!], - } + FileSearch = + enableFileSearch ? + new FileSearchToolResources() + { + VectorStoreIds = [definition.VectorStoreId!], + } : + null, + CodeInterpreter = + hasCodeInterpreterFiles ? + new CodeInterpreterToolResources() + { + FileIds = (IList)definition.CodeInterpterFileIds!, + } : + null, }; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index 7a206685a3ec..b2e17e5fb17d 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -33,6 +33,11 @@ public sealed class OpenAIAssistantDefinition /// public string? Name { get; init; } + /// + /// Optional file-ids made available to the code_interpreter tool. + /// + public IReadOnlyList? CodeInterpterFileIds { get; init; } + /// /// Set if code-interpreter is enabled. /// @@ -70,8 +75,6 @@ public sealed class OpenAIAssistantDefinition /// public string? VectorStoreId { get; init; } - // %%% CODE INTERPRETER FILEIDS - /// /// Default execution settings for each agent invocation. /// From 43e3579788f1b69d969706a6f1c38887e945e70d Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sun, 7 Jul 2024 14:17:17 -0700 Subject: [PATCH 014/121] File-service clean-up --- .../OpenAIAssistant_FileManipulation.cs | 38 ++++++------------- .../Agents/OpenAIAssistant_FileSearch.cs | 26 ++++--------- 2 files changed, 20 insertions(+), 44 deletions(-) diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs index e0fbb920e2e9..69b249801727 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs @@ -4,8 +4,7 @@ using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; -using OpenAI; -using OpenAI.Files; +using Microsoft.SemanticKernel.Connectors.OpenAI; using Resources; namespace Agents; @@ -23,21 +22,11 @@ public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTe [Fact] public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() { - OpenAIClient rootClient = OpenAIClientFactory.CreateClient(GetOpenAIConfiguration()); // %%% HACK - FileClient fileClient = rootClient.GetFileClient(); - - await using Stream fileStream = EmbeddedResource.ReadStream("sales.csv")!; - OpenAIFileInfo fileInfo = - await fileClient.UploadFileAsync( - fileStream, - "sales.csv", - FileUploadPurpose.Assistants); - - //OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); // %%% USE THIS - //OpenAIFileReference uploadFile = - // await fileService.UploadContentAsync( - // new BinaryContent(await EmbeddedResource.ReadAllAsync("sales.csv"), mimeType: "text/plain"), - // new OpenAIFileUploadExecutionSettings("sales.csv", OpenAIFilePurpose.Assistants)); + OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); + OpenAIFileReference uploadFile = + await fileService.UploadContentAsync( + new BinaryContent(await EmbeddedResource.ReadAllAsync("sales.csv"), mimeType: "text/plain"), + new OpenAIFileUploadExecutionSettings("sales.csv", OpenAIFilePurpose.Assistants)); // Define the agent OpenAIAssistantAgent agent = @@ -63,8 +52,7 @@ await OpenAIAssistantAgent.CreateAsync( finally { await agent.DeleteAsync(); - //await fileService.DeleteFileAsync(uploadFile.Id); // %%% USE THIS - await fileClient.DeleteFileAsync(fileInfo.Id); // %%% HACK + await fileService.DeleteFileAsync(uploadFile.Id); } // Local function to invoke agent and display the conversation messages. @@ -73,7 +61,7 @@ async Task InvokeAgentAsync(string input) chat.AddChatMessage( new(AuthorRole.User, content: null) { - Items = [new TextContent(input), new FileReferenceContent(fileInfo.Id)] + Items = [new TextContent(input), new FileReferenceContent(uploadFile.Id)] }); Console.WriteLine($"# {AuthorRole.User}: '{input}'"); @@ -84,12 +72,10 @@ async Task InvokeAgentAsync(string input) foreach (AnnotationContent annotation in message.Items.OfType()) { - Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}"); // %%% HACK - BinaryData fileData = await fileClient.DownloadFileAsync(annotation.FileId!); - Console.WriteLine(Encoding.Default.GetString(fileData.ToArray())); - //BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!); // %%% USE THIS - //byte[] byteContent = fileContent.Data?.ToArray() ?? []; - //Console.WriteLine(Encoding.Default.GetString(byteContent)); + Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}"); + BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!); + byte[] byteContent = fileContent.Data?.ToArray() ?? []; + Console.WriteLine(Encoding.Default.GetString(byteContent)); } } } diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs index aeb237984a83..1fa2ca2de885 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs @@ -3,10 +3,9 @@ using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; -using OpenAI.Files; -using OpenAI; -using Resources; +using Microsoft.SemanticKernel.Connectors.OpenAI; using OpenAI.VectorStores; +using Resources; namespace Agents; @@ -23,28 +22,18 @@ public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseTest(out [Fact] public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync() { - OpenAIClient rootClient = OpenAIClientFactory.CreateClient(GetOpenAIConfiguration()); // %%% HACK - FileClient fileClient = rootClient.GetFileClient(); - - Stream fileStream = EmbeddedResource.ReadStream("travelinfo.txt")!; // %%% USING - OpenAIFileInfo fileInfo = - await fileClient.UploadFileAsync( - fileStream, - "travelinfo.txt", - FileUploadPurpose.Assistants); + OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); + OpenAIFileReference uploadFile = + await fileService.UploadContentAsync(new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"), + new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants)); VectorStore vectorStore = await new OpenAIVectorStoreBuilder(GetOpenAIConfiguration()) - .AddFile(fileInfo.Id) + .AddFile(uploadFile.Id) .CreateAsync(); OpenAIVectorStore openAIStore = new(vectorStore.Id, GetOpenAIConfiguration()); - //OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); // %%% USE THIS - //OpenAIFileReference uploadFile = - // await fileService.UploadContentAsync(new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"), - // new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants)); - // Define the agent OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( @@ -70,6 +59,7 @@ await OpenAIAssistantAgent.CreateAsync( { await agent.DeleteAsync(); await openAIStore.DeleteAsync(); + await fileService.DeleteFileAsync(uploadFile.Id); } // Local function to invoke agent and display the conversation messages. From 417f2b4901cdd343265c3296f0961c28824f1997 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sun, 7 Jul 2024 14:24:48 -0700 Subject: [PATCH 015/121] Refine content generation --- .../OpenAI/Internal/AssistantThreadActions.cs | 90 ++++++------------- 1 file changed, 29 insertions(+), 61 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index c2516fb3b494..4481948dfa50 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -131,19 +131,9 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist foreach (MessageContent itemContent in message.Content) { - ChatMessageContent? content = null; + ChatMessageContent content = GenerateMessageContent(role, assistantName, itemContent); - if (!string.IsNullOrEmpty(itemContent.Text)) - { - content = GenerateTextMessageContent(assistantName, role, itemContent); - } - // Process image content - else if (itemContent.ImageFileId != null) - { - content = GenerateImageFileContent(assistantName, role, itemContent); - } - - if (content is not null) + if (content.Items.Count > 0) { yield return content; } @@ -277,20 +267,9 @@ public static async IAsyncEnumerable InvokeAsync( foreach (MessageContent itemContent in message.Content) { - ChatMessageContent? content = null; // %%% ITEMS - - // Process text content - if (!string.IsNullOrEmpty(itemContent.Text)) - { - content = GenerateTextMessageContent(agent.GetName(), role, itemContent); - } - // Process image content - else if (itemContent.ImageFileId != null) - { - content = GenerateImageFileContent(agent.GetName(), role, itemContent); - } + ChatMessageContent content = GenerateMessageContent(role, agent.Name, itemContent); - if (content is not null) + if (content.Items.Count > 0) { ++messageCount; @@ -425,42 +404,6 @@ private static AnnotationContent GenerateAnnotationContent(TextAnnotation annota }; } - private static ChatMessageContent GenerateImageFileContent(string agentName, AuthorRole role, MessageContent contentImage) - { - return - new ChatMessageContent( - role, - [ - new FileReferenceContent(contentImage.ImageFileId) - ]) - { - AuthorName = agentName, - }; - } - - private static ChatMessageContent? GenerateTextMessageContent(string agentName, AuthorRole role, MessageContent contentMessage) - { - ChatMessageContent? messageContent = null; - - string textContent = contentMessage.Text.Trim(); - - if (!string.IsNullOrWhiteSpace(textContent)) - { - messageContent = - new(role, textContent) - { - AuthorName = agentName - }; - - foreach (TextAnnotation annotation in contentMessage.TextAnnotations) - { - messageContent.Items.Add(GenerateAnnotationContent(annotation)); - } - } - - return messageContent; - } - private static ChatMessageContent GenerateCodeInterpreterContent(string agentName, string code) { return @@ -503,6 +446,31 @@ private static ChatMessageContent GenerateFunctionResultContent(string agentName return functionCallContent; } + private static ChatMessageContent GenerateMessageContent(AuthorRole role, string? assistantName, MessageContent itemContent) + { + ChatMessageContent content = + new(role, content: null) + { + AuthorName = assistantName, + }; + + if (!string.IsNullOrEmpty(itemContent.Text)) + { + content.Items.Add(new TextContent(itemContent.Text.Trim())); + foreach (TextAnnotation annotation in itemContent.TextAnnotations) + { + content.Items.Add(GenerateAnnotationContent(annotation)); + } + } + // Process image content + else if (itemContent.ImageFileId != null) + { + content.Items.Add(new FileReferenceContent(itemContent.ImageFileId)); + } + + return content; + } + private static Task[] ExecuteFunctionSteps(OpenAIAssistantAgent agent, FunctionCallContent[] functionSteps, CancellationToken cancellationToken) { Task[] functionTasks = new Task[functionSteps.Length]; From b1084297c32643021cb7d77165c407d9caf24995 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sun, 7 Jul 2024 14:28:26 -0700 Subject: [PATCH 016/121] Improve sample --- .../ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs index 69b249801727..3b57e8a1c911 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs @@ -35,6 +35,7 @@ await OpenAIAssistantAgent.CreateAsync( config: GetOpenAIConfiguration(), new() { + CodeInterpterFileIds = [uploadFile.Id], EnableCodeInterpreter = true, // Enable code-interpreter ModelName = this.Model, }); @@ -58,11 +59,7 @@ await OpenAIAssistantAgent.CreateAsync( // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - chat.AddChatMessage( - new(AuthorRole.User, content: null) - { - Items = [new TextContent(input), new FileReferenceContent(uploadFile.Id)] - }); + chat.AddChatMessage(new(AuthorRole.User, input)); Console.WriteLine($"# {AuthorRole.User}: '{input}'"); From ec182607fe176850361c627c39f48eb897df31e3 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sun, 7 Jul 2024 14:42:56 -0700 Subject: [PATCH 017/121] Improve thread creation --- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 91 ++++++++++++------- .../OpenAI/OpenAIAssistantDefinition.cs | 2 +- .../OpenAI/OpenAIThreadCreationSettings.cs | 37 ++++++++ 3 files changed, 98 insertions(+), 32 deletions(-) create mode 100644 dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 1bd9c9080288..3db35168aa03 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -130,9 +130,33 @@ public static async Task RetrieveAsync( /// /// The to monitor for cancellation requests. The default is . /// The thread identifier - public async Task CreateThreadAsync(CancellationToken cancellationToken = default) // %%% OPTIONS: MESSAGES / TOOL_RESOURCES + public Task CreateThreadAsync(CancellationToken cancellationToken = default) + => this.CreateThreadAsync(settings: null, cancellationToken); + + /// + /// Create a new assistant thread. + /// + /// %%% + /// The to monitor for cancellation requests. The default is . + /// The thread identifier + public async Task CreateThreadAsync(OpenAIThreadCreationSettings? settings, CancellationToken cancellationToken = default) { - ThreadCreationOptions options = new(); // %%% + ThreadCreationOptions options = + new() + { + ToolResources = GenerateToolResources(settings?.VectorStoreId, settings?.CodeInterpterFileIds), + }; + + //options.InitialMessages, // %%% TODO + + if (settings?.Metadata != null) + { + foreach (KeyValuePair item in settings.Metadata) + { + options.Metadata[item.Key] = item.Value; + } + } + AssistantThread thread = await this._client.CreateThreadAsync(options, cancellationToken).ConfigureAwait(false); return thread.Id; @@ -303,40 +327,13 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAssistantDefinition definition) { - bool enableFileSearch = !string.IsNullOrWhiteSpace(definition.VectorStoreId); - bool hasCodeInterpreterFiles = (definition.CodeInterpterFileIds?.Count ?? 0) > 0; - - ToolResources? toolResources = null; - - if (enableFileSearch || hasCodeInterpreterFiles) - { - toolResources = - new ToolResources() - { - FileSearch = - enableFileSearch ? - new FileSearchToolResources() - { - VectorStoreIds = [definition.VectorStoreId!], - } : - null, - CodeInterpreter = - hasCodeInterpreterFiles ? - new CodeInterpreterToolResources() - { - FileIds = (IList)definition.CodeInterpterFileIds!, - } : - null, - }; - } - AssistantCreationOptions assistantCreationOptions = new() { Description = definition.Description, Instructions = definition.Instructions, Name = definition.Name, - ToolResources = toolResources, + ToolResources = GenerateToolResources(definition.VectorStoreId, definition.EnableCodeInterpreter ? definition.CodeInterpterFileIds : null), ResponseFormat = definition.EnableJsonResponse ? AssistantResponseFormat.JsonObject : AssistantResponseFormat.Auto, Temperature = definition.Temperature, NucleusSamplingFactor = definition.TopP, @@ -361,7 +358,7 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss assistantCreationOptions.Tools.Add(new CodeInterpreterToolDefinition()); } - if (enableFileSearch) + if (!string.IsNullOrWhiteSpace(definition.VectorStoreId)) { assistantCreationOptions.Tools.Add(new FileSearchToolDefinition()); } @@ -369,6 +366,38 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss return assistantCreationOptions; } + private static ToolResources? GenerateToolResources(string? vectorStoreId, IReadOnlyList? codeInterpreterFileIds) + { + bool hasFileSearch = !string.IsNullOrWhiteSpace(vectorStoreId); + bool hasCodeInterpreterFiles = (codeInterpreterFileIds?.Count ?? 0) > 0; + + ToolResources? toolResources = null; + + if (hasFileSearch || hasCodeInterpreterFiles) + { + toolResources = + new ToolResources() + { + FileSearch = + hasFileSearch ? + new FileSearchToolResources() + { + VectorStoreIds = [vectorStoreId!], + } : + null, + CodeInterpreter = + hasCodeInterpreterFiles ? + new CodeInterpreterToolResources() + { + FileIds = (IList)codeInterpreterFileIds!, + } : + null, + }; + } + + return toolResources; + } + private static AssistantClient CreateClient(OpenAIConfiguration config) { OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index b2e17e5fb17d..e694a165e912 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -34,7 +34,7 @@ public sealed class OpenAIAssistantDefinition public string? Name { get; init; } /// - /// Optional file-ids made available to the code_interpreter tool. + /// Optional file-ids made available to the code_interpreter tool, if enabled. /// public IReadOnlyList? CodeInterpterFileIds { get; init; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs new file mode 100644 index 000000000000..3a5411d38cb1 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// Thread creation settings. +/// +public sealed class OpenAIThreadCreationSettings +{ + /// + /// Optional file-ids made available to the code_interpreter tool, if enabled. + /// + public IReadOnlyList? CodeInterpterFileIds { get; init; } + + /// + /// Set if code-interpreter is enabled. + /// + public bool EnableCodeInterpreter { get; init; } + + /// + /// Optional messages to initialize thread with.. + /// + public IReadOnlyList? Messages { get; init; } + + /// + /// Enables file-serach if specified. + /// + public string? VectorStoreId { get; init; } + + /// + /// A set of up to 16 key/value pairs that can be attached to an agent, used for + /// storing additional information about that object in a structured format.Keys + /// may be up to 64 characters in length and values may be up to 512 characters in length. + /// + public IReadOnlyDictionary? Metadata { get; init; } +} From 9e9f364dec107d32658dc8f77716e7f9489b1999 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Sun, 7 Jul 2024 14:49:15 -0700 Subject: [PATCH 018/121] Typo --- dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs | 2 +- dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index e694a165e912..53546d44fb5f 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -71,7 +71,7 @@ public sealed class OpenAIAssistantDefinition public float? TopP { get; init; } /// - /// Enables file-serach if specified. + /// Enables file-search if specified. /// public string? VectorStoreId { get; init; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs index 3a5411d38cb1..614ad64f6ba2 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs @@ -24,7 +24,7 @@ public sealed class OpenAIThreadCreationSettings public IReadOnlyList? Messages { get; init; } /// - /// Enables file-serach if specified. + /// Enables file-search if specified. /// public string? VectorStoreId { get; init; } From 6fd7c7ff64b0d1e2b6619738d1afe4d26a9cf074 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 08:05:58 -0700 Subject: [PATCH 019/121] Exclude V1 sample --- 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 dfcddf2920c5..f12e501aab3e 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -104,6 +104,7 @@ + From ec0aa8cfac9c712a505deb4fb8acd993045fdcff Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 08:25:14 -0700 Subject: [PATCH 020/121] Project clean-up --- .../Agents/OpenAIAssistantAgentTests.cs | 135 ------------------ .../IntegrationTests/IntegrationTests.csproj | 5 - 2 files changed, 140 deletions(-) delete mode 100644 dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs diff --git a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs deleted file mode 100644 index fbea0341810c..000000000000 --- a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using System.ComponentModel; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.ChatCompletion; -using SemanticKernel.IntegrationTests.TestSettings; -using Xunit; -using Xunit.Abstractions; - -namespace SemanticKernel.IntegrationTests.Agents.OpenAI; - -#pragma warning disable xUnit1004 // Contains test methods used in manual verification. Disable warning for this file only. - -public sealed class OpenAIAssistantAgentTests(ITestOutputHelper output) : IDisposable -{ - private readonly IKernelBuilder _kernelBuilder = Kernel.CreateBuilder(); - private readonly IConfigurationRoot _configuration = new ConfigurationBuilder() - .AddJsonFile(path: "testsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile(path: "testsettings.development.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables() - .AddUserSecrets() - .Build(); - - /// - /// Integration test for using function calling - /// and targeting Open AI services. - /// - [Theory(Skip = "OpenAI will often throttle requests. This test is for manual verification.")] - [InlineData("What is the special soup?", "Clam Chowder")] - public async Task OpenAIAssistantAgentTestAsync(string input, string expectedAnswerContains) - { - var openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); - Assert.NotNull(openAIConfiguration); - - await this.ExecuteAgentAsync( - new(openAIConfiguration.ApiKey), - openAIConfiguration.ModelId, - input, - expectedAnswerContains); - } - - /// - /// Integration test for using function calling - /// and targeting Azure OpenAI services. - /// - [Theory(Skip = "No supported endpoint configured.")] - [InlineData("What is the special soup?", "Clam Chowder")] - public async Task AzureOpenAIAssistantAgentAsync(string input, string expectedAnswerContains) - { - var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); - Assert.NotNull(azureOpenAIConfiguration); - - await this.ExecuteAgentAsync( - new(azureOpenAIConfiguration.ApiKey, azureOpenAIConfiguration.Endpoint), - azureOpenAIConfiguration.ChatDeploymentName!, - input, - expectedAnswerContains); - } - - private async Task ExecuteAgentAsync( - OpenAIAssistantConfiguration config, - string modelName, - string input, - string expected) - { - // Arrange - this._kernelBuilder.Services.AddSingleton(this._logger); - - Kernel kernel = this._kernelBuilder.Build(); - - KernelPlugin plugin = KernelPluginFactory.CreateFromType(); - kernel.Plugins.Add(plugin); - - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - kernel, - config, - new() - { - Instructions = "Answer questions about the menu.", - ModelName = modelName, - }); - - AgentGroupChat chat = new(); - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); - - // Act - StringBuilder builder = new(); - await foreach (var message in chat.InvokeAsync(agent)) - { - builder.Append(message.Content); - } - - // Assert - Assert.Contains(expected, builder.ToString(), StringComparison.OrdinalIgnoreCase); - } - - private readonly XunitLogger _logger = new(output); - private readonly RedirectOutput _testOutputHelper = new(output); - - public void Dispose() - { - this._logger.Dispose(); - this._testOutputHelper.Dispose(); - } - - public sealed class MenuPlugin - { - [KernelFunction, Description("Provides a list of specials from the menu.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")] - public string GetSpecials() - { - return @" -Special Soup: Clam Chowder -Special Salad: Cobb Salad -Special Drink: Chai Tea -"; - } - - [KernelFunction, Description("Provides the price of the requested menu item.")] - public string GetItemPrice( - [Description("The name of the menu item.")] - string menuItem) - { - return "$9.99"; - } - } -} diff --git a/dotnet/src/IntegrationTests/IntegrationTests.csproj b/dotnet/src/IntegrationTests/IntegrationTests.csproj index 3d1b93a71253..df5afa473ce7 100644 --- a/dotnet/src/IntegrationTests/IntegrationTests.csproj +++ b/dotnet/src/IntegrationTests/IntegrationTests.csproj @@ -17,7 +17,6 @@ - @@ -157,8 +156,4 @@ Always - - - - \ No newline at end of file From b2c172653191a5bd91f67785b97a1761251bf1b4 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 10:19:41 -0700 Subject: [PATCH 021/121] Dependency clean-up: IntegrationTests --- .../Agents/OpenAIAssistant_ChartMaker.cs | 6 ++--- .../Agents/OpenAIAssistant_CodeInterpreter.cs | 6 ++--- .../OpenAIAssistant_FileManipulation.cs | 6 ++--- .../Agents/OpenAIAssistant_FileSearch.cs | 6 ++--- .../Step8_OpenAIAssistant.cs | 6 ++--- .../OpenAI/Internal/OpenAIClientFactory.cs | 10 ++++----- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 10 ++++----- ...ation.cs => OpenAIServiceConfiguration.cs} | 22 +++++++++---------- dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs | 4 ++-- .../Agents/OpenAI/OpenAIVectorStoreBuilder.cs | 2 +- .../OpenAI/OpenAIAssistantAgentTests.cs | 6 ++--- .../OpenAI/OpenAIConfigurationTests.cs | 6 ++--- .../IntegrationTests/IntegrationTests.csproj | 4 ++-- .../Agents/OpenAIAssistantAgentTests.cs | 11 ++++------ 14 files changed, 51 insertions(+), 54 deletions(-) rename dotnet/src/Agents/OpenAI/{OpenAIConfiguration.cs => OpenAIServiceConfiguration.cs} (75%) diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs index 70b4fe3d99fe..63ed511742f8 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs @@ -83,9 +83,9 @@ async Task InvokeAgentAsync(string input) } } - private OpenAIConfiguration GetOpenAIConfiguration() + private OpenAIServiceConfiguration GetOpenAIConfiguration() => this.UseOpenAIConfig ? - OpenAIConfiguration.ForOpenAI(this.ApiKey) : - OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs index 5a5314935eca..646c1f244967 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs @@ -54,9 +54,9 @@ async Task InvokeAgentAsync(string input) } } - private OpenAIConfiguration GetOpenAIConfiguration() + private OpenAIServiceConfiguration GetOpenAIConfiguration() => this.UseOpenAIConfig ? - OpenAIConfiguration.ForOpenAI(this.ApiKey) : - OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs index 3b57e8a1c911..a0fa5a074694 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs @@ -78,9 +78,9 @@ async Task InvokeAgentAsync(string input) } } - private OpenAIConfiguration GetOpenAIConfiguration() + private OpenAIServiceConfiguration GetOpenAIConfiguration() => this.UseOpenAIConfig ? - OpenAIConfiguration.ForOpenAI(this.ApiKey) : - OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs index 1fa2ca2de885..9fef5a0c5830 100644 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs +++ b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs @@ -76,9 +76,9 @@ async Task InvokeAgentAsync(string input) } } - private OpenAIConfiguration GetOpenAIConfiguration() + private OpenAIServiceConfiguration GetOpenAIConfiguration() => this.UseOpenAIConfig ? - OpenAIConfiguration.ForOpenAI(this.ApiKey) : - OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs index 458c79fd5347..c2c1868f5edd 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs @@ -72,11 +72,11 @@ async Task InvokeAgentAsync(string input) } } - private OpenAIConfiguration GetOpenAIConfiguration() + private OpenAIServiceConfiguration GetOpenAIConfiguration() => this.UseOpenAIConfig ? - OpenAIConfiguration.ForOpenAI(this.ApiKey) : - OpenAIConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); private sealed class MenuPlugin { diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs index 0f6b88c5dfa1..d71973de5d7b 100644 --- a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs @@ -20,12 +20,12 @@ internal static class OpenAIClientFactory /// /// Configuration required to target a specific Open AI service /// An initialized Open AI client - public static OpenAIClient CreateClient(OpenAIConfiguration config) + public static OpenAIClient CreateClient(OpenAIServiceConfiguration config) { // Inspect options switch (config.Type) { - case OpenAIConfiguration.OpenAIConfigurationType.AzureOpenAI: + case OpenAIServiceConfiguration.OpenAIServiceType.AzureOpenAI: { AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(config); @@ -40,7 +40,7 @@ public static OpenAIClient CreateClient(OpenAIConfiguration config) throw new KernelException($"Unsupported configuration type: {config.Type}"); } - case OpenAIConfiguration.OpenAIConfigurationType.OpenAI: + case OpenAIServiceConfiguration.OpenAIServiceType.OpenAI: { OpenAIClientOptions clientOptions = CreateOpenAIClientOptions(config); return new OpenAIClient(config.ApiKey ?? SingleSpaceKey, clientOptions); @@ -50,7 +50,7 @@ public static OpenAIClient CreateClient(OpenAIConfiguration config) } } - private static AzureOpenAIClientOptions CreateAzureClientOptions(OpenAIConfiguration config) + private static AzureOpenAIClientOptions CreateAzureClientOptions(OpenAIServiceConfiguration config) { AzureOpenAIClientOptions options = new() @@ -64,7 +64,7 @@ private static AzureOpenAIClientOptions CreateAzureClientOptions(OpenAIConfigura return options; } - private static OpenAIClientOptions CreateOpenAIClientOptions(OpenAIConfiguration config) + private static OpenAIClientOptions CreateOpenAIClientOptions(OpenAIServiceConfiguration config) { OpenAIClientOptions options = new() diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 3db35168aa03..39dbd32ebe22 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -53,7 +53,7 @@ public sealed class OpenAIAssistantAgent : KernelAgent /// An instance public static async Task CreateAsync( Kernel kernel, - OpenAIConfiguration config, + OpenAIServiceConfiguration config, OpenAIAssistantDefinition definition, CancellationToken cancellationToken = default) { @@ -84,7 +84,7 @@ public static async Task CreateAsync( /// The to monitor for cancellation requests. The default is . /// An list of objects. public static async IAsyncEnumerable ListDefinitionsAsync( - OpenAIConfiguration config, + OpenAIServiceConfiguration config, [EnumeratorCancellation] CancellationToken cancellationToken = default) { // Create the client @@ -107,7 +107,7 @@ public static async IAsyncEnumerable ListDefinitionsA /// An instance public static async Task RetrieveAsync( Kernel kernel, - OpenAIConfiguration config, + OpenAIServiceConfiguration config, string id, CancellationToken cancellationToken = default) { @@ -398,13 +398,13 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss return toolResources; } - private static AssistantClient CreateClient(OpenAIConfiguration config) + private static AssistantClient CreateClient(OpenAIServiceConfiguration config) { OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); return openAIClient.GetAssistantClient(); } - private static IEnumerable DefineChannelKeys(OpenAIConfiguration config) + private static IEnumerable DefineChannelKeys(OpenAIServiceConfiguration config) { // Distinguish from other channel types. yield return typeof(AgentChannel).FullName!; diff --git a/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs similarity index 75% rename from dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs rename to dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs index 54b7eed768aa..66b1c97952f9 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIConfiguration.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs @@ -8,9 +8,9 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Configuration to target a specific Open AI service. /// -public sealed class OpenAIConfiguration +public sealed class OpenAIServiceConfiguration { - internal enum OpenAIConfigurationType + internal enum OpenAIServiceType { AzureOpenAI, OpenAI, @@ -23,7 +23,7 @@ internal enum OpenAIConfigurationType /// /// /// - public static OpenAIConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, HttpClient? httpClient = null) + public static OpenAIServiceConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, HttpClient? httpClient = null) { Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); Verify.NotNull(endpoint, nameof(endpoint)); @@ -34,7 +34,7 @@ public static OpenAIConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, Ht ApiKey = apiKey, Endpoint = endpoint, HttpClient = httpClient, - Type = OpenAIConfigurationType.AzureOpenAI, + Type = OpenAIServiceType.AzureOpenAI, }; } @@ -45,7 +45,7 @@ public static OpenAIConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, Ht /// /// /// - public static OpenAIConfiguration ForAzureOpenAI(TokenCredential credential, Uri endpoint, HttpClient? httpClient = null) + public static OpenAIServiceConfiguration ForAzureOpenAI(TokenCredential credential, Uri endpoint, HttpClient? httpClient = null) { Verify.NotNull(credential, nameof(credential)); Verify.NotNull(endpoint, nameof(endpoint)); @@ -56,7 +56,7 @@ public static OpenAIConfiguration ForAzureOpenAI(TokenCredential credential, Uri Credential = credential, Endpoint = endpoint, HttpClient = httpClient, - Type = OpenAIConfigurationType.AzureOpenAI, + Type = OpenAIServiceType.AzureOpenAI, }; } @@ -67,7 +67,7 @@ public static OpenAIConfiguration ForAzureOpenAI(TokenCredential credential, Uri /// /// /// - public static OpenAIConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, HttpClient? httpClient = null) + public static OpenAIServiceConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, HttpClient? httpClient = null) { Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); @@ -77,7 +77,7 @@ public static OpenAIConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, ApiKey = apiKey, Endpoint = endpoint, HttpClient = httpClient, - Type = OpenAIConfigurationType.OpenAI, + Type = OpenAIServiceType.OpenAI, }; } /// @@ -88,7 +88,7 @@ public static OpenAIConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, /// /// /// - public static OpenAIConfiguration ForOpenAI(string apiKey, string organizationId, Uri? endpoint = null, HttpClient? httpClient = null) + public static OpenAIServiceConfiguration ForOpenAI(string apiKey, string organizationId, Uri? endpoint = null, HttpClient? httpClient = null) { Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); Verify.NotNullOrWhiteSpace(organizationId, nameof(organizationId)); @@ -101,7 +101,7 @@ public static OpenAIConfiguration ForOpenAI(string apiKey, string organizationId Endpoint = endpoint, HttpClient = httpClient, OrganizationId = organizationId, - Type = OpenAIConfigurationType.OpenAI, + Type = OpenAIServiceType.OpenAI, }; } @@ -110,5 +110,5 @@ public static OpenAIConfiguration ForOpenAI(string apiKey, string organizationId internal Uri? Endpoint { get; init; } internal HttpClient? HttpClient { get; init; } internal string? OrganizationId { get; init; } - internal OpenAIConfigurationType Type { get; init; } + internal OpenAIServiceType Type { get; init; } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs index 9fc092d5e29a..74714fd66e02 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs @@ -26,7 +26,7 @@ public sealed class OpenAIVectorStore /// /// /// - public static IAsyncEnumerable GetVectorStoresAsync(OpenAIConfiguration config, CancellationToken cancellationToken = default) + public static IAsyncEnumerable GetVectorStoresAsync(OpenAIServiceConfiguration config, CancellationToken cancellationToken = default) { OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); VectorStoreClient client = openAIClient.GetVectorStoreClient(); @@ -39,7 +39,7 @@ public static IAsyncEnumerable GetVectorStoresAsync(OpenAIConfigura /// /// /// - public OpenAIVectorStore(string vectorStoreId, OpenAIConfiguration config) + public OpenAIVectorStore(string vectorStoreId, OpenAIServiceConfiguration config) { OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); this._client = openAIClient.GetVectorStoreClient(); diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs index cefb8d05a855..ac2c23293d73 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs @@ -11,7 +11,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// %%% /// -public sealed class OpenAIVectorStoreBuilder(OpenAIConfiguration config) +public sealed class OpenAIVectorStoreBuilder(OpenAIServiceConfiguration config) { private string? _name; private FileChunkingStrategy? _chunkingStrategy; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index e9873085c79d..01285e2372a2 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -381,10 +381,10 @@ private Task CreateAgentAsync() definition); } - private OpenAIConfiguration CreateTestConfiguration(bool targetAzure = false) + private OpenAIServiceConfiguration CreateTestConfiguration(bool targetAzure = false) => targetAzure ? - OpenAIConfiguration.ForAzureOpenAI(apiKey: "fakekey", endpoint: new Uri("https://localhost"), this._httpClient) : - OpenAIConfiguration.ForOpenAI(apiKey: "fakekey", endpoint: null, this._httpClient); + OpenAIServiceConfiguration.ForAzureOpenAI(apiKey: "fakekey", endpoint: new Uri("https://localhost"), this._httpClient) : + OpenAIServiceConfiguration.ForOpenAI(apiKey: "fakekey", endpoint: null, this._httpClient); private void SetupResponse(HttpStatusCode statusCode, string content) { diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs index ac7caad578ac..14237eebcd19 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs @@ -7,7 +7,7 @@ namespace SemanticKernel.Agents.UnitTests.OpenAI; /// -/// Unit testing of . +/// Unit testing of . /// public class OpenAIConfigurationTests { @@ -17,7 +17,7 @@ public class OpenAIConfigurationTests [Fact] public void VerifyOpenAIAssistantConfigurationInitialState() { - OpenAIConfiguration config = OpenAIConfiguration.ForOpenAI(apiKey: "testkey"); + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: "testkey"); Assert.Equal("testkey", config.ApiKey); Assert.Null(config.Endpoint); @@ -32,7 +32,7 @@ public void VerifyOpenAIAssistantConfigurationAssignment() { using HttpClient client = new(); - OpenAIConfiguration config = OpenAIConfiguration.ForOpenAI(apiKey: "testkey", endpoint: new Uri("https://localhost"), client); + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: "testkey", endpoint: new Uri("https://localhost"), client); Assert.Equal("testkey", config.ApiKey); Assert.NotNull(config.Endpoint); diff --git a/dotnet/src/IntegrationTests/IntegrationTests.csproj b/dotnet/src/IntegrationTests/IntegrationTests.csproj index df5afa473ce7..4e3089d00432 100644 --- a/dotnet/src/IntegrationTests/IntegrationTests.csproj +++ b/dotnet/src/IntegrationTests/IntegrationTests.csproj @@ -60,8 +60,8 @@ - - + diff --git a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs index d084b0fd0ed5..7e34c4ebe65e 100644 --- a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs @@ -11,9 +11,6 @@ using SemanticKernel.IntegrationTests.TestSettings; using Xunit; -using OpenAIConfiguration = Microsoft.SemanticKernel.Agents.OpenAI.OpenAIConfiguration; -using OpenAISettings = SemanticKernel.IntegrationTests.TestSettings.OpenAIConfiguration; - namespace SemanticKernel.IntegrationTests.Agents.OpenAI; #pragma warning disable xUnit1004 // Contains test methods used in manual verification. Disable warning for this file only. @@ -35,11 +32,11 @@ public sealed class OpenAIAssistantAgentTests [InlineData("What is the special soup?", "Clam Chowder")] public async Task OpenAIAssistantAgentTestAsync(string input, string expectedAnswerContains) { - OpenAISettings openAISettings = this._configuration.GetSection("OpenAI").Get()!; + OpenAIConfiguration openAISettings = this._configuration.GetSection("OpenAI").Get()!; Assert.NotNull(openAISettings); await this.ExecuteAgentAsync( - OpenAIConfiguration.ForOpenAI(openAISettings.ApiKey), + OpenAIServiceConfiguration.ForOpenAI(openAISettings.ApiKey), openAISettings.ModelId, input, expectedAnswerContains); @@ -57,14 +54,14 @@ public async Task AzureOpenAIAssistantAgentAsync(string input, string expectedAn Assert.NotNull(azureOpenAIConfiguration); await this.ExecuteAgentAsync( - OpenAIConfiguration.ForAzureOpenAI(azureOpenAIConfiguration.ApiKey, new Uri(azureOpenAIConfiguration.Endpoint)), + OpenAIServiceConfiguration.ForAzureOpenAI(azureOpenAIConfiguration.ApiKey, new Uri(azureOpenAIConfiguration.Endpoint)), azureOpenAIConfiguration.ChatDeploymentName!, input, expectedAnswerContains); } private async Task ExecuteAgentAsync( - OpenAIConfiguration config, + OpenAIServiceConfiguration config, string modelName, string input, string expected) From 7e4cd3984fb260862cc521635e541f7b4105077c Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 10:25:28 -0700 Subject: [PATCH 022/121] Fix path case for unbuntu --- dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj index f8efab0ea0fd..a4cd9b8b9f57 100644 --- a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj +++ b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj @@ -19,7 +19,7 @@ - + From e0a33c048f9de12ec0282f6b69d719c13c3d91e5 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 10:32:56 -0700 Subject: [PATCH 023/121] Comments and orgid clean-up --- .../OpenAI/Internal/OpenAIClientFactory.cs | 5 -- .../OpenAI/OpenAIServiceConfiguration.cs | 52 +++++-------------- .../src/Http/HttpHeaderConstant.cs | 3 -- 3 files changed, 12 insertions(+), 48 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs index d71973de5d7b..ddfd8aef8c31 100644 --- a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs @@ -73,11 +73,6 @@ private static OpenAIClientOptions CreateOpenAIClientOptions(OpenAIServiceConfig Endpoint = config.Endpoint ?? config.HttpClient?.BaseAddress, }; - if (!string.IsNullOrWhiteSpace(config.OrganizationId)) - { - options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.OpenAIOrganizationId, config.OrganizationId!), PipelinePosition.PerCall); - } - ConfigureClientOptions(config.HttpClient, options); return options; diff --git a/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs index 66b1c97952f9..5b93f74fb87b 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs @@ -17,12 +17,11 @@ internal enum OpenAIServiceType } /// - /// %%% + /// Produce a that targets an Azure OpenAI endpoint using an API key. /// - /// - /// - /// - /// + /// The API key + /// The service endpoint + /// Custom for HTTP requests. public static OpenAIServiceConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, HttpClient? httpClient = null) { Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); @@ -39,12 +38,11 @@ public static OpenAIServiceConfiguration ForAzureOpenAI(string apiKey, Uri endpo } /// - /// %%% + /// Produce a that targets an Azure OpenAI endpoint using an token credentials. /// - /// - /// - /// - /// + /// The credentials + /// The service endpoint + /// Custom for HTTP requests. public static OpenAIServiceConfiguration ForAzureOpenAI(TokenCredential credential, Uri endpoint, HttpClient? httpClient = null) { Verify.NotNull(credential, nameof(credential)); @@ -61,12 +59,11 @@ public static OpenAIServiceConfiguration ForAzureOpenAI(TokenCredential credenti } /// - /// %%% + /// Produce a that targets OpenAI services using an API key. /// - /// - /// - /// - /// + /// The API key + /// An optional endpoint + /// Custom for HTTP requests. public static OpenAIServiceConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, HttpClient? httpClient = null) { Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); @@ -80,35 +77,10 @@ public static OpenAIServiceConfiguration ForOpenAI(string apiKey, Uri? endpoint Type = OpenAIServiceType.OpenAI, }; } - /// - /// %%% - /// - /// - /// - /// - /// - /// - public static OpenAIServiceConfiguration ForOpenAI(string apiKey, string organizationId, Uri? endpoint = null, HttpClient? httpClient = null) - { - Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); - Verify.NotNullOrWhiteSpace(organizationId, nameof(organizationId)); - Verify.NotNull(endpoint, nameof(endpoint)); - - return - new() - { - ApiKey = apiKey, - Endpoint = endpoint, - HttpClient = httpClient, - OrganizationId = organizationId, - Type = OpenAIServiceType.OpenAI, - }; - } internal string? ApiKey { get; init; } internal TokenCredential? Credential { get; init; } internal Uri? Endpoint { get; init; } internal HttpClient? HttpClient { get; init; } - internal string? OrganizationId { get; init; } internal OpenAIServiceType Type { get; init; } } diff --git a/dotnet/src/InternalUtilities/src/Http/HttpHeaderConstant.cs b/dotnet/src/InternalUtilities/src/Http/HttpHeaderConstant.cs index a0d0dea0b50a..db45523ee3bd 100644 --- a/dotnet/src/InternalUtilities/src/Http/HttpHeaderConstant.cs +++ b/dotnet/src/InternalUtilities/src/Http/HttpHeaderConstant.cs @@ -13,9 +13,6 @@ public static class Names { /// HTTP header name to use to include the Semantic Kernel package version in all HTTP requests issued by Semantic Kernel. public static string SemanticKernelVersion => "Semantic-Kernel-Version"; - - /// HTTP header name to use to include the Open AI organization identifier. - public static string OpenAIOrganizationId => "OpenAI-Organization"; } public static class Values From a042a8554d148b5a97d15ed4d6fc695daa5b1197 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 10:47:49 -0700 Subject: [PATCH 024/121] Comments --- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 6 +-- dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs | 40 ++++++++-------- .../Agents/OpenAI/OpenAIVectorStoreBuilder.cs | 48 ++++++++++--------- 3 files changed, 48 insertions(+), 46 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 39dbd32ebe22..8c7ee432246e 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -47,7 +47,7 @@ public sealed class OpenAIAssistantAgent : KernelAgent /// Define a new . /// /// The containing services, plugins, and other state for use throughout the operation. - /// Configuration for accessing the Assistants API service, such as the api-key. + /// Configuration for accessing the Assistants API service. /// The assistant definition. /// The to monitor for cancellation requests. The default is . /// An instance @@ -80,7 +80,7 @@ public static async Task CreateAsync( /// /// Retrieve a list of assistant definitions: . /// - /// Configuration for accessing the Assistants API service, such as the api-key. + /// Configuration for accessing the Assistants API service. /// The to monitor for cancellation requests. The default is . /// An list of objects. public static async IAsyncEnumerable ListDefinitionsAsync( @@ -101,7 +101,7 @@ public static async IAsyncEnumerable ListDefinitionsA /// Retrieve a by identifier. /// /// The containing services, plugins, and other state for use throughout the operation. - /// Configuration for accessing the Assistants API service, such as the api-key. + /// Configuration for accessing the Assistants API service. /// The agent identifier /// The to monitor for cancellation requests. The default is . /// An instance diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs index 74714fd66e02..2d9bd616b21b 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs @@ -9,23 +9,23 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// %%% +/// Supports management operations for a >. /// public sealed class OpenAIVectorStore { private readonly VectorStoreClient _client; /// - /// %%% + /// The identifier of the targeted vectore store /// public string VectorStoreId { get; } /// - /// %%% + /// List all vector stores. /// - /// - /// - /// + /// Configuration for accessing the vector-store service. + /// The to monitor for cancellation requests. The default is . + /// An enumeration of models. public static IAsyncEnumerable GetVectorStoresAsync(OpenAIServiceConfiguration config, CancellationToken cancellationToken = default) { OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); @@ -35,10 +35,10 @@ public static IAsyncEnumerable GetVectorStoresAsync(OpenAIServiceCo } /// - /// %%% + /// Initializes a new instance of the class. /// - /// - /// + /// The identifier of the targeted vectore store + /// Configuration for accessing the vector-store service. public OpenAIVectorStore(string vectorStoreId, OpenAIServiceConfiguration config) { OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); @@ -47,29 +47,27 @@ public OpenAIVectorStore(string vectorStoreId, OpenAIServiceConfiguration config this.VectorStoreId = vectorStoreId; } - // %%% BATCH JOBS ??? - /// - /// %%% + /// Add a file from the vector store. /// - /// - /// + /// The file to add, by identifier. + /// The to monitor for cancellation requests. The default is . /// public async Task AddFileAsync(string fileId, CancellationToken cancellationToken = default) => await this._client.AddFileToVectorStoreAsync(this.VectorStoreId, fileId, cancellationToken).ConfigureAwait(false); /// - /// %%% + /// Deletes the entire vector store. /// - /// + /// The to monitor for cancellation requests. The default is . /// public async Task DeleteAsync(CancellationToken cancellationToken = default) => await this._client.DeleteVectorStoreAsync(this.VectorStoreId, cancellationToken).ConfigureAwait(false); /// - /// %%% + /// List the files (by identifier) in the vector store. /// - /// + /// The to monitor for cancellation requests. The default is . /// public async IAsyncEnumerable GetFilesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) { @@ -80,10 +78,10 @@ public async IAsyncEnumerable GetFilesAsync([EnumeratorCancellation] Can } /// - /// %%% + /// Remove a file from the vector store. /// - /// - /// + /// The file to remove, by identifier. + /// The to monitor for cancellation requests. The default is . /// public async Task RemoveFileAsync(string fileId, CancellationToken cancellationToken = default) => await this._client.RemoveFileFromStoreAsync(this.VectorStoreId, fileId, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs index ac2c23293d73..8983e1009bfc 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs @@ -9,8 +9,9 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// %%% +/// Fluent builder for creating a new . /// +/// Configuration for accessing the vector-store service. public sealed class OpenAIVectorStoreBuilder(OpenAIServiceConfiguration config) { private string? _name; @@ -20,7 +21,7 @@ public sealed class OpenAIVectorStoreBuilder(OpenAIServiceConfiguration config) private Dictionary? _metadata; /// - /// %%% + /// Added a file (by identifier) to the vector store. /// /// public OpenAIVectorStoreBuilder AddFile(string fileId) @@ -32,10 +33,10 @@ public OpenAIVectorStoreBuilder AddFile(string fileId) } /// - /// %%% + /// Added files (by identifier) to the vector store. /// /// - public OpenAIVectorStoreBuilder AddFile(string[] fileIds) + public OpenAIVectorStoreBuilder AddFiles(string[] fileIds) { this._fileIds ??= []; this._fileIds.AddRange(fileIds); @@ -44,10 +45,10 @@ public OpenAIVectorStoreBuilder AddFile(string[] fileIds) } /// - /// %%% + /// Define the vector store chunking strategy (if not default). /// - /// - /// + /// The maximum number of tokens in each chunk. + /// The number of tokens that overlap between chunks. public OpenAIVectorStoreBuilder WithChunkingStrategy(int maxTokensPerChunk, int overlappingTokenCount) { this._chunkingStrategy = FileChunkingStrategy.CreateStaticStrategy(maxTokensPerChunk, overlappingTokenCount); @@ -56,9 +57,9 @@ public OpenAIVectorStoreBuilder WithChunkingStrategy(int maxTokensPerChunk, int } /// - /// %%% + /// The number of days of from the last use until vector store will expire. /// - /// + /// The duration (in days) from the last usage. public OpenAIVectorStoreBuilder WithExpiration(TimeSpan duration) { this._expirationPolicy = new VectorStoreExpirationPolicy(VectorStoreExpirationAnchor.LastActiveAt, duration.Days); @@ -67,11 +68,15 @@ public OpenAIVectorStoreBuilder WithExpiration(TimeSpan duration) } /// - /// %%% + /// Adds a single key/value pair to the metadata. /// - /// - /// - /// + /// The metadata key + /// The metadata value + /// + /// The metadata is a set of up to 16 key/value pairs that can be attached to an agent, used for + /// storing additional information about that object in a structured format.Keys + /// may be up to 64 characters in length and values may be up to 512 characters in length. + /// > public OpenAIVectorStoreBuilder WithMetadata(string key, string value) { this._metadata ??= []; @@ -82,10 +87,11 @@ public OpenAIVectorStoreBuilder WithMetadata(string key, string value) } /// - /// %%% + /// A set of up to 16 key/value pairs that can be attached to an agent, used for + /// storing additional information about that object in a structured format.Keys + /// may be up to 64 characters in length and values may be up to 512 characters in length. /// - /// - /// + /// The metadata public OpenAIVectorStoreBuilder WithMetadata(IDictionary metadata) { this._metadata ??= []; @@ -99,10 +105,9 @@ public OpenAIVectorStoreBuilder WithMetadata(IDictionary metadat } /// - /// %%% + /// Defines the name of the vector store when not anonymous. /// - /// - /// + /// The store name. public OpenAIVectorStoreBuilder WithName(string name) { this._name = name; @@ -111,10 +116,9 @@ public OpenAIVectorStoreBuilder WithName(string name) } /// - /// %%% + /// Creates a as defined. /// - /// - /// + /// The to monitor for cancellation requests. The default is . public async Task CreateAsync(CancellationToken cancellationToken = default) { OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); From 3081083cfa09e26bb829e3096fdae8d30b9f05c2 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 10:55:54 -0700 Subject: [PATCH 025/121] More clean-up --- dotnet/Directory.Packages.props | 3 +-- .../src/Agents/OpenAI/Internal/AssistantThreadActions.cs | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index bb4233ad6ba9..f2a129da8726 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -5,11 +5,10 @@ true - + - diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 4481948dfa50..c7a9342e6700 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -88,14 +88,14 @@ IEnumerable GetMessageContents() { yield return MessageContent.FromText(content.ToString()); } - else if (content is ImageContent imageContent) + else if (content is ImageContent imageContent && imageContent.Data.HasValue) { yield return MessageContent.FromImageUrl( - imageContent.Uri ?? new Uri(Convert.ToBase64String(imageContent.Data?.ToArray() ?? []))); // %%% WUT A MESS - API BUG? + imageContent.Uri ?? new Uri(Convert.ToBase64String(imageContent.Data.Value.ToArray()))); } else if (content is FileReferenceContent fileContent) { - options.Attachments.Add(new MessageCreationAttachment(fileContent.FileId, [new CodeInterpreterToolDefinition()])); // %%% WUT A MESS - TOOLS? + options.Attachments.Add(new MessageCreationAttachment(fileContent.FileId, [new CodeInterpreterToolDefinition()])); } } } @@ -210,7 +210,7 @@ public static async IAsyncEnumerable InvokeAsync( // Process tool output ToolOutput[] toolOutputs = GenerateToolOutputs(functionResults); - await client.SubmitToolOutputsToRunAsync(run, toolOutputs).ConfigureAwait(false); // %%% BUG CANCEL TOKEN + await client.SubmitToolOutputsToRunAsync(threadId, run.Id, toolOutputs, cancellationToken).ConfigureAwait(false); } if (logger.IsEnabled(LogLevel.Information)) // Avoid boxing if not enabled From d3b5528416fb0e6f9cf1e84e7caf48fd6468bda5 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 11:36:14 -0700 Subject: [PATCH 026/121] Coverage --- .../OpenAI/OpenAIAssistantDefinitionTests.cs | 26 ++++++ .../OpenAIAssistantInvocationSettingsTests.cs | 68 +++++++++++++++ .../OpenAI/OpenAIConfigurationTests.cs | 44 ---------- .../OpenAI/OpenAIServiceConfigurationTests.cs | 84 +++++++++++++++++++ .../OpenAIThreadCreationSettingsTests.cs | 52 ++++++++++++ 5 files changed, 230 insertions(+), 44 deletions(-) create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationSettingsTests.cs delete mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIServiceConfigurationTests.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationSettingsTests.cs diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index 602eddd07704..e692166986eb 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -24,8 +24,13 @@ public void VerifyOpenAIAssistantDefinitionInitialState() Assert.Null(definition.Instructions); Assert.Null(definition.Description); Assert.Null(definition.Metadata); + Assert.Null(definition.ExecutionSettings); + Assert.Null(definition.Temperature); + Assert.Null(definition.TopP); Assert.Null(definition.VectorStoreId); + Assert.Null(definition.CodeInterpterFileIds); Assert.False(definition.EnableCodeInterpreter); + Assert.False(definition.EnableJsonResponse); } /// @@ -44,7 +49,19 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Description = "testdescription", VectorStoreId = "#vs", Metadata = new Dictionary() { { "a", "1" } }, + Temperature = 2, + TopP = 0, + ExecutionSettings = + new() + { + MaxCompletionTokens = 1000, + MaxPromptTokens = 1000, + ParallelToolCallsEnabled = false, + TruncationMessageCount = 12, + }, + CodeInterpterFileIds = ["file1"], EnableCodeInterpreter = true, + EnableJsonResponse = true, }; Assert.Equal("testid", definition.Id); @@ -53,7 +70,16 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Assert.Equal("testinstructions", definition.Instructions); Assert.Equal("testdescription", definition.Description); Assert.Equal("#vs", definition.VectorStoreId); + Assert.Equal(2, definition.Temperature); + Assert.Equal(0, definition.TopP); + Assert.NotNull(definition.ExecutionSettings); + Assert.Equal(1000, definition.ExecutionSettings.MaxCompletionTokens); + Assert.Equal(1000, definition.ExecutionSettings.MaxPromptTokens); + Assert.Equal(12, definition.ExecutionSettings.TruncationMessageCount); + Assert.False(definition.ExecutionSettings.ParallelToolCallsEnabled); Assert.Single(definition.Metadata); + Assert.Single(definition.CodeInterpterFileIds); Assert.True(definition.EnableCodeInterpreter); + Assert.True(definition.EnableJsonResponse); } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationSettingsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationSettingsTests.cs new file mode 100644 index 000000000000..ac9ae051bf4d --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationSettingsTests.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI; + +/// +/// Unit testing of . +/// +public class OpenAIAssistantInvocationSettingsTests +{ + /// + /// Verify initial state. + /// + [Fact] + public void OpenAIAssistantInvocationSettingsInitialState() + { + OpenAIAssistantInvocationSettings settings = new(); + + Assert.Null(settings.ModelName); + Assert.Null(settings.Metadata); + Assert.Null(settings.Temperature); + Assert.Null(settings.TopP); + Assert.Null(settings.ParallelToolCallsEnabled); + Assert.Null(settings.MaxCompletionTokens); + Assert.Null(settings.MaxPromptTokens); + Assert.Null(settings.TruncationMessageCount); + Assert.Null(settings.EnableJsonResponse); + Assert.False(settings.EnableCodeInterpreter); + Assert.False(settings.EnableFileSearch); + } + + /// + /// Verify initialization. + /// + [Fact] + public void OpenAIAssistantInvocationSettingsAssignment() + { + OpenAIAssistantInvocationSettings settings = + new() + { + ModelName = "testmodel", + Metadata = new Dictionary() { { "a", "1" } }, + MaxCompletionTokens = 1000, + MaxPromptTokens = 1000, + ParallelToolCallsEnabled = false, + TruncationMessageCount = 12, + Temperature = 2, + TopP = 0, + EnableCodeInterpreter = true, + EnableJsonResponse = true, + EnableFileSearch = true, + }; + + Assert.Equal("testmodel", settings.ModelName); + Assert.Equal(2, settings.Temperature); + Assert.Equal(0, settings.TopP); + Assert.Equal(1000, settings.MaxCompletionTokens); + Assert.Equal(1000, settings.MaxPromptTokens); + Assert.Equal(12, settings.TruncationMessageCount); + Assert.False(settings.ParallelToolCallsEnabled); + Assert.Single(settings.Metadata); + Assert.True(settings.EnableCodeInterpreter); + Assert.True(settings.EnableJsonResponse); + Assert.True(settings.EnableFileSearch); + } +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs deleted file mode 100644 index 14237eebcd19..000000000000 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIConfigurationTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using System.Net.Http; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Xunit; - -namespace SemanticKernel.Agents.UnitTests.OpenAI; - -/// -/// Unit testing of . -/// -public class OpenAIConfigurationTests -{ - /// - /// Verify initial state. - /// - [Fact] - public void VerifyOpenAIAssistantConfigurationInitialState() - { - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: "testkey"); - - Assert.Equal("testkey", config.ApiKey); - Assert.Null(config.Endpoint); - Assert.Null(config.HttpClient); - } - - /// - /// Verify assignment. - /// - [Fact] - public void VerifyOpenAIAssistantConfigurationAssignment() - { - using HttpClient client = new(); - - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: "testkey", endpoint: new Uri("https://localhost"), client); - - Assert.Equal("testkey", config.ApiKey); - Assert.NotNull(config.Endpoint); - Assert.Equal("https://localhost/", config.Endpoint.ToString()); - Assert.NotNull(config.HttpClient); - } - - // %%% MORE -} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIServiceConfigurationTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIServiceConfigurationTests.cs new file mode 100644 index 000000000000..dce5d5c9ceaf --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIServiceConfigurationTests.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Net.Http; +using Azure.Core; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Moq; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI; + +/// +/// Unit testing of . +/// +public class OpenAIServiceConfigurationTests +{ + /// + /// Verify Open AI service configuration. + /// + [Fact] + public void VerifyOpenAIAssistantConfiguration() + { + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: "testkey"); + + Assert.Equal(OpenAIServiceConfiguration.OpenAIServiceType.OpenAI, config.Type); + Assert.Equal("testkey", config.ApiKey); + Assert.Null(config.Credential); + Assert.Null(config.Endpoint); + Assert.Null(config.HttpClient); + } + + /// + /// Verify Open AI service configuration with endpoint. + /// + [Fact] + public void VerifyOpenAIAssistantProxyConfiguration() + { + using HttpClient client = new(); + + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: "testkey", endpoint: new Uri("https://localhost"), client); + + Assert.Equal(OpenAIServiceConfiguration.OpenAIServiceType.OpenAI, config.Type); + Assert.Equal("testkey", config.ApiKey); + Assert.Null(config.Credential); + Assert.NotNull(config.Endpoint); + Assert.Equal("https://localhost/", config.Endpoint.ToString()); + Assert.NotNull(config.HttpClient); + } + + /// + /// Verify Azure Open AI service configuration with API key. + /// + [Fact] + public void VerifyAzureOpenAIAssistantApiKeyConfiguration() + { + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForAzureOpenAI(apiKey: "testkey", endpoint: new Uri("https://localhost")); + + Assert.Equal(OpenAIServiceConfiguration.OpenAIServiceType.AzureOpenAI, config.Type); + Assert.Equal("testkey", config.ApiKey); + Assert.Null(config.Credential); + Assert.NotNull(config.Endpoint); + Assert.Equal("https://localhost/", config.Endpoint.ToString()); + Assert.Null(config.HttpClient); + } + + /// + /// Verify Azure Open AI service configuration with API key. + /// + [Fact] + public void VerifyAzureOpenAIAssistantCredentialConfiguration() + { + using HttpClient client = new(); + + Mock credential = new(); + + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForAzureOpenAI(credential.Object, endpoint: new Uri("https://localhost"), client); + + Assert.Equal(OpenAIServiceConfiguration.OpenAIServiceType.AzureOpenAI, config.Type); + Assert.Null(config.ApiKey); + Assert.NotNull(config.Credential); + Assert.NotNull(config.Endpoint); + Assert.Equal("https://localhost/", config.Endpoint.ToString()); + Assert.NotNull(config.HttpClient); + } +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationSettingsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationSettingsTests.cs new file mode 100644 index 000000000000..0c096c0ba62e --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationSettingsTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI; + +/// +/// Unit testing of . +/// +public class OpenAIThreadCreationSettingsTests +{ + /// + /// Verify initial state. + /// + [Fact] + public void OpenAIThreadCreationSettingsInitialState() + { + OpenAIThreadCreationSettings settings = new(); + + Assert.Null(settings.Messages); + Assert.Null(settings.Metadata); + Assert.Null(settings.VectorStoreId); + Assert.Null(settings.CodeInterpterFileIds); + Assert.False(settings.EnableCodeInterpreter); + } + + /// + /// Verify initialization. + /// + [Fact] + public void OpenAIThreadCreationSettingsAssignment() + { + OpenAIThreadCreationSettings definition = + new() + { + Messages = [new ChatMessageContent(AuthorRole.User, "test")], + VectorStoreId = "#vs", + Metadata = new Dictionary() { { "a", "1" } }, + CodeInterpterFileIds = ["file1"], + EnableCodeInterpreter = true, + }; + + Assert.Single(definition.Messages); + Assert.Single(definition.Metadata); + Assert.Equal("#vs", definition.VectorStoreId); + Assert.Single(definition.CodeInterpterFileIds); + Assert.True(definition.EnableCodeInterpreter); + } +} From 0ae6739e71ffa18e57c176d5c1e24b0ce1550fdd Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 11:37:52 -0700 Subject: [PATCH 027/121] Typo --- dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs index 2d9bd616b21b..a882729cde14 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs @@ -16,7 +16,7 @@ public sealed class OpenAIVectorStore private readonly VectorStoreClient _client; /// - /// The identifier of the targeted vectore store + /// The identifier of the targeted vector store /// public string VectorStoreId { get; } @@ -37,7 +37,7 @@ public static IAsyncEnumerable GetVectorStoresAsync(OpenAIServiceCo /// /// Initializes a new instance of the class. /// - /// The identifier of the targeted vectore store + /// The identifier of the targeted vector store /// Configuration for accessing the vector-store service. public OpenAIVectorStore(string vectorStoreId, OpenAIServiceConfiguration config) { From 18f6ad2e4e0028e7bb93e2499cbc2a5efc2d919f Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 13:17:38 -0700 Subject: [PATCH 028/121] More test coverage --- .../OpenAI/OpenAIServiceConfiguration.cs | 4 +- .../Agents/OpenAI/OpenAIVectorStoreBuilder.cs | 10 +- .../OpenAI/OpenAIClientFactoryTests.cs | 65 +++ .../OpenAI/OpenAIVectorStoreBuilderTests.cs | 129 +++++ .../OpenAI/OpenAIVectorStoreTests.cs | 546 ++++++++++++++++++ 5 files changed, 743 insertions(+), 11 deletions(-) create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientFactoryTests.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreBuilderTests.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs diff --git a/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs index 5b93f74fb87b..1bc6431e5487 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs @@ -64,10 +64,8 @@ public static OpenAIServiceConfiguration ForAzureOpenAI(TokenCredential credenti /// The API key /// An optional endpoint /// Custom for HTTP requests. - public static OpenAIServiceConfiguration ForOpenAI(string apiKey, Uri? endpoint = null, HttpClient? httpClient = null) + public static OpenAIServiceConfiguration ForOpenAI(string? apiKey, Uri? endpoint = null, HttpClient? httpClient = null) { - Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); - return new() { diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs index 8983e1009bfc..65e9bf5c7496 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs @@ -17,8 +17,8 @@ public sealed class OpenAIVectorStoreBuilder(OpenAIServiceConfiguration config) private string? _name; private FileChunkingStrategy? _chunkingStrategy; private VectorStoreExpirationPolicy? _expirationPolicy; - private List? _fileIds; - private Dictionary? _metadata; + private readonly List _fileIds = []; + private readonly Dictionary _metadata = []; /// /// Added a file (by identifier) to the vector store. @@ -26,7 +26,6 @@ public sealed class OpenAIVectorStoreBuilder(OpenAIServiceConfiguration config) /// public OpenAIVectorStoreBuilder AddFile(string fileId) { - this._fileIds ??= []; this._fileIds.Add(fileId); return this; @@ -38,7 +37,6 @@ public OpenAIVectorStoreBuilder AddFile(string fileId) /// public OpenAIVectorStoreBuilder AddFiles(string[] fileIds) { - this._fileIds ??= []; this._fileIds.AddRange(fileIds); return this; @@ -79,8 +77,6 @@ public OpenAIVectorStoreBuilder WithExpiration(TimeSpan duration) /// > public OpenAIVectorStoreBuilder WithMetadata(string key, string value) { - this._metadata ??= []; - this._metadata[key] = value; return this; @@ -94,8 +90,6 @@ public OpenAIVectorStoreBuilder WithMetadata(string key, string value) /// The metadata public OpenAIVectorStoreBuilder WithMetadata(IDictionary metadata) { - this._metadata ??= []; - foreach (KeyValuePair item in this._metadata) { this._metadata[item.Key] = item.Value; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientFactoryTests.cs new file mode 100644 index 000000000000..46b45e419213 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientFactoryTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Net.Http; +using Azure.Core; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Moq; +using OpenAI; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI; + +/// +/// Unit testing of . +/// +public class OpenAIClientFactoryTests +{ + /// + /// Verify that the factory can create a client for Azure OpenAI. + /// + [Fact] + public void VerifyOpenAIClientFactoryTargetAzureByKey() + { + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForAzureOpenAI("key", new Uri("https://localhost")); + OpenAIClient client = OpenAIClientFactory.CreateClient(config); + Assert.NotNull(client); + } + + /// + /// Verify that the factory can create a client for Azure OpenAI. + /// + [Fact] + public void VerifyOpenAIClientFactoryTargetAzureByCredential() + { + Mock mockCredential = new(); + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForAzureOpenAI(mockCredential.Object, new Uri("https://localhost")); + OpenAIClient client = OpenAIClientFactory.CreateClient(config); + Assert.NotNull(client); + } + + /// + /// Verify that the factory can create a client for various OpenAI service configurations. + /// + [Theory] + [InlineData(null, null)] + [InlineData("key", null)] + [InlineData("key", "http://myproxy:9819")] + public void VerifyOpenAIClientFactoryTargetOpenAI(string? key, string? endpoint) + { + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(key, endpoint != null ? new Uri(endpoint) : null); + OpenAIClient client = OpenAIClientFactory.CreateClient(config); + Assert.NotNull(client); + } + + /// + /// Verify that the factory can create a client with http proxy. + /// + [Fact] + public void VerifyOpenAIClientFactoryWithHttpClient() + { + using HttpClient httpClient = new() { BaseAddress = new Uri("http://myproxy:9819") }; + OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: null, httpClient: httpClient); + OpenAIClient client = OpenAIClientFactory.CreateClient(config); + Assert.NotNull(client); + } +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreBuilderTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreBuilderTests.cs new file mode 100644 index 000000000000..dd6734e8cb71 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreBuilderTests.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.SemanticKernel.Agents.OpenAI; +using OpenAI.VectorStores; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI; + +/// +/// Unit testing of . +/// +public sealed class OpenAIVectorStoreBuilderTests : IDisposable +{ + private readonly HttpMessageHandlerStub _messageHandlerStub; + private readonly HttpClient _httpClient; + + /// + /// %%% + /// + [Fact] + public async Task VerifyOpenAIVectorStoreBuilderEmptyAsync() + { + this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateStore); + + VectorStore store = + await new OpenAIVectorStoreBuilder(this.CreateTestConfiguration()) + .CreateAsync(); + + Assert.NotNull(store); + } + + /// + /// %%% + /// + [Fact] + public async Task VerifyOpenAIVectorStoreBuilderFluentAsync() + { + this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateStore); + + Dictionary metadata = new() { { "key2", "value2" } }; + + VectorStore store = + await new OpenAIVectorStoreBuilder(this.CreateTestConfiguration()) + .WithName("my_vector_store") + .AddFile("#file_1") + .AddFiles(["#file_2", "#file_3"]) + .AddFiles(["#file_4", "#file_5"]) + .WithChunkingStrategy(1000, 400) + .WithExpiration(TimeSpan.FromDays(30)) + .WithMetadata("key1", "value1") + .WithMetadata(metadata) + .CreateAsync(); + + Assert.NotNull(store); + } + + /// + public void Dispose() + { + this._messageHandlerStub.Dispose(); + this._httpClient.Dispose(); + } + + /// + /// Initializes a new instance of the class. + /// + public OpenAIVectorStoreBuilderTests() + { + this._messageHandlerStub = new HttpMessageHandlerStub(); + this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false); + } + + private OpenAIServiceConfiguration CreateTestConfiguration(bool targetAzure = false) + => targetAzure ? + OpenAIServiceConfiguration.ForAzureOpenAI(apiKey: "fakekey", endpoint: new Uri("https://localhost"), this._httpClient) : + OpenAIServiceConfiguration.ForOpenAI(apiKey: "fakekey", endpoint: null, this._httpClient); + + private void SetupResponse(HttpStatusCode statusCode, string content) + { + this._messageHandlerStub.ResponseToReturn = + new(statusCode) + { + Content = new StringContent(content) + }; + } + + private void SetupResponses(HttpStatusCode statusCode, params string[] content) + { + foreach (var item in content) + { +#pragma warning disable CA2000 // Dispose objects before losing scope + this._messageHandlerStub.ResponseQueue.Enqueue( + new(statusCode) + { + Content = new StringContent(item) + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + } + } + + private static class ResponseContent + { + public const string CreateStore = + """ + { + "id": "vs_123", + "object": "vector_store", + "created_at": 1698107661, + "usage_bytes": 123456, + "last_active_at": 1698107661, + "name": "my_vector_store", + "status": "completed", + "file_counts": { + "in_progress": 0, + "completed": 5, + "cancelled": 0, + "failed": 0, + "total": 5 + }, + "metadata": {}, + "last_used_at": 1698107661 + } + """; + } +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs new file mode 100644 index 000000000000..4016a7770fd9 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs @@ -0,0 +1,546 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI; + +/// +/// Unit testing of . +/// +public sealed class OpenAIVectorStoreTests : IDisposable +{ + private readonly HttpMessageHandlerStub _messageHandlerStub; + private readonly HttpClient _httpClient; + + /// + /// %%% + /// + [Fact] + public void VerifyOpenAIVectorStoreInitialization() + { + //this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentSimple); + + OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); + Assert.Equal("#vs1", store.VectorStoreId); + } + + /// + public void Dispose() + { + this._messageHandlerStub.Dispose(); + this._httpClient.Dispose(); + } + + /// + /// Initializes a new instance of the class. + /// + public OpenAIVectorStoreTests() + { + this._messageHandlerStub = new HttpMessageHandlerStub(); + this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false); + } + + private OpenAIServiceConfiguration CreateTestConfiguration(bool targetAzure = false) + => targetAzure ? + OpenAIServiceConfiguration.ForAzureOpenAI(apiKey: "fakekey", endpoint: new Uri("https://localhost"), this._httpClient) : + OpenAIServiceConfiguration.ForOpenAI(apiKey: "fakekey", endpoint: null, this._httpClient); + + private void SetupResponse(HttpStatusCode statusCode, string content) + { + this._messageHandlerStub.ResponseToReturn = + new(statusCode) + { + Content = new StringContent(content) + }; + } + + private void SetupResponses(HttpStatusCode statusCode, params string[] content) + { + foreach (var item in content) + { +#pragma warning disable CA2000 // Dispose objects before losing scope + this._messageHandlerStub.ResponseQueue.Enqueue( + new(statusCode) + { + Content = new StringContent(item) + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + } + } + + private static class ResponseContent + { + public const string CreateAgentSimple = + """ + { + "id": "asst_abc123", + "object": "assistant", + "created_at": 1698984975, + "name": null, + "description": null, + "model": "gpt-4-turbo", + "instructions": null, + "tools": [], + "file_ids": [], + "metadata": {} + } + """; + + public const string CreateAgentFull = + """ + { + "id": "asst_abc123", + "object": "assistant", + "created_at": 1698984975, + "name": "testname", + "description": "testdescription", + "model": "gpt-4-turbo", + "instructions": "testinstructions", + "tools": [], + "file_ids": [], + "metadata": {} + } + """; + + public const string CreateAgentWithEverything = + """ + { + "id": "asst_abc123", + "object": "assistant", + "created_at": 1698984975, + "name": null, + "description": null, + "model": "gpt-4-turbo", + "instructions": null, + "tools": [ + { + "type": "code_interpreter" + }, + { + "type": "file_search" + } + ], + "tool_resources": { + "file_search": { + "vector_store_ids": ["#vs"] + } + }, + "metadata": {"a": "1"} + } + """; + + public const string DeleteAgent = + """ + { + "id": "asst_abc123", + "object": "assistant.deleted", + "deleted": true + } + """; + + public const string CreateThread = + """ + { + "id": "thread_abc123", + "object": "thread", + "created_at": 1699012949, + "metadata": {} + } + """; + + public const string CreateRun = + """ + { + "id": "run_abc123", + "object": "thread.run", + "created_at": 1699063290, + "assistant_id": "asst_abc123", + "thread_id": "thread_abc123", + "status": "queued", + "started_at": 1699063290, + "expires_at": null, + "cancelled_at": null, + "failed_at": null, + "completed_at": 1699063291, + "last_error": null, + "model": "gpt-4-turbo", + "instructions": null, + "tools": [], + "file_ids": [], + "metadata": {}, + "usage": null, + "temperature": 1 + } + """; + + public const string PendingRun = + """ + { + "id": "run_abc123", + "object": "thread.run", + "created_at": 1699063290, + "assistant_id": "asst_abc123", + "thread_id": "thread_abc123", + "status": "requires_action", + "started_at": 1699063290, + "expires_at": null, + "cancelled_at": null, + "failed_at": null, + "completed_at": 1699063291, + "last_error": null, + "model": "gpt-4-turbo", + "instructions": null, + "tools": [], + "file_ids": [], + "metadata": {}, + "usage": null, + "temperature": 1 + } + """; + + public const string CompletedRun = + """ + { + "id": "run_abc123", + "object": "thread.run", + "created_at": 1699063290, + "assistant_id": "asst_abc123", + "thread_id": "thread_abc123", + "status": "completed", + "started_at": 1699063290, + "expires_at": null, + "cancelled_at": null, + "failed_at": null, + "completed_at": 1699063291, + "last_error": null, + "model": "gpt-4-turbo", + "instructions": null, + "tools": [], + "file_ids": [], + "metadata": {}, + "usage": null, + "temperature": 1 + } + """; + + public const string MessageSteps = + """ + { + "object": "list", + "data": [ + { + "id": "step_abc123", + "object": "thread.run.step", + "created_at": 1699063291, + "run_id": "run_abc123", + "assistant_id": "asst_abc123", + "thread_id": "thread_abc123", + "type": "message_creation", + "status": "completed", + "cancelled_at": null, + "completed_at": 1699063291, + "expired_at": null, + "failed_at": null, + "last_error": null, + "step_details": { + "type": "message_creation", + "message_creation": { + "message_id": "msg_abc123" + } + }, + "usage": { + "prompt_tokens": 123, + "completion_tokens": 456, + "total_tokens": 579 + } + } + ], + "first_id": "step_abc123", + "last_id": "step_abc456", + "has_more": false + } + """; + + public const string ToolSteps = + """ + { + "object": "list", + "data": [ + { + "id": "step_abc987", + "object": "thread.run.step", + "created_at": 1699063291, + "run_id": "run_abc123", + "assistant_id": "asst_abc123", + "thread_id": "thread_abc123", + "type": "tool_calls", + "status": "in_progress", + "cancelled_at": null, + "completed_at": 1699063291, + "expired_at": null, + "failed_at": null, + "last_error": null, + "step_details": { + "type": "tool_calls", + "tool_calls": [ + { + "id": "tool_1", + "type": "function", + "function": { + "name": "MyPlugin-MyFunction", + "arguments": "{ \"index\": 3 }", + "output": "test" + } + } + ] + }, + "usage": { + "prompt_tokens": 123, + "completion_tokens": 456, + "total_tokens": 579 + } + } + ], + "first_id": "step_abc123", + "last_id": "step_abc456", + "has_more": false + } + """; + + public const string ToolResponse = "{ }"; + + public const string GetImageMessage = + """ + { + "id": "msg_abc123", + "object": "thread.message", + "created_at": 1699017614, + "thread_id": "thread_abc123", + "role": "user", + "content": [ + { + "type": "image_file", + "image_file": { + "file_id": "file_123" + } + } + ], + "assistant_id": "asst_abc123", + "run_id": "run_abc123" + } + """; + + public const string GetTextMessage = + """ + { + "id": "msg_abc123", + "object": "thread.message", + "created_at": 1699017614, + "thread_id": "thread_abc123", + "role": "user", + "content": [ + { + "type": "text", + "text": { + "value": "How does AI work? Explain it in simple terms.", + "annotations": [] + } + } + ], + "assistant_id": "asst_abc123", + "run_id": "run_abc123" + } + """; + + public const string GetTextMessageWithAnnotation = + """ + { + "id": "msg_abc123", + "object": "thread.message", + "created_at": 1699017614, + "thread_id": "thread_abc123", + "role": "user", + "content": [ + { + "type": "text", + "text": { + "value": "How does AI work? Explain it in simple terms.**f1", + "annotations": [ + { + "type": "file_citation", + "text": "**f1", + "file_citation": { + "file_id": "file_123", + "quote": "does" + }, + "start_index": 3, + "end_index": 6 + } + ] + } + } + ], + "assistant_id": "asst_abc123", + "run_id": "run_abc123" + } + """; + + public const string ListAgentsPageMore = + """ + { + "object": "list", + "data": [ + { + "id": "asst_abc123", + "object": "assistant", + "created_at": 1698982736, + "name": "Coding Tutor", + "description": null, + "model": "gpt-4-turbo", + "instructions": "You are a helpful assistant designed to make me better at coding!", + "tools": [], + "metadata": {} + }, + { + "id": "asst_abc456", + "object": "assistant", + "created_at": 1698982718, + "name": "My Assistant", + "description": null, + "model": "gpt-4-turbo", + "instructions": "You are a helpful assistant designed to make me better at coding!", + "tools": [], + "metadata": {} + }, + { + "id": "asst_abc789", + "object": "assistant", + "created_at": 1698982643, + "name": null, + "description": null, + "model": "gpt-4-turbo", + "instructions": null, + "tools": [], + "metadata": {} + } + ], + "first_id": "asst_abc123", + "last_id": "asst_abc789", + "has_more": true + } + """; + + public const string ListAgentsPageFinal = + """ + { + "object": "list", + "data": [ + { + "id": "asst_abc789", + "object": "assistant", + "created_at": 1698982736, + "name": "Coding Tutor", + "description": null, + "model": "gpt-4-turbo", + "instructions": "You are a helpful assistant designed to make me better at coding!", + "tools": [], + "metadata": {} + } + ], + "first_id": "asst_abc789", + "last_id": "asst_abc789", + "has_more": false + } + """; + + public const string ListMessagesPageMore = + """ + { + "object": "list", + "data": [ + { + "id": "msg_abc123", + "object": "thread.message", + "created_at": 1699016383, + "thread_id": "thread_abc123", + "role": "user", + "content": [ + { + "type": "text", + "text": { + "value": "How does AI work? Explain it in simple terms.", + "annotations": [] + } + } + ], + "file_ids": [], + "assistant_id": null, + "run_id": null, + "metadata": {} + }, + { + "id": "msg_abc456", + "object": "thread.message", + "created_at": 1699016383, + "thread_id": "thread_abc123", + "role": "user", + "content": [ + { + "type": "text", + "text": { + "value": "Hello, what is AI?", + "annotations": [] + } + } + ], + "file_ids": [ + "file-abc123" + ], + "assistant_id": null, + "run_id": null, + "metadata": {} + } + ], + "first_id": "msg_abc123", + "last_id": "msg_abc456", + "has_more": true + } + """; + + public const string ListMessagesPageFinal = + """ + { + "object": "list", + "data": [ + { + "id": "msg_abc789", + "object": "thread.message", + "created_at": 1699016383, + "thread_id": "thread_abc123", + "role": "user", + "content": [ + { + "type": "text", + "text": { + "value": "How does AI work? Explain it in simple terms.", + "annotations": [] + } + } + ], + "file_ids": [], + "assistant_id": null, + "run_id": null, + "metadata": {} + } + ], + "first_id": "msg_abc789", + "last_id": "msg_abc789", + "has_more": false + } + """; + } +} From 2f459f000470b178c148c4ca98345b1ef9c2d831 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 13:38:15 -0700 Subject: [PATCH 029/121] More coverage --- .../OpenAI/OpenAIVectorStoreTests.cs | 557 ++++-------------- 1 file changed, 126 insertions(+), 431 deletions(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs index 4016a7770fd9..dbcdef23f8b7 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.SemanticKernel.Agents.OpenAI; +using OpenAI.VectorStores; using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI; @@ -22,12 +24,79 @@ public sealed class OpenAIVectorStoreTests : IDisposable [Fact] public void VerifyOpenAIVectorStoreInitialization() { - //this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentSimple); - OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); Assert.Equal("#vs1", store.VectorStoreId); } + /// + /// %%% + /// + [Fact] + public async Task VerifyOpenAIVectorStoreDeleteAsync() + { + this.SetupResponse(HttpStatusCode.OK, ResponseContent.DeleteStore); + + OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); + bool isDeleted = await store.DeleteAsync(); + + Assert.True(isDeleted); + } + + /// + /// %%% + /// + [Fact] + public async Task VerifyOpenAIVectorStoreListAsync() + { + this.SetupResponse(HttpStatusCode.OK, ResponseContent.ListStores); + + VectorStore[] stores = await OpenAIVectorStore.GetVectorStoresAsync(this.CreateTestConfiguration()).ToArrayAsync(); + + Assert.Equal(2, stores.Length); + } + + /// + /// %%% + /// + [Fact] + public async Task VerifyOpenAIVectorStoreAddFileAsync() + { + this.SetupResponse(HttpStatusCode.OK, ResponseContent.AddFile); + + OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); + await store.AddFileAsync("#file_1"); + + // %%% VERIFY + } + + /// + /// %%% + /// + [Fact] + public async Task VerifyOpenAIVectorStoreRemoveFileAsync() + { + this.SetupResponse(HttpStatusCode.OK, ResponseContent.DeleteFile); + + OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); + bool isDeleted = await store.RemoveFileAsync("#file_1"); + + Assert.True(isDeleted); + } + + /// + /// %%% + /// + [Fact] + public async Task VerifyOpenAIVectorStoreGetFilesAsync() + { + this.SetupResponse(HttpStatusCode.OK, ResponseContent.ListFiles); + + OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); + string[] files = await store.GetFilesAsync().ToArrayAsync(); + + Assert.Equal(2, files.Length); + } + /// public void Dispose() { @@ -74,473 +143,99 @@ private void SetupResponses(HttpStatusCode statusCode, params string[] content) private static class ResponseContent { - public const string CreateAgentSimple = - """ - { - "id": "asst_abc123", - "object": "assistant", - "created_at": 1698984975, - "name": null, - "description": null, - "model": "gpt-4-turbo", - "instructions": null, - "tools": [], - "file_ids": [], - "metadata": {} - } - """; - - public const string CreateAgentFull = - """ - { - "id": "asst_abc123", - "object": "assistant", - "created_at": 1698984975, - "name": "testname", - "description": "testdescription", - "model": "gpt-4-turbo", - "instructions": "testinstructions", - "tools": [], - "file_ids": [], - "metadata": {} - } - """; - - public const string CreateAgentWithEverything = - """ - { - "id": "asst_abc123", - "object": "assistant", - "created_at": 1698984975, - "name": null, - "description": null, - "model": "gpt-4-turbo", - "instructions": null, - "tools": [ - { - "type": "code_interpreter" - }, - { - "type": "file_search" - } - ], - "tool_resources": { - "file_search": { - "vector_store_ids": ["#vs"] - } - }, - "metadata": {"a": "1"} - } - """; - - public const string DeleteAgent = - """ - { - "id": "asst_abc123", - "object": "assistant.deleted", - "deleted": true - } - """; - - public const string CreateThread = - """ - { - "id": "thread_abc123", - "object": "thread", - "created_at": 1699012949, - "metadata": {} - } - """; - - public const string CreateRun = - """ - { - "id": "run_abc123", - "object": "thread.run", - "created_at": 1699063290, - "assistant_id": "asst_abc123", - "thread_id": "thread_abc123", - "status": "queued", - "started_at": 1699063290, - "expires_at": null, - "cancelled_at": null, - "failed_at": null, - "completed_at": 1699063291, - "last_error": null, - "model": "gpt-4-turbo", - "instructions": null, - "tools": [], - "file_ids": [], - "metadata": {}, - "usage": null, - "temperature": 1 - } - """; - - public const string PendingRun = - """ - { - "id": "run_abc123", - "object": "thread.run", - "created_at": 1699063290, - "assistant_id": "asst_abc123", - "thread_id": "thread_abc123", - "status": "requires_action", - "started_at": 1699063290, - "expires_at": null, - "cancelled_at": null, - "failed_at": null, - "completed_at": 1699063291, - "last_error": null, - "model": "gpt-4-turbo", - "instructions": null, - "tools": [], - "file_ids": [], - "metadata": {}, - "usage": null, - "temperature": 1 - } - """; - - public const string CompletedRun = + public const string AddFile = """ { - "id": "run_abc123", - "object": "thread.run", - "created_at": 1699063290, - "assistant_id": "asst_abc123", - "thread_id": "thread_abc123", + "id": "#file_1", + "object": "vector_store.file", + "created_at": 1699061776, + "usage_bytes": 1234, + "vector_store_id": "vs_abcd", "status": "completed", - "started_at": 1699063290, - "expires_at": null, - "cancelled_at": null, - "failed_at": null, - "completed_at": 1699063291, - "last_error": null, - "model": "gpt-4-turbo", - "instructions": null, - "tools": [], - "file_ids": [], - "metadata": {}, - "usage": null, - "temperature": 1 - } - """; - - public const string MessageSteps = - """ - { - "object": "list", - "data": [ - { - "id": "step_abc123", - "object": "thread.run.step", - "created_at": 1699063291, - "run_id": "run_abc123", - "assistant_id": "asst_abc123", - "thread_id": "thread_abc123", - "type": "message_creation", - "status": "completed", - "cancelled_at": null, - "completed_at": 1699063291, - "expired_at": null, - "failed_at": null, - "last_error": null, - "step_details": { - "type": "message_creation", - "message_creation": { - "message_id": "msg_abc123" - } - }, - "usage": { - "prompt_tokens": 123, - "completion_tokens": 456, - "total_tokens": 579 - } - } - ], - "first_id": "step_abc123", - "last_id": "step_abc456", - "has_more": false - } - """; - - public const string ToolSteps = - """ - { - "object": "list", - "data": [ - { - "id": "step_abc987", - "object": "thread.run.step", - "created_at": 1699063291, - "run_id": "run_abc123", - "assistant_id": "asst_abc123", - "thread_id": "thread_abc123", - "type": "tool_calls", - "status": "in_progress", - "cancelled_at": null, - "completed_at": 1699063291, - "expired_at": null, - "failed_at": null, - "last_error": null, - "step_details": { - "type": "tool_calls", - "tool_calls": [ - { - "id": "tool_1", - "type": "function", - "function": { - "name": "MyPlugin-MyFunction", - "arguments": "{ \"index\": 3 }", - "output": "test" - } - } - ] - }, - "usage": { - "prompt_tokens": 123, - "completion_tokens": 456, - "total_tokens": 579 - } - } - ], - "first_id": "step_abc123", - "last_id": "step_abc456", - "has_more": false - } - """; - - public const string ToolResponse = "{ }"; - - public const string GetImageMessage = - """ - { - "id": "msg_abc123", - "object": "thread.message", - "created_at": 1699017614, - "thread_id": "thread_abc123", - "role": "user", - "content": [ - { - "type": "image_file", - "image_file": { - "file_id": "file_123" - } - } - ], - "assistant_id": "asst_abc123", - "run_id": "run_abc123" + "last_error": null } """; - public const string GetTextMessage = + public const string DeleteFile = """ { - "id": "msg_abc123", - "object": "thread.message", - "created_at": 1699017614, - "thread_id": "thread_abc123", - "role": "user", - "content": [ - { - "type": "text", - "text": { - "value": "How does AI work? Explain it in simple terms.", - "annotations": [] - } - } - ], - "assistant_id": "asst_abc123", - "run_id": "run_abc123" + "id": "#file_1", + "object": "vector_store.file.deleted", + "deleted": true } """; - public const string GetTextMessageWithAnnotation = + public const string DeleteStore = """ { - "id": "msg_abc123", - "object": "thread.message", - "created_at": 1699017614, - "thread_id": "thread_abc123", - "role": "user", - "content": [ - { - "type": "text", - "text": { - "value": "How does AI work? Explain it in simple terms.**f1", - "annotations": [ - { - "type": "file_citation", - "text": "**f1", - "file_citation": { - "file_id": "file_123", - "quote": "does" - }, - "start_index": 3, - "end_index": 6 - } - ] - } - } - ], - "assistant_id": "asst_abc123", - "run_id": "run_abc123" + "id": "vs_abc123", + "object": "vector_store.deleted", + "deleted": true } """; - public const string ListAgentsPageMore = + public const string ListFiles = """ { "object": "list", "data": [ { - "id": "asst_abc123", - "object": "assistant", - "created_at": 1698982736, - "name": "Coding Tutor", - "description": null, - "model": "gpt-4-turbo", - "instructions": "You are a helpful assistant designed to make me better at coding!", - "tools": [], - "metadata": {} + "id": "file-abc123", + "object": "vector_store.file", + "created_at": 1699061776, + "vector_store_id": "vs_abc123" }, { - "id": "asst_abc456", - "object": "assistant", - "created_at": 1698982718, - "name": "My Assistant", - "description": null, - "model": "gpt-4-turbo", - "instructions": "You are a helpful assistant designed to make me better at coding!", - "tools": [], - "metadata": {} - }, - { - "id": "asst_abc789", - "object": "assistant", - "created_at": 1698982643, - "name": null, - "description": null, - "model": "gpt-4-turbo", - "instructions": null, - "tools": [], - "metadata": {} + "id": "file-abc456", + "object": "vector_store.file", + "created_at": 1699061776, + "vector_store_id": "vs_abc123" } ], - "first_id": "asst_abc123", - "last_id": "asst_abc789", - "has_more": true - } - """; - - public const string ListAgentsPageFinal = - """ - { - "object": "list", - "data": [ - { - "id": "asst_abc789", - "object": "assistant", - "created_at": 1698982736, - "name": "Coding Tutor", - "description": null, - "model": "gpt-4-turbo", - "instructions": "You are a helpful assistant designed to make me better at coding!", - "tools": [], - "metadata": {} - } - ], - "first_id": "asst_abc789", - "last_id": "asst_abc789", + "first_id": "file-abc123", + "last_id": "file-abc456", "has_more": false - } + } """; - public const string ListMessagesPageMore = + public const string ListStores = """ { "object": "list", "data": [ { - "id": "msg_abc123", - "object": "thread.message", - "created_at": 1699016383, - "thread_id": "thread_abc123", - "role": "user", - "content": [ - { - "type": "text", - "text": { - "value": "How does AI work? Explain it in simple terms.", - "annotations": [] - } - } - ], - "file_ids": [], - "assistant_id": null, - "run_id": null, - "metadata": {} + "id": "vs_abc123", + "object": "vector_store", + "created_at": 1699061776, + "name": "Support FAQ", + "bytes": 139920, + "file_counts": { + "in_progress": 0, + "completed": 3, + "failed": 0, + "cancelled": 0, + "total": 3 + } }, { - "id": "msg_abc456", - "object": "thread.message", - "created_at": 1699016383, - "thread_id": "thread_abc123", - "role": "user", - "content": [ - { - "type": "text", - "text": { - "value": "Hello, what is AI?", - "annotations": [] - } - } - ], - "file_ids": [ - "file-abc123" - ], - "assistant_id": null, - "run_id": null, - "metadata": {} - } - ], - "first_id": "msg_abc123", - "last_id": "msg_abc456", - "has_more": true - } - """; - - public const string ListMessagesPageFinal = - """ - { - "object": "list", - "data": [ - { - "id": "msg_abc789", - "object": "thread.message", - "created_at": 1699016383, - "thread_id": "thread_abc123", - "role": "user", - "content": [ - { - "type": "text", - "text": { - "value": "How does AI work? Explain it in simple terms.", - "annotations": [] - } - } - ], - "file_ids": [], - "assistant_id": null, - "run_id": null, - "metadata": {} + "id": "vs_abc456", + "object": "vector_store", + "created_at": 1699061776, + "name": "Support FAQ v2", + "bytes": 139920, + "file_counts": { + "in_progress": 0, + "completed": 3, + "failed": 0, + "cancelled": 0, + "total": 3 + } } ], - "first_id": "msg_abc789", - "last_id": "msg_abc789", + "first_id": "vs_abc123", + "last_id": "vs_abc456", "has_more": false - } + } """; } } From 4d2914be632880036b81b8d1962b69e3fd37166e Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 8 Jul 2024 14:01:34 -0700 Subject: [PATCH 030/121] Partial arc optimization --- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 10 ++- .../OpenAI/OpenAIAssistantAgentTests.cs | 74 ++++++++++++++++++- 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 8c7ee432246e..98d6768c6a42 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -306,6 +306,10 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod settings = JsonSerializer.Deserialize(settingsJson); } + IReadOnlyList? fileIds = (IReadOnlyList?)model.ToolResources?.CodeInterpreter?.FileIds; + string? vectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.Single(); + bool enableJsonResponse = model.ResponseFormat is not null && model.ResponseFormat == AssistantResponseFormat.JsonObject; + return new() { @@ -313,14 +317,14 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod Name = model.Name, Description = model.Description, Instructions = model.Instructions, - CodeInterpterFileIds = (IReadOnlyList?)(model.ToolResources?.CodeInterpreter?.FileIds), + CodeInterpterFileIds = fileIds, EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), Metadata = model.Metadata, ModelName = model.Model, - EnableJsonResponse = model.ResponseFormat is not null && model.ResponseFormat == AssistantResponseFormat.JsonObject, + EnableJsonResponse = enableJsonResponse, TopP = model.NucleusSamplingFactor, Temperature = model.Temperature, - VectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.Single(), + VectorStoreId = vectorStoreId, ExecutionSettings = settings, }; } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index 01285e2372a2..3d67537cb590 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -85,11 +85,10 @@ await OpenAIAssistantAgent.CreateAsync( } /// - /// Verify the invocation and response of - /// for an agent that has all properties defined.. + /// %%% /// [Fact] - public async Task VerifyOpenAIAssistantAgentCreationEverythingAsync() + public async Task VerifyOpenAIAssistantAgentCreationEverythingAsync() // %%% NAME { OpenAIAssistantDefinition definition = new() @@ -113,6 +112,75 @@ await OpenAIAssistantAgent.CreateAsync( Assert.True(agent.Tools.OfType().Any()); Assert.True(agent.Tools.OfType().Any()); Assert.Equal("#vs", agent.Definition.VectorStoreId); + Assert.Null(agent.Definition.CodeInterpterFileIds); + Assert.NotNull(agent.Definition.Metadata); + Assert.NotEmpty(agent.Definition.Metadata); + } + + /// + /// %%% + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationEverything2Async() // %%% NAME + { + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + EnableCodeInterpreter = true, + CodeInterpterFileIds = ["file1", "file2"], + Metadata = new Dictionary() { { "a", "1" } }, + }; + + this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentWithEverything); + + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + this._emptyKernel, + this.CreateTestConfiguration(), + definition); + + Assert.NotNull(agent); + Assert.Equal(2, agent.Tools.Count); + Assert.True(agent.Tools.OfType().Any()); + Assert.True(agent.Tools.OfType().Any()); + //Assert.Null(agent.Definition.VectorStoreId); // %%% SETUP + //Assert.Null(agent.Definition.CodeInterpterFileIds); // %%% SETUP + Assert.NotNull(agent.Definition.Metadata); + Assert.NotEmpty(agent.Definition.Metadata); + } + + /// + /// %%% + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationEverything3Async() // %%% NAME + { + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + EnableCodeInterpreter = false, + EnableJsonResponse = true, + CodeInterpterFileIds = ["file1", "file2"], + Metadata = new Dictionary() { { "a", "1" } }, + ExecutionSettings = new(), + }; + + this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentWithEverything); + + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + this._emptyKernel, + this.CreateTestConfiguration(), + definition); + + Assert.NotNull(agent); + Assert.Equal(2, agent.Tools.Count); + Assert.True(agent.Tools.OfType().Any()); + Assert.True(agent.Tools.OfType().Any()); + //Assert.Null(agent.Definition.VectorStoreId); // %%% SETUP + //Assert.Null(agent.Definition.CodeInterpterFileIds); // %%% SETUP Assert.NotNull(agent.Definition.Metadata); Assert.NotEmpty(agent.Definition.Metadata); } From 5eba985d208038baeabaadf3659f075e0ea8aba9 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 08:51:11 -0700 Subject: [PATCH 031/121] Resolve merge from parent --- .../OpenAI/Logging/AssistantThreadActionsLogMessages.cs | 2 +- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs b/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs index bc7c8d9919f0..288abadde2dd 100644 --- a/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs +++ b/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Diagnostics.CodeAnalysis; -using Azure.AI.OpenAI.Assistants; using Microsoft.Extensions.Logging; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 3016324a1920..182acac7c9c2 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -262,8 +262,8 @@ protected override async Task CreateChannelAsync(CancellationToken this.Logger.LogInformation("[{MethodName}] Created assistant thread: {ThreadId}", nameof(CreateChannelAsync), thread.Id); - return - new OpenAIAssistantChannel(this._client, thread.Id) + OpenAIAssistantChannel channel = + new (this._client, thread.Id) { Logger = this.LoggerFactory.CreateLogger() }; From 696652d5376733cbe743a3f5be1dfdd1a4e77a58 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 09:06:53 -0700 Subject: [PATCH 032/121] Resolve breaking changes (first pass) --- .../Agents/ComplexChat_NestedShopper.cs | 3 +- .../Concepts/Agents/Legacy_AgentTools.cs | 9 ++--- .../Concepts/Agents/MixedChat_Agents.cs | 10 ++++-- .../Agents/OpenAIAssistant_ChartMaker.cs | 18 ++++++---- .../Agents/OpenAIAssistant_CodeInterpreter.cs | 14 +++++--- .../OpenAIAssistant_FileManipulation.cs | 23 ++++++------ ...ieval.cs => OpenAIAssistant_FileSearch.cs} | 30 +++++++++++----- dotnet/samples/Concepts/Concepts.csproj | 36 ++----------------- .../Agents/Experimental.Agents.csproj | 2 +- .../Agents/Extensions/OpenAIRestExtensions.cs | 3 +- .../src/Experimental/Agents/Internal/Agent.cs | 2 +- 11 files changed, 75 insertions(+), 75 deletions(-) rename dotnet/samples/Concepts/Agents/{OpenAIAssistant_Retrieval.cs => OpenAIAssistant_FileSearch.cs} (66%) diff --git a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs index aae984906ba3..d75e4fad7026 100644 --- a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs +++ b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs @@ -5,6 +5,7 @@ using Microsoft.SemanticKernel.Agents.Chat; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI.Chat; using Resources; namespace Agents; @@ -98,7 +99,7 @@ public async Task NestedChatWithAggregatorAgentAsync() { Console.WriteLine($"! {Model}"); - OpenAIPromptExecutionSettings jsonSettings = new() { ResponseFormat = ChatCompletionsResponseFormat.JsonObject }; + OpenAIPromptExecutionSettings jsonSettings = new() { ResponseFormat = ChatResponseFormat.JsonObject }; OpenAIPromptExecutionSettings autoInvokeSettings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }; ChatCompletionAgent internalLeaderAgent = CreateAgent(InternalLeaderName, InternalLeaderInstructions); diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs index 66d93ecc88d9..c285810fcb74 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs @@ -165,13 +165,8 @@ async Task InvokeAgentAsync(IAgent agent, string question) } } - private static Kernel CreateFileEnabledKernel() - { - return - ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? - Kernel.CreateBuilder().AddOpenAIFiles(TestConfiguration.OpenAI.ApiKey).Build() : - Kernel.CreateBuilder().AddAzureOpenAIFiles(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey).Build(); - } + private static Kernel CreateFileEnabledKernel() => + Kernel.CreateBuilder().AddOpenAIFiles(TestConfiguration.OpenAI.ApiKey).Build(); private static AgentBuilder CreateAgentBuilder() { diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs index d3a894dd6c8e..20769fa030b7 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs @@ -47,12 +47,12 @@ public async Task ChatWithOpenAIAssistantAgentAndChatCompletionAgentAsync() OpenAIAssistantAgent agentWriter = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: new(this.ApiKey, this.Endpoint), + config: this.GetOpenAIConfiguration(), definition: new() { Instructions = CopyWriterInstructions, Name = CopyWriterName, - ModelId = this.Model, + ModelName = this.Model, }); // Create a chat for agent interaction. @@ -94,4 +94,10 @@ private sealed class ApprovalTerminationStrategy : TerminationStrategy protected override Task ShouldAgentTerminateAsync(Agent agent, IReadOnlyList history, CancellationToken cancellationToken) => Task.FromResult(history[history.Count - 1].Content?.Contains("approve", StringComparison.OrdinalIgnoreCase) ?? false); } + + private OpenAIServiceConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index ef5ba80154fa..63ed511742f8 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -27,17 +27,17 @@ public async Task GenerateChartWithOpenAIAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: new(this.ApiKey, this.Endpoint), + config: GetOpenAIConfiguration(), new() { Instructions = AgentInstructions, Name = AgentName, EnableCodeInterpreter = true, - ModelId = this.Model, + ModelName = this.Model, }); // Create a chat for agent interaction. - AgentGroupChat chat = new(); + var chat = new AgentGroupChat(); // Respond to user input try @@ -54,7 +54,7 @@ Others 23 373 156 552 Sum 426 1622 856 2904 """); - await InvokeAgentAsync("Can you regenerate this same chart using the category names as the bar colors?"); + await InvokeAgentAsync("Can you regenerate this same chart using the category names as the bar colors?"); // %%% WHY NOT ??? } finally { @@ -68,18 +68,24 @@ async Task InvokeAgentAsync(string input) Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - await foreach (ChatMessageContent message in chat.InvokeAsync(agent)) + await foreach (var message in chat.InvokeAsync(agent)) { if (!string.IsNullOrWhiteSpace(message.Content)) { Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); } - foreach (FileReferenceContent fileReference in message.Items.OfType()) + foreach (var fileReference in message.Items.OfType()) { Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: @{fileReference.FileId}"); } } } } + + private OpenAIServiceConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs index 75b237489025..646c1f244967 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs @@ -11,7 +11,7 @@ namespace Agents; /// public class OpenAIAssistant_CodeInterpreter(ITestOutputHelper output) : BaseTest(output) { - protected override bool ForceOpenAI => true; + protected override bool ForceOpenAI => false; [Fact] public async Task UseCodeInterpreterToolWithOpenAIAssistantAgentAsync() @@ -20,15 +20,15 @@ public async Task UseCodeInterpreterToolWithOpenAIAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: new(this.ApiKey, this.Endpoint), + config: GetOpenAIConfiguration(), new() { EnableCodeInterpreter = true, // Enable code-interpreter - ModelId = this.Model, + ModelName = this.Model, }); // Create a chat for agent interaction. - AgentGroupChat chat = new(); + var chat = new AgentGroupChat(); // Respond to user input try @@ -53,4 +53,10 @@ async Task InvokeAgentAsync(string input) } } } + + private OpenAIServiceConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index 8e64006ee9d3..a0fa5a074694 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -23,24 +23,21 @@ public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTe public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() { OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); - OpenAIFileReference uploadFile = await fileService.UploadContentAsync( new BinaryContent(await EmbeddedResource.ReadAllAsync("sales.csv"), mimeType: "text/plain"), new OpenAIFileUploadExecutionSettings("sales.csv", OpenAIFilePurpose.Assistants)); - Console.WriteLine(this.ApiKey); - // Define the agent OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: new(this.ApiKey, this.Endpoint), + config: GetOpenAIConfiguration(), new() { + CodeInterpterFileIds = [uploadFile.Id], EnableCodeInterpreter = true, // Enable code-interpreter - ModelId = this.Model, - FileIds = [uploadFile.Id] // Associate uploaded file + ModelName = this.Model, }); // Create a chat for agent interaction. @@ -62,15 +59,15 @@ await OpenAIAssistantAgent.CreateAsync( // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + chat.AddChatMessage(new(AuthorRole.User, input)); Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - await foreach (ChatMessageContent content in chat.InvokeAsync(agent)) + await foreach (ChatMessageContent message in chat.InvokeAsync(agent)) { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); - foreach (AnnotationContent annotation in content.Items.OfType()) + foreach (AnnotationContent annotation in message.Items.OfType()) { Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}"); BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!); @@ -80,4 +77,10 @@ async Task InvokeAgentAsync(string input) } } } + + private OpenAIServiceConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_Retrieval.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs similarity index 66% rename from dotnet/samples/Concepts/Agents/OpenAIAssistant_Retrieval.cs rename to dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs index 6f30b6974ff7..9fef5a0c5830 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_Retrieval.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs @@ -4,6 +4,7 @@ using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI.VectorStores; using Resources; namespace Agents; @@ -11,7 +12,7 @@ namespace Agents; /// /// Demonstrate using retrieval on . /// -public class OpenAIAssistant_Retrieval(ITestOutputHelper output) : BaseTest(output) +public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseTest(output) { /// /// Retrieval tool not supported on Azure OpenAI. @@ -22,25 +23,30 @@ public class OpenAIAssistant_Retrieval(ITestOutputHelper output) : BaseTest(outp public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync() { OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); - OpenAIFileReference uploadFile = await fileService.UploadContentAsync(new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"), new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants)); + VectorStore vectorStore = + await new OpenAIVectorStoreBuilder(GetOpenAIConfiguration()) + .AddFile(uploadFile.Id) + .CreateAsync(); + + OpenAIVectorStore openAIStore = new(vectorStore.Id, GetOpenAIConfiguration()); + // Define the agent OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: new(this.ApiKey, this.Endpoint), + config: GetOpenAIConfiguration(), new() { - EnableRetrieval = true, // Enable retrieval - ModelId = this.Model, - FileIds = [uploadFile.Id] // Associate uploaded file + ModelName = this.Model, + VectorStoreId = vectorStore.Id, }); // Create a chat for agent interaction. - AgentGroupChat chat = new(); + var chat = new AgentGroupChat(); // Respond to user input try @@ -52,6 +58,8 @@ await OpenAIAssistantAgent.CreateAsync( finally { await agent.DeleteAsync(); + await openAIStore.DeleteAsync(); + await fileService.DeleteFileAsync(uploadFile.Id); } // Local function to invoke agent and display the conversation messages. @@ -61,10 +69,16 @@ async Task InvokeAgentAsync(string input) Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - await foreach (ChatMessageContent content in chat.InvokeAsync(agent)) + await foreach (var content in chat.InvokeAsync(agent)) { Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); } } } + + private OpenAIServiceConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index 1ef7ed607096..7233a8131715 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -47,8 +47,8 @@ - - + + @@ -64,7 +64,7 @@ - + @@ -105,21 +105,6 @@ - - - - - - - - - - - - - - - @@ -216,21 +201,6 @@ - - - - - - - - - - - - - - - diff --git a/dotnet/src/Experimental/Agents/Experimental.Agents.csproj b/dotnet/src/Experimental/Agents/Experimental.Agents.csproj index b5038dbabde9..648d6b7fd02f 100644 --- a/dotnet/src/Experimental/Agents/Experimental.Agents.csproj +++ b/dotnet/src/Experimental/Agents/Experimental.Agents.csproj @@ -20,7 +20,7 @@ - + diff --git a/dotnet/src/Experimental/Agents/Extensions/OpenAIRestExtensions.cs b/dotnet/src/Experimental/Agents/Extensions/OpenAIRestExtensions.cs index aa4f324490d8..c099f7d609e4 100644 --- a/dotnet/src/Experimental/Agents/Extensions/OpenAIRestExtensions.cs +++ b/dotnet/src/Experimental/Agents/Extensions/OpenAIRestExtensions.cs @@ -4,7 +4,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Microsoft.SemanticKernel.Connectors.OpenAI; using Microsoft.SemanticKernel.Experimental.Agents.Exceptions; using Microsoft.SemanticKernel.Experimental.Agents.Internal; using Microsoft.SemanticKernel.Http; @@ -92,7 +91,7 @@ private static void AddHeaders(this HttpRequestMessage request, OpenAIRestContex { request.Headers.Add(HeaderNameOpenAIAssistant, HeaderOpenAIValueAssistant); request.Headers.Add(HeaderNameUserAgent, HttpHeaderConstant.Values.UserAgent); - request.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIFileService))); + request.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(IAgent))); if (context.HasVersion) { diff --git a/dotnet/src/Experimental/Agents/Internal/Agent.cs b/dotnet/src/Experimental/Agents/Internal/Agent.cs index ae64af04d39a..c078703cda02 100644 --- a/dotnet/src/Experimental/Agents/Internal/Agent.cs +++ b/dotnet/src/Experimental/Agents/Internal/Agent.cs @@ -121,7 +121,7 @@ internal Agent( this.Kernel = this._restContext.HasVersion ? builder.AddAzureOpenAIChatCompletion(this._model.Model, this.GetAzureRootEndpoint(), this._restContext.ApiKey).Build() : - builder.AddOpenAIChatCompletion(this._model.Model, this._restContext.ApiKey).Build(); + new(); // %%% HACK builder.AddOpenAIChatCompletion(this._model.Model, this._restContext.ApiKey).Build(); if (plugins is not null) { From bb98e448500abb495d3b2a21109fa315dd4a9446 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 09:15:07 -0700 Subject: [PATCH 033/121] Remove ConceptsV2 shim --- .../Agents/OpenAIAssistant_ChartMaker.cs | 91 --- .../Agents/OpenAIAssistant_CodeInterpreter.cs | 62 -- .../OpenAIAssistant_FileManipulation.cs | 86 --- .../Agents/OpenAIAssistant_FileSearch.cs | 84 --- dotnet/samples/ConceptsV2/ConceptsV2.csproj | 77 -- dotnet/samples/ConceptsV2/Resources/sales.csv | 701 ------------------ .../ConceptsV2/Resources/travelinfo.txt | 217 ------ 7 files changed, 1318 deletions(-) delete mode 100644 dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs delete mode 100644 dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs delete mode 100644 dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs delete mode 100644 dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs delete mode 100644 dotnet/samples/ConceptsV2/ConceptsV2.csproj delete mode 100644 dotnet/samples/ConceptsV2/Resources/sales.csv delete mode 100644 dotnet/samples/ConceptsV2/Resources/travelinfo.txt diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs deleted file mode 100644 index 63ed511742f8..000000000000 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_ChartMaker.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.ChatCompletion; - -namespace Agents; - -/// -/// Demonstrate using code-interpreter with to -/// produce image content displays the requested charts. -/// -public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseTest(output) -{ - /// - /// Target Open AI services. - /// - protected override bool ForceOpenAI => true; - - private const string AgentName = "ChartMaker"; - private const string AgentInstructions = "Create charts as requested without explanation."; - - [Fact] - public async Task GenerateChartWithOpenAIAssistantAgentAsync() - { - // Define the agent - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - kernel: new(), - config: GetOpenAIConfiguration(), - new() - { - Instructions = AgentInstructions, - Name = AgentName, - EnableCodeInterpreter = true, - ModelName = this.Model, - }); - - // Create a chat for agent interaction. - var chat = new AgentGroupChat(); - - // Respond to user input - try - { - await InvokeAgentAsync( - """ - Display this data using a bar-chart: - - Banding Brown Pink Yellow Sum - X00000 339 433 126 898 - X00300 48 421 222 691 - X12345 16 395 352 763 - Others 23 373 156 552 - Sum 426 1622 856 2904 - """); - - await InvokeAgentAsync("Can you regenerate this same chart using the category names as the bar colors?"); // %%% WHY NOT ??? - } - finally - { - await agent.DeleteAsync(); - } - - // Local function to invoke agent and display the conversation messages. - async Task InvokeAgentAsync(string input) - { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); - - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (var message in chat.InvokeAsync(agent)) - { - if (!string.IsNullOrWhiteSpace(message.Content)) - { - Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); - } - - foreach (var fileReference in message.Items.OfType()) - { - Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: @{fileReference.FileId}"); - } - } - } - } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); -} diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs deleted file mode 100644 index 646c1f244967..000000000000 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_CodeInterpreter.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.ChatCompletion; - -namespace Agents; - -/// -/// Demonstrate using code-interpreter on . -/// -public class OpenAIAssistant_CodeInterpreter(ITestOutputHelper output) : BaseTest(output) -{ - protected override bool ForceOpenAI => false; - - [Fact] - public async Task UseCodeInterpreterToolWithOpenAIAssistantAgentAsync() - { - // Define the agent - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - kernel: new(), - config: GetOpenAIConfiguration(), - new() - { - EnableCodeInterpreter = true, // Enable code-interpreter - ModelName = this.Model, - }); - - // Create a chat for agent interaction. - var chat = new AgentGroupChat(); - - // Respond to user input - try - { - await InvokeAgentAsync("Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?"); - } - finally - { - await agent.DeleteAsync(); - } - - // Local function to invoke agent and display the conversation messages. - async Task InvokeAgentAsync(string input) - { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); - - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (var content in chat.InvokeAsync(agent)) - { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); - } - } - } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); -} diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs deleted file mode 100644 index a0fa5a074694..000000000000 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileManipulation.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System.Text; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.OpenAI; -using Resources; - -namespace Agents; - -/// -/// Demonstrate using code-interpreter to manipulate and generate csv files with . -/// -public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTest(output) -{ - /// - /// Target OpenAI services. - /// - protected override bool ForceOpenAI => true; - - [Fact] - public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() - { - OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); - OpenAIFileReference uploadFile = - await fileService.UploadContentAsync( - new BinaryContent(await EmbeddedResource.ReadAllAsync("sales.csv"), mimeType: "text/plain"), - new OpenAIFileUploadExecutionSettings("sales.csv", OpenAIFilePurpose.Assistants)); - - // Define the agent - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - kernel: new(), - config: GetOpenAIConfiguration(), - new() - { - CodeInterpterFileIds = [uploadFile.Id], - EnableCodeInterpreter = true, // Enable code-interpreter - ModelName = this.Model, - }); - - // Create a chat for agent interaction. - AgentGroupChat chat = new(); - - // Respond to user input - try - { - await InvokeAgentAsync("Which segment had the most sales?"); - await InvokeAgentAsync("List the top 5 countries that generated the most profit."); - await InvokeAgentAsync("Create a tab delimited file report of profit by each country per month."); - } - finally - { - await agent.DeleteAsync(); - await fileService.DeleteFileAsync(uploadFile.Id); - } - - // Local function to invoke agent and display the conversation messages. - async Task InvokeAgentAsync(string input) - { - chat.AddChatMessage(new(AuthorRole.User, input)); - - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (ChatMessageContent message in chat.InvokeAsync(agent)) - { - Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); - - foreach (AnnotationContent annotation in message.Items.OfType()) - { - Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}"); - BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!); - byte[] byteContent = fileContent.Data?.ToArray() ?? []; - Console.WriteLine(Encoding.Default.GetString(byteContent)); - } - } - } - } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); -} diff --git a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs deleted file mode 100644 index 9fef5a0c5830..000000000000 --- a/dotnet/samples/ConceptsV2/Agents/OpenAIAssistant_FileSearch.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.OpenAI; -using OpenAI.VectorStores; -using Resources; - -namespace Agents; - -/// -/// Demonstrate using retrieval on . -/// -public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseTest(output) -{ - /// - /// Retrieval tool not supported on Azure OpenAI. - /// - protected override bool ForceOpenAI => true; - - [Fact] - public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync() - { - OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); - OpenAIFileReference uploadFile = - await fileService.UploadContentAsync(new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"), - new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants)); - - VectorStore vectorStore = - await new OpenAIVectorStoreBuilder(GetOpenAIConfiguration()) - .AddFile(uploadFile.Id) - .CreateAsync(); - - OpenAIVectorStore openAIStore = new(vectorStore.Id, GetOpenAIConfiguration()); - - // Define the agent - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - kernel: new(), - config: GetOpenAIConfiguration(), - new() - { - ModelName = this.Model, - VectorStoreId = vectorStore.Id, - }); - - // Create a chat for agent interaction. - var chat = new AgentGroupChat(); - - // Respond to user input - try - { - await InvokeAgentAsync("Where did sam go?"); - await InvokeAgentAsync("When does the flight leave Seattle?"); - await InvokeAgentAsync("What is the hotel contact info at the destination?"); - } - finally - { - await agent.DeleteAsync(); - await openAIStore.DeleteAsync(); - await fileService.DeleteFileAsync(uploadFile.Id); - } - - // Local function to invoke agent and display the conversation messages. - async Task InvokeAgentAsync(string input) - { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); - - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (var content in chat.InvokeAsync(agent)) - { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); - } - } - } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); -} diff --git a/dotnet/samples/ConceptsV2/ConceptsV2.csproj b/dotnet/samples/ConceptsV2/ConceptsV2.csproj deleted file mode 100644 index 8eee3868170b..000000000000 --- a/dotnet/samples/ConceptsV2/ConceptsV2.csproj +++ /dev/null @@ -1,77 +0,0 @@ - - - - Concepts - - net8.0 - enable - false - true - - $(NoWarn);CS8618,IDE0009,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110 - Library - 5ee045b0-aea3-4f08-8d31-32d1a6f8fed0 - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - - - Always - - - diff --git a/dotnet/samples/ConceptsV2/Resources/sales.csv b/dotnet/samples/ConceptsV2/Resources/sales.csv deleted file mode 100644 index 4a355d11bf83..000000000000 --- a/dotnet/samples/ConceptsV2/Resources/sales.csv +++ /dev/null @@ -1,701 +0,0 @@ -Segment,Country,Product,Units Sold,Sale Price,Gross Sales,Discounts,Sales,COGS,Profit,Date,Month Number,Month Name,Year -Government,Canada,Carretera,1618.5,20.00,32370.00,0.00,32370.00,16185.00,16185.00,1/1/2014,1,January,2014 -Government,Germany,Carretera,1321,20.00,26420.00,0.00,26420.00,13210.00,13210.00,1/1/2014,1,January,2014 -Midmarket,France,Carretera,2178,15.00,32670.00,0.00,32670.00,21780.00,10890.00,6/1/2014,6,June,2014 -Midmarket,Germany,Carretera,888,15.00,13320.00,0.00,13320.00,8880.00,4440.00,6/1/2014,6,June,2014 -Midmarket,Mexico,Carretera,2470,15.00,37050.00,0.00,37050.00,24700.00,12350.00,6/1/2014,6,June,2014 -Government,Germany,Carretera,1513,350.00,529550.00,0.00,529550.00,393380.00,136170.00,12/1/2014,12,December,2014 -Midmarket,Germany,Montana,921,15.00,13815.00,0.00,13815.00,9210.00,4605.00,3/1/2014,3,March,2014 -Channel Partners,Canada,Montana,2518,12.00,30216.00,0.00,30216.00,7554.00,22662.00,6/1/2014,6,June,2014 -Government,France,Montana,1899,20.00,37980.00,0.00,37980.00,18990.00,18990.00,6/1/2014,6,June,2014 -Channel Partners,Germany,Montana,1545,12.00,18540.00,0.00,18540.00,4635.00,13905.00,6/1/2014,6,June,2014 -Midmarket,Mexico,Montana,2470,15.00,37050.00,0.00,37050.00,24700.00,12350.00,6/1/2014,6,June,2014 -Enterprise,Canada,Montana,2665.5,125.00,333187.50,0.00,333187.50,319860.00,13327.50,7/1/2014,7,July,2014 -Small Business,Mexico,Montana,958,300.00,287400.00,0.00,287400.00,239500.00,47900.00,8/1/2014,8,August,2014 -Government,Germany,Montana,2146,7.00,15022.00,0.00,15022.00,10730.00,4292.00,9/1/2014,9,September,2014 -Enterprise,Canada,Montana,345,125.00,43125.00,0.00,43125.00,41400.00,1725.00,10/1/2013,10,October,2013 -Midmarket,United States of America,Montana,615,15.00,9225.00,0.00,9225.00,6150.00,3075.00,12/1/2014,12,December,2014 -Government,Canada,Paseo,292,20.00,5840.00,0.00,5840.00,2920.00,2920.00,2/1/2014,2,February,2014 -Midmarket,Mexico,Paseo,974,15.00,14610.00,0.00,14610.00,9740.00,4870.00,2/1/2014,2,February,2014 -Channel Partners,Canada,Paseo,2518,12.00,30216.00,0.00,30216.00,7554.00,22662.00,6/1/2014,6,June,2014 -Government,Germany,Paseo,1006,350.00,352100.00,0.00,352100.00,261560.00,90540.00,6/1/2014,6,June,2014 -Channel Partners,Germany,Paseo,367,12.00,4404.00,0.00,4404.00,1101.00,3303.00,7/1/2014,7,July,2014 -Government,Mexico,Paseo,883,7.00,6181.00,0.00,6181.00,4415.00,1766.00,8/1/2014,8,August,2014 -Midmarket,France,Paseo,549,15.00,8235.00,0.00,8235.00,5490.00,2745.00,9/1/2013,9,September,2013 -Small Business,Mexico,Paseo,788,300.00,236400.00,0.00,236400.00,197000.00,39400.00,9/1/2013,9,September,2013 -Midmarket,Mexico,Paseo,2472,15.00,37080.00,0.00,37080.00,24720.00,12360.00,9/1/2014,9,September,2014 -Government,United States of America,Paseo,1143,7.00,8001.00,0.00,8001.00,5715.00,2286.00,10/1/2014,10,October,2014 -Government,Canada,Paseo,1725,350.00,603750.00,0.00,603750.00,448500.00,155250.00,11/1/2013,11,November,2013 -Channel Partners,United States of America,Paseo,912,12.00,10944.00,0.00,10944.00,2736.00,8208.00,11/1/2013,11,November,2013 -Midmarket,Canada,Paseo,2152,15.00,32280.00,0.00,32280.00,21520.00,10760.00,12/1/2013,12,December,2013 -Government,Canada,Paseo,1817,20.00,36340.00,0.00,36340.00,18170.00,18170.00,12/1/2014,12,December,2014 -Government,Germany,Paseo,1513,350.00,529550.00,0.00,529550.00,393380.00,136170.00,12/1/2014,12,December,2014 -Government,Mexico,Velo,1493,7.00,10451.00,0.00,10451.00,7465.00,2986.00,1/1/2014,1,January,2014 -Enterprise,France,Velo,1804,125.00,225500.00,0.00,225500.00,216480.00,9020.00,2/1/2014,2,February,2014 -Channel Partners,Germany,Velo,2161,12.00,25932.00,0.00,25932.00,6483.00,19449.00,3/1/2014,3,March,2014 -Government,Germany,Velo,1006,350.00,352100.00,0.00,352100.00,261560.00,90540.00,6/1/2014,6,June,2014 -Channel Partners,Germany,Velo,1545,12.00,18540.00,0.00,18540.00,4635.00,13905.00,6/1/2014,6,June,2014 -Enterprise,United States of America,Velo,2821,125.00,352625.00,0.00,352625.00,338520.00,14105.00,8/1/2014,8,August,2014 -Enterprise,Canada,Velo,345,125.00,43125.00,0.00,43125.00,41400.00,1725.00,10/1/2013,10,October,2013 -Small Business,Canada,VTT,2001,300.00,600300.00,0.00,600300.00,500250.00,100050.00,2/1/2014,2,February,2014 -Channel Partners,Germany,VTT,2838,12.00,34056.00,0.00,34056.00,8514.00,25542.00,4/1/2014,4,April,2014 -Midmarket,France,VTT,2178,15.00,32670.00,0.00,32670.00,21780.00,10890.00,6/1/2014,6,June,2014 -Midmarket,Germany,VTT,888,15.00,13320.00,0.00,13320.00,8880.00,4440.00,6/1/2014,6,June,2014 -Government,France,VTT,1527,350.00,534450.00,0.00,534450.00,397020.00,137430.00,9/1/2013,9,September,2013 -Small Business,France,VTT,2151,300.00,645300.00,0.00,645300.00,537750.00,107550.00,9/1/2014,9,September,2014 -Government,Canada,VTT,1817,20.00,36340.00,0.00,36340.00,18170.00,18170.00,12/1/2014,12,December,2014 -Government,France,Amarilla,2750,350.00,962500.00,0.00,962500.00,715000.00,247500.00,2/1/2014,2,February,2014 -Channel Partners,United States of America,Amarilla,1953,12.00,23436.00,0.00,23436.00,5859.00,17577.00,4/1/2014,4,April,2014 -Enterprise,Germany,Amarilla,4219.5,125.00,527437.50,0.00,527437.50,506340.00,21097.50,4/1/2014,4,April,2014 -Government,France,Amarilla,1899,20.00,37980.00,0.00,37980.00,18990.00,18990.00,6/1/2014,6,June,2014 -Government,Germany,Amarilla,1686,7.00,11802.00,0.00,11802.00,8430.00,3372.00,7/1/2014,7,July,2014 -Channel Partners,United States of America,Amarilla,2141,12.00,25692.00,0.00,25692.00,6423.00,19269.00,8/1/2014,8,August,2014 -Government,United States of America,Amarilla,1143,7.00,8001.00,0.00,8001.00,5715.00,2286.00,10/1/2014,10,October,2014 -Midmarket,United States of America,Amarilla,615,15.00,9225.00,0.00,9225.00,6150.00,3075.00,12/1/2014,12,December,2014 -Government,France,Paseo,3945,7.00,27615.00,276.15,27338.85,19725.00,7613.85,1/1/2014,1,January,2014 -Midmarket,France,Paseo,2296,15.00,34440.00,344.40,34095.60,22960.00,11135.60,2/1/2014,2,February,2014 -Government,France,Paseo,1030,7.00,7210.00,72.10,7137.90,5150.00,1987.90,5/1/2014,5,May,2014 -Government,France,Velo,639,7.00,4473.00,44.73,4428.27,3195.00,1233.27,11/1/2014,11,November,2014 -Government,Canada,VTT,1326,7.00,9282.00,92.82,9189.18,6630.00,2559.18,3/1/2014,3,March,2014 -Channel Partners,United States of America,Carretera,1858,12.00,22296.00,222.96,22073.04,5574.00,16499.04,2/1/2014,2,February,2014 -Government,Mexico,Carretera,1210,350.00,423500.00,4235.00,419265.00,314600.00,104665.00,3/1/2014,3,March,2014 -Government,United States of America,Carretera,2529,7.00,17703.00,177.03,17525.97,12645.00,4880.97,7/1/2014,7,July,2014 -Channel Partners,Canada,Carretera,1445,12.00,17340.00,173.40,17166.60,4335.00,12831.60,9/1/2014,9,September,2014 -Enterprise,United States of America,Carretera,330,125.00,41250.00,412.50,40837.50,39600.00,1237.50,9/1/2013,9,September,2013 -Channel Partners,France,Carretera,2671,12.00,32052.00,320.52,31731.48,8013.00,23718.48,9/1/2014,9,September,2014 -Channel Partners,Germany,Carretera,766,12.00,9192.00,91.92,9100.08,2298.00,6802.08,10/1/2013,10,October,2013 -Small Business,Mexico,Carretera,494,300.00,148200.00,1482.00,146718.00,123500.00,23218.00,10/1/2013,10,October,2013 -Government,Mexico,Carretera,1397,350.00,488950.00,4889.50,484060.50,363220.00,120840.50,10/1/2014,10,October,2014 -Government,France,Carretera,2155,350.00,754250.00,7542.50,746707.50,560300.00,186407.50,12/1/2014,12,December,2014 -Midmarket,Mexico,Montana,2214,15.00,33210.00,332.10,32877.90,22140.00,10737.90,3/1/2014,3,March,2014 -Small Business,United States of America,Montana,2301,300.00,690300.00,6903.00,683397.00,575250.00,108147.00,4/1/2014,4,April,2014 -Government,France,Montana,1375.5,20.00,27510.00,275.10,27234.90,13755.00,13479.90,7/1/2014,7,July,2014 -Government,Canada,Montana,1830,7.00,12810.00,128.10,12681.90,9150.00,3531.90,8/1/2014,8,August,2014 -Small Business,United States of America,Montana,2498,300.00,749400.00,7494.00,741906.00,624500.00,117406.00,9/1/2013,9,September,2013 -Enterprise,United States of America,Montana,663,125.00,82875.00,828.75,82046.25,79560.00,2486.25,10/1/2013,10,October,2013 -Midmarket,United States of America,Paseo,1514,15.00,22710.00,227.10,22482.90,15140.00,7342.90,2/1/2014,2,February,2014 -Government,United States of America,Paseo,4492.5,7.00,31447.50,314.48,31133.03,22462.50,8670.53,4/1/2014,4,April,2014 -Enterprise,United States of America,Paseo,727,125.00,90875.00,908.75,89966.25,87240.00,2726.25,6/1/2014,6,June,2014 -Enterprise,France,Paseo,787,125.00,98375.00,983.75,97391.25,94440.00,2951.25,6/1/2014,6,June,2014 -Enterprise,Mexico,Paseo,1823,125.00,227875.00,2278.75,225596.25,218760.00,6836.25,7/1/2014,7,July,2014 -Midmarket,Germany,Paseo,747,15.00,11205.00,112.05,11092.95,7470.00,3622.95,9/1/2014,9,September,2014 -Channel Partners,Germany,Paseo,766,12.00,9192.00,91.92,9100.08,2298.00,6802.08,10/1/2013,10,October,2013 -Small Business,United States of America,Paseo,2905,300.00,871500.00,8715.00,862785.00,726250.00,136535.00,11/1/2014,11,November,2014 -Government,France,Paseo,2155,350.00,754250.00,7542.50,746707.50,560300.00,186407.50,12/1/2014,12,December,2014 -Government,France,Velo,3864,20.00,77280.00,772.80,76507.20,38640.00,37867.20,4/1/2014,4,April,2014 -Government,Mexico,Velo,362,7.00,2534.00,25.34,2508.66,1810.00,698.66,5/1/2014,5,May,2014 -Enterprise,Canada,Velo,923,125.00,115375.00,1153.75,114221.25,110760.00,3461.25,8/1/2014,8,August,2014 -Enterprise,United States of America,Velo,663,125.00,82875.00,828.75,82046.25,79560.00,2486.25,10/1/2013,10,October,2013 -Government,Canada,Velo,2092,7.00,14644.00,146.44,14497.56,10460.00,4037.56,11/1/2013,11,November,2013 -Government,Germany,VTT,263,7.00,1841.00,18.41,1822.59,1315.00,507.59,3/1/2014,3,March,2014 -Government,Canada,VTT,943.5,350.00,330225.00,3302.25,326922.75,245310.00,81612.75,4/1/2014,4,April,2014 -Enterprise,United States of America,VTT,727,125.00,90875.00,908.75,89966.25,87240.00,2726.25,6/1/2014,6,June,2014 -Enterprise,France,VTT,787,125.00,98375.00,983.75,97391.25,94440.00,2951.25,6/1/2014,6,June,2014 -Small Business,Germany,VTT,986,300.00,295800.00,2958.00,292842.00,246500.00,46342.00,9/1/2014,9,September,2014 -Small Business,Mexico,VTT,494,300.00,148200.00,1482.00,146718.00,123500.00,23218.00,10/1/2013,10,October,2013 -Government,Mexico,VTT,1397,350.00,488950.00,4889.50,484060.50,363220.00,120840.50,10/1/2014,10,October,2014 -Enterprise,France,VTT,1744,125.00,218000.00,2180.00,215820.00,209280.00,6540.00,11/1/2014,11,November,2014 -Channel Partners,United States of America,Amarilla,1989,12.00,23868.00,238.68,23629.32,5967.00,17662.32,9/1/2013,9,September,2013 -Midmarket,France,Amarilla,321,15.00,4815.00,48.15,4766.85,3210.00,1556.85,11/1/2013,11,November,2013 -Enterprise,Canada,Carretera,742.5,125.00,92812.50,1856.25,90956.25,89100.00,1856.25,4/1/2014,4,April,2014 -Channel Partners,Canada,Carretera,1295,12.00,15540.00,310.80,15229.20,3885.00,11344.20,10/1/2014,10,October,2014 -Small Business,Germany,Carretera,214,300.00,64200.00,1284.00,62916.00,53500.00,9416.00,10/1/2013,10,October,2013 -Government,France,Carretera,2145,7.00,15015.00,300.30,14714.70,10725.00,3989.70,11/1/2013,11,November,2013 -Government,Canada,Carretera,2852,350.00,998200.00,19964.00,978236.00,741520.00,236716.00,12/1/2014,12,December,2014 -Channel Partners,United States of America,Montana,1142,12.00,13704.00,274.08,13429.92,3426.00,10003.92,6/1/2014,6,June,2014 -Government,United States of America,Montana,1566,20.00,31320.00,626.40,30693.60,15660.00,15033.60,10/1/2014,10,October,2014 -Channel Partners,Mexico,Montana,690,12.00,8280.00,165.60,8114.40,2070.00,6044.40,11/1/2014,11,November,2014 -Enterprise,Mexico,Montana,1660,125.00,207500.00,4150.00,203350.00,199200.00,4150.00,11/1/2013,11,November,2013 -Midmarket,Canada,Paseo,2363,15.00,35445.00,708.90,34736.10,23630.00,11106.10,2/1/2014,2,February,2014 -Small Business,France,Paseo,918,300.00,275400.00,5508.00,269892.00,229500.00,40392.00,5/1/2014,5,May,2014 -Small Business,Germany,Paseo,1728,300.00,518400.00,10368.00,508032.00,432000.00,76032.00,5/1/2014,5,May,2014 -Channel Partners,United States of America,Paseo,1142,12.00,13704.00,274.08,13429.92,3426.00,10003.92,6/1/2014,6,June,2014 -Enterprise,Mexico,Paseo,662,125.00,82750.00,1655.00,81095.00,79440.00,1655.00,6/1/2014,6,June,2014 -Channel Partners,Canada,Paseo,1295,12.00,15540.00,310.80,15229.20,3885.00,11344.20,10/1/2014,10,October,2014 -Enterprise,Germany,Paseo,809,125.00,101125.00,2022.50,99102.50,97080.00,2022.50,10/1/2013,10,October,2013 -Enterprise,Mexico,Paseo,2145,125.00,268125.00,5362.50,262762.50,257400.00,5362.50,10/1/2013,10,October,2013 -Channel Partners,France,Paseo,1785,12.00,21420.00,428.40,20991.60,5355.00,15636.60,11/1/2013,11,November,2013 -Small Business,Canada,Paseo,1916,300.00,574800.00,11496.00,563304.00,479000.00,84304.00,12/1/2014,12,December,2014 -Government,Canada,Paseo,2852,350.00,998200.00,19964.00,978236.00,741520.00,236716.00,12/1/2014,12,December,2014 -Enterprise,Canada,Paseo,2729,125.00,341125.00,6822.50,334302.50,327480.00,6822.50,12/1/2014,12,December,2014 -Midmarket,United States of America,Paseo,1925,15.00,28875.00,577.50,28297.50,19250.00,9047.50,12/1/2013,12,December,2013 -Government,United States of America,Paseo,2013,7.00,14091.00,281.82,13809.18,10065.00,3744.18,12/1/2013,12,December,2013 -Channel Partners,France,Paseo,1055,12.00,12660.00,253.20,12406.80,3165.00,9241.80,12/1/2014,12,December,2014 -Channel Partners,Mexico,Paseo,1084,12.00,13008.00,260.16,12747.84,3252.00,9495.84,12/1/2014,12,December,2014 -Government,United States of America,Velo,1566,20.00,31320.00,626.40,30693.60,15660.00,15033.60,10/1/2014,10,October,2014 -Government,Germany,Velo,2966,350.00,1038100.00,20762.00,1017338.00,771160.00,246178.00,10/1/2013,10,October,2013 -Government,Germany,Velo,2877,350.00,1006950.00,20139.00,986811.00,748020.00,238791.00,10/1/2014,10,October,2014 -Enterprise,Germany,Velo,809,125.00,101125.00,2022.50,99102.50,97080.00,2022.50,10/1/2013,10,October,2013 -Enterprise,Mexico,Velo,2145,125.00,268125.00,5362.50,262762.50,257400.00,5362.50,10/1/2013,10,October,2013 -Channel Partners,France,Velo,1055,12.00,12660.00,253.20,12406.80,3165.00,9241.80,12/1/2014,12,December,2014 -Government,Mexico,Velo,544,20.00,10880.00,217.60,10662.40,5440.00,5222.40,12/1/2013,12,December,2013 -Channel Partners,Mexico,Velo,1084,12.00,13008.00,260.16,12747.84,3252.00,9495.84,12/1/2014,12,December,2014 -Enterprise,Mexico,VTT,662,125.00,82750.00,1655.00,81095.00,79440.00,1655.00,6/1/2014,6,June,2014 -Small Business,Germany,VTT,214,300.00,64200.00,1284.00,62916.00,53500.00,9416.00,10/1/2013,10,October,2013 -Government,Germany,VTT,2877,350.00,1006950.00,20139.00,986811.00,748020.00,238791.00,10/1/2014,10,October,2014 -Enterprise,Canada,VTT,2729,125.00,341125.00,6822.50,334302.50,327480.00,6822.50,12/1/2014,12,December,2014 -Government,United States of America,VTT,266,350.00,93100.00,1862.00,91238.00,69160.00,22078.00,12/1/2013,12,December,2013 -Government,Mexico,VTT,1940,350.00,679000.00,13580.00,665420.00,504400.00,161020.00,12/1/2013,12,December,2013 -Small Business,Germany,Amarilla,259,300.00,77700.00,1554.00,76146.00,64750.00,11396.00,3/1/2014,3,March,2014 -Small Business,Mexico,Amarilla,1101,300.00,330300.00,6606.00,323694.00,275250.00,48444.00,3/1/2014,3,March,2014 -Enterprise,Germany,Amarilla,2276,125.00,284500.00,5690.00,278810.00,273120.00,5690.00,5/1/2014,5,May,2014 -Government,Germany,Amarilla,2966,350.00,1038100.00,20762.00,1017338.00,771160.00,246178.00,10/1/2013,10,October,2013 -Government,United States of America,Amarilla,1236,20.00,24720.00,494.40,24225.60,12360.00,11865.60,11/1/2014,11,November,2014 -Government,France,Amarilla,941,20.00,18820.00,376.40,18443.60,9410.00,9033.60,11/1/2014,11,November,2014 -Small Business,Canada,Amarilla,1916,300.00,574800.00,11496.00,563304.00,479000.00,84304.00,12/1/2014,12,December,2014 -Enterprise,France,Carretera,4243.5,125.00,530437.50,15913.13,514524.38,509220.00,5304.38,4/1/2014,4,April,2014 -Government,Germany,Carretera,2580,20.00,51600.00,1548.00,50052.00,25800.00,24252.00,4/1/2014,4,April,2014 -Small Business,Germany,Carretera,689,300.00,206700.00,6201.00,200499.00,172250.00,28249.00,6/1/2014,6,June,2014 -Channel Partners,United States of America,Carretera,1947,12.00,23364.00,700.92,22663.08,5841.00,16822.08,9/1/2014,9,September,2014 -Channel Partners,Canada,Carretera,908,12.00,10896.00,326.88,10569.12,2724.00,7845.12,12/1/2013,12,December,2013 -Government,Germany,Montana,1958,7.00,13706.00,411.18,13294.82,9790.00,3504.82,2/1/2014,2,February,2014 -Channel Partners,France,Montana,1901,12.00,22812.00,684.36,22127.64,5703.00,16424.64,6/1/2014,6,June,2014 -Government,France,Montana,544,7.00,3808.00,114.24,3693.76,2720.00,973.76,9/1/2014,9,September,2014 -Government,Germany,Montana,1797,350.00,628950.00,18868.50,610081.50,467220.00,142861.50,9/1/2013,9,September,2013 -Enterprise,France,Montana,1287,125.00,160875.00,4826.25,156048.75,154440.00,1608.75,12/1/2014,12,December,2014 -Enterprise,Germany,Montana,1706,125.00,213250.00,6397.50,206852.50,204720.00,2132.50,12/1/2014,12,December,2014 -Small Business,France,Paseo,2434.5,300.00,730350.00,21910.50,708439.50,608625.00,99814.50,1/1/2014,1,January,2014 -Enterprise,Canada,Paseo,1774,125.00,221750.00,6652.50,215097.50,212880.00,2217.50,3/1/2014,3,March,2014 -Channel Partners,France,Paseo,1901,12.00,22812.00,684.36,22127.64,5703.00,16424.64,6/1/2014,6,June,2014 -Small Business,Germany,Paseo,689,300.00,206700.00,6201.00,200499.00,172250.00,28249.00,6/1/2014,6,June,2014 -Enterprise,Germany,Paseo,1570,125.00,196250.00,5887.50,190362.50,188400.00,1962.50,6/1/2014,6,June,2014 -Channel Partners,United States of America,Paseo,1369.5,12.00,16434.00,493.02,15940.98,4108.50,11832.48,7/1/2014,7,July,2014 -Enterprise,Canada,Paseo,2009,125.00,251125.00,7533.75,243591.25,241080.00,2511.25,10/1/2014,10,October,2014 -Midmarket,Germany,Paseo,1945,15.00,29175.00,875.25,28299.75,19450.00,8849.75,10/1/2013,10,October,2013 -Enterprise,France,Paseo,1287,125.00,160875.00,4826.25,156048.75,154440.00,1608.75,12/1/2014,12,December,2014 -Enterprise,Germany,Paseo,1706,125.00,213250.00,6397.50,206852.50,204720.00,2132.50,12/1/2014,12,December,2014 -Enterprise,Canada,Velo,2009,125.00,251125.00,7533.75,243591.25,241080.00,2511.25,10/1/2014,10,October,2014 -Small Business,United States of America,VTT,2844,300.00,853200.00,25596.00,827604.00,711000.00,116604.00,2/1/2014,2,February,2014 -Channel Partners,Mexico,VTT,1916,12.00,22992.00,689.76,22302.24,5748.00,16554.24,4/1/2014,4,April,2014 -Enterprise,Germany,VTT,1570,125.00,196250.00,5887.50,190362.50,188400.00,1962.50,6/1/2014,6,June,2014 -Small Business,Canada,VTT,1874,300.00,562200.00,16866.00,545334.00,468500.00,76834.00,8/1/2014,8,August,2014 -Government,Mexico,VTT,1642,350.00,574700.00,17241.00,557459.00,426920.00,130539.00,8/1/2014,8,August,2014 -Midmarket,Germany,VTT,1945,15.00,29175.00,875.25,28299.75,19450.00,8849.75,10/1/2013,10,October,2013 -Government,Canada,Carretera,831,20.00,16620.00,498.60,16121.40,8310.00,7811.40,5/1/2014,5,May,2014 -Government,Mexico,Paseo,1760,7.00,12320.00,369.60,11950.40,8800.00,3150.40,9/1/2013,9,September,2013 -Government,Canada,Velo,3850.5,20.00,77010.00,2310.30,74699.70,38505.00,36194.70,4/1/2014,4,April,2014 -Channel Partners,Germany,VTT,2479,12.00,29748.00,892.44,28855.56,7437.00,21418.56,1/1/2014,1,January,2014 -Midmarket,Mexico,Montana,2031,15.00,30465.00,1218.60,29246.40,20310.00,8936.40,10/1/2014,10,October,2014 -Midmarket,Mexico,Paseo,2031,15.00,30465.00,1218.60,29246.40,20310.00,8936.40,10/1/2014,10,October,2014 -Midmarket,France,Paseo,2261,15.00,33915.00,1356.60,32558.40,22610.00,9948.40,12/1/2013,12,December,2013 -Government,United States of America,Velo,736,20.00,14720.00,588.80,14131.20,7360.00,6771.20,9/1/2013,9,September,2013 -Government,Canada,Carretera,2851,7.00,19957.00,798.28,19158.72,14255.00,4903.72,10/1/2013,10,October,2013 -Small Business,Germany,Carretera,2021,300.00,606300.00,24252.00,582048.00,505250.00,76798.00,10/1/2014,10,October,2014 -Government,United States of America,Carretera,274,350.00,95900.00,3836.00,92064.00,71240.00,20824.00,12/1/2014,12,December,2014 -Midmarket,Canada,Montana,1967,15.00,29505.00,1180.20,28324.80,19670.00,8654.80,3/1/2014,3,March,2014 -Small Business,Germany,Montana,1859,300.00,557700.00,22308.00,535392.00,464750.00,70642.00,8/1/2014,8,August,2014 -Government,Canada,Montana,2851,7.00,19957.00,798.28,19158.72,14255.00,4903.72,10/1/2013,10,October,2013 -Small Business,Germany,Montana,2021,300.00,606300.00,24252.00,582048.00,505250.00,76798.00,10/1/2014,10,October,2014 -Enterprise,Mexico,Montana,1138,125.00,142250.00,5690.00,136560.00,136560.00,0.00,12/1/2014,12,December,2014 -Government,Canada,Paseo,4251,7.00,29757.00,1190.28,28566.72,21255.00,7311.72,1/1/2014,1,January,2014 -Enterprise,Germany,Paseo,795,125.00,99375.00,3975.00,95400.00,95400.00,0.00,3/1/2014,3,March,2014 -Small Business,Germany,Paseo,1414.5,300.00,424350.00,16974.00,407376.00,353625.00,53751.00,4/1/2014,4,April,2014 -Small Business,United States of America,Paseo,2918,300.00,875400.00,35016.00,840384.00,729500.00,110884.00,5/1/2014,5,May,2014 -Government,United States of America,Paseo,3450,350.00,1207500.00,48300.00,1159200.00,897000.00,262200.00,7/1/2014,7,July,2014 -Enterprise,France,Paseo,2988,125.00,373500.00,14940.00,358560.00,358560.00,0.00,7/1/2014,7,July,2014 -Midmarket,Canada,Paseo,218,15.00,3270.00,130.80,3139.20,2180.00,959.20,9/1/2014,9,September,2014 -Government,Canada,Paseo,2074,20.00,41480.00,1659.20,39820.80,20740.00,19080.80,9/1/2014,9,September,2014 -Government,United States of America,Paseo,1056,20.00,21120.00,844.80,20275.20,10560.00,9715.20,9/1/2014,9,September,2014 -Midmarket,United States of America,Paseo,671,15.00,10065.00,402.60,9662.40,6710.00,2952.40,10/1/2013,10,October,2013 -Midmarket,Mexico,Paseo,1514,15.00,22710.00,908.40,21801.60,15140.00,6661.60,10/1/2013,10,October,2013 -Government,United States of America,Paseo,274,350.00,95900.00,3836.00,92064.00,71240.00,20824.00,12/1/2014,12,December,2014 -Enterprise,Mexico,Paseo,1138,125.00,142250.00,5690.00,136560.00,136560.00,0.00,12/1/2014,12,December,2014 -Channel Partners,United States of America,Velo,1465,12.00,17580.00,703.20,16876.80,4395.00,12481.80,3/1/2014,3,March,2014 -Government,Canada,Velo,2646,20.00,52920.00,2116.80,50803.20,26460.00,24343.20,9/1/2013,9,September,2013 -Government,France,Velo,2177,350.00,761950.00,30478.00,731472.00,566020.00,165452.00,10/1/2014,10,October,2014 -Channel Partners,France,VTT,866,12.00,10392.00,415.68,9976.32,2598.00,7378.32,5/1/2014,5,May,2014 -Government,United States of America,VTT,349,350.00,122150.00,4886.00,117264.00,90740.00,26524.00,9/1/2013,9,September,2013 -Government,France,VTT,2177,350.00,761950.00,30478.00,731472.00,566020.00,165452.00,10/1/2014,10,October,2014 -Midmarket,Mexico,VTT,1514,15.00,22710.00,908.40,21801.60,15140.00,6661.60,10/1/2013,10,October,2013 -Government,Mexico,Amarilla,1865,350.00,652750.00,26110.00,626640.00,484900.00,141740.00,2/1/2014,2,February,2014 -Enterprise,Mexico,Amarilla,1074,125.00,134250.00,5370.00,128880.00,128880.00,0.00,4/1/2014,4,April,2014 -Government,Germany,Amarilla,1907,350.00,667450.00,26698.00,640752.00,495820.00,144932.00,9/1/2014,9,September,2014 -Midmarket,United States of America,Amarilla,671,15.00,10065.00,402.60,9662.40,6710.00,2952.40,10/1/2013,10,October,2013 -Government,Canada,Amarilla,1778,350.00,622300.00,24892.00,597408.00,462280.00,135128.00,12/1/2013,12,December,2013 -Government,Germany,Montana,1159,7.00,8113.00,405.65,7707.35,5795.00,1912.35,10/1/2013,10,October,2013 -Government,Germany,Paseo,1372,7.00,9604.00,480.20,9123.80,6860.00,2263.80,1/1/2014,1,January,2014 -Government,Canada,Paseo,2349,7.00,16443.00,822.15,15620.85,11745.00,3875.85,9/1/2013,9,September,2013 -Government,Mexico,Paseo,2689,7.00,18823.00,941.15,17881.85,13445.00,4436.85,10/1/2014,10,October,2014 -Channel Partners,Canada,Paseo,2431,12.00,29172.00,1458.60,27713.40,7293.00,20420.40,12/1/2014,12,December,2014 -Channel Partners,Canada,Velo,2431,12.00,29172.00,1458.60,27713.40,7293.00,20420.40,12/1/2014,12,December,2014 -Government,Mexico,VTT,2689,7.00,18823.00,941.15,17881.85,13445.00,4436.85,10/1/2014,10,October,2014 -Government,Mexico,Amarilla,1683,7.00,11781.00,589.05,11191.95,8415.00,2776.95,7/1/2014,7,July,2014 -Channel Partners,Mexico,Amarilla,1123,12.00,13476.00,673.80,12802.20,3369.00,9433.20,8/1/2014,8,August,2014 -Government,Germany,Amarilla,1159,7.00,8113.00,405.65,7707.35,5795.00,1912.35,10/1/2013,10,October,2013 -Channel Partners,France,Carretera,1865,12.00,22380.00,1119.00,21261.00,5595.00,15666.00,2/1/2014,2,February,2014 -Channel Partners,Germany,Carretera,1116,12.00,13392.00,669.60,12722.40,3348.00,9374.40,2/1/2014,2,February,2014 -Government,France,Carretera,1563,20.00,31260.00,1563.00,29697.00,15630.00,14067.00,5/1/2014,5,May,2014 -Small Business,United States of America,Carretera,991,300.00,297300.00,14865.00,282435.00,247750.00,34685.00,6/1/2014,6,June,2014 -Government,Germany,Carretera,1016,7.00,7112.00,355.60,6756.40,5080.00,1676.40,11/1/2013,11,November,2013 -Midmarket,Mexico,Carretera,2791,15.00,41865.00,2093.25,39771.75,27910.00,11861.75,11/1/2014,11,November,2014 -Government,United States of America,Carretera,570,7.00,3990.00,199.50,3790.50,2850.00,940.50,12/1/2014,12,December,2014 -Government,France,Carretera,2487,7.00,17409.00,870.45,16538.55,12435.00,4103.55,12/1/2014,12,December,2014 -Government,France,Montana,1384.5,350.00,484575.00,24228.75,460346.25,359970.00,100376.25,1/1/2014,1,January,2014 -Enterprise,United States of America,Montana,3627,125.00,453375.00,22668.75,430706.25,435240.00,-4533.75,7/1/2014,7,July,2014 -Government,Mexico,Montana,720,350.00,252000.00,12600.00,239400.00,187200.00,52200.00,9/1/2013,9,September,2013 -Channel Partners,Germany,Montana,2342,12.00,28104.00,1405.20,26698.80,7026.00,19672.80,11/1/2014,11,November,2014 -Small Business,Mexico,Montana,1100,300.00,330000.00,16500.00,313500.00,275000.00,38500.00,12/1/2013,12,December,2013 -Government,France,Paseo,1303,20.00,26060.00,1303.00,24757.00,13030.00,11727.00,2/1/2014,2,February,2014 -Enterprise,United States of America,Paseo,2992,125.00,374000.00,18700.00,355300.00,359040.00,-3740.00,3/1/2014,3,March,2014 -Enterprise,France,Paseo,2385,125.00,298125.00,14906.25,283218.75,286200.00,-2981.25,3/1/2014,3,March,2014 -Small Business,Mexico,Paseo,1607,300.00,482100.00,24105.00,457995.00,401750.00,56245.00,4/1/2014,4,April,2014 -Government,United States of America,Paseo,2327,7.00,16289.00,814.45,15474.55,11635.00,3839.55,5/1/2014,5,May,2014 -Small Business,United States of America,Paseo,991,300.00,297300.00,14865.00,282435.00,247750.00,34685.00,6/1/2014,6,June,2014 -Government,United States of America,Paseo,602,350.00,210700.00,10535.00,200165.00,156520.00,43645.00,6/1/2014,6,June,2014 -Midmarket,France,Paseo,2620,15.00,39300.00,1965.00,37335.00,26200.00,11135.00,9/1/2014,9,September,2014 -Government,Canada,Paseo,1228,350.00,429800.00,21490.00,408310.00,319280.00,89030.00,10/1/2013,10,October,2013 -Government,Canada,Paseo,1389,20.00,27780.00,1389.00,26391.00,13890.00,12501.00,10/1/2013,10,October,2013 -Enterprise,United States of America,Paseo,861,125.00,107625.00,5381.25,102243.75,103320.00,-1076.25,10/1/2014,10,October,2014 -Enterprise,France,Paseo,704,125.00,88000.00,4400.00,83600.00,84480.00,-880.00,10/1/2013,10,October,2013 -Government,Canada,Paseo,1802,20.00,36040.00,1802.00,34238.00,18020.00,16218.00,12/1/2013,12,December,2013 -Government,United States of America,Paseo,2663,20.00,53260.00,2663.00,50597.00,26630.00,23967.00,12/1/2014,12,December,2014 -Government,France,Paseo,2136,7.00,14952.00,747.60,14204.40,10680.00,3524.40,12/1/2013,12,December,2013 -Midmarket,Germany,Paseo,2116,15.00,31740.00,1587.00,30153.00,21160.00,8993.00,12/1/2013,12,December,2013 -Midmarket,United States of America,Velo,555,15.00,8325.00,416.25,7908.75,5550.00,2358.75,1/1/2014,1,January,2014 -Midmarket,Mexico,Velo,2861,15.00,42915.00,2145.75,40769.25,28610.00,12159.25,1/1/2014,1,January,2014 -Enterprise,Germany,Velo,807,125.00,100875.00,5043.75,95831.25,96840.00,-1008.75,2/1/2014,2,February,2014 -Government,United States of America,Velo,602,350.00,210700.00,10535.00,200165.00,156520.00,43645.00,6/1/2014,6,June,2014 -Government,United States of America,Velo,2832,20.00,56640.00,2832.00,53808.00,28320.00,25488.00,8/1/2014,8,August,2014 -Government,France,Velo,1579,20.00,31580.00,1579.00,30001.00,15790.00,14211.00,8/1/2014,8,August,2014 -Enterprise,United States of America,Velo,861,125.00,107625.00,5381.25,102243.75,103320.00,-1076.25,10/1/2014,10,October,2014 -Enterprise,France,Velo,704,125.00,88000.00,4400.00,83600.00,84480.00,-880.00,10/1/2013,10,October,2013 -Government,France,Velo,1033,20.00,20660.00,1033.00,19627.00,10330.00,9297.00,12/1/2013,12,December,2013 -Small Business,Germany,Velo,1250,300.00,375000.00,18750.00,356250.00,312500.00,43750.00,12/1/2014,12,December,2014 -Government,Canada,VTT,1389,20.00,27780.00,1389.00,26391.00,13890.00,12501.00,10/1/2013,10,October,2013 -Government,United States of America,VTT,1265,20.00,25300.00,1265.00,24035.00,12650.00,11385.00,11/1/2013,11,November,2013 -Government,Germany,VTT,2297,20.00,45940.00,2297.00,43643.00,22970.00,20673.00,11/1/2013,11,November,2013 -Government,United States of America,VTT,2663,20.00,53260.00,2663.00,50597.00,26630.00,23967.00,12/1/2014,12,December,2014 -Government,United States of America,VTT,570,7.00,3990.00,199.50,3790.50,2850.00,940.50,12/1/2014,12,December,2014 -Government,France,VTT,2487,7.00,17409.00,870.45,16538.55,12435.00,4103.55,12/1/2014,12,December,2014 -Government,Germany,Amarilla,1350,350.00,472500.00,23625.00,448875.00,351000.00,97875.00,2/1/2014,2,February,2014 -Government,Canada,Amarilla,552,350.00,193200.00,9660.00,183540.00,143520.00,40020.00,8/1/2014,8,August,2014 -Government,Canada,Amarilla,1228,350.00,429800.00,21490.00,408310.00,319280.00,89030.00,10/1/2013,10,October,2013 -Small Business,Germany,Amarilla,1250,300.00,375000.00,18750.00,356250.00,312500.00,43750.00,12/1/2014,12,December,2014 -Midmarket,France,Paseo,3801,15.00,57015.00,3420.90,53594.10,38010.00,15584.10,4/1/2014,4,April,2014 -Government,United States of America,Carretera,1117.5,20.00,22350.00,1341.00,21009.00,11175.00,9834.00,1/1/2014,1,January,2014 -Midmarket,Canada,Carretera,2844,15.00,42660.00,2559.60,40100.40,28440.00,11660.40,6/1/2014,6,June,2014 -Channel Partners,Mexico,Carretera,562,12.00,6744.00,404.64,6339.36,1686.00,4653.36,9/1/2014,9,September,2014 -Channel Partners,Canada,Carretera,2299,12.00,27588.00,1655.28,25932.72,6897.00,19035.72,10/1/2013,10,October,2013 -Midmarket,United States of America,Carretera,2030,15.00,30450.00,1827.00,28623.00,20300.00,8323.00,11/1/2014,11,November,2014 -Government,United States of America,Carretera,263,7.00,1841.00,110.46,1730.54,1315.00,415.54,11/1/2013,11,November,2013 -Enterprise,Germany,Carretera,887,125.00,110875.00,6652.50,104222.50,106440.00,-2217.50,12/1/2013,12,December,2013 -Government,Mexico,Montana,980,350.00,343000.00,20580.00,322420.00,254800.00,67620.00,4/1/2014,4,April,2014 -Government,Germany,Montana,1460,350.00,511000.00,30660.00,480340.00,379600.00,100740.00,5/1/2014,5,May,2014 -Government,France,Montana,1403,7.00,9821.00,589.26,9231.74,7015.00,2216.74,10/1/2013,10,October,2013 -Channel Partners,United States of America,Montana,2723,12.00,32676.00,1960.56,30715.44,8169.00,22546.44,11/1/2014,11,November,2014 -Government,France,Paseo,1496,350.00,523600.00,31416.00,492184.00,388960.00,103224.00,6/1/2014,6,June,2014 -Channel Partners,Canada,Paseo,2299,12.00,27588.00,1655.28,25932.72,6897.00,19035.72,10/1/2013,10,October,2013 -Government,United States of America,Paseo,727,350.00,254450.00,15267.00,239183.00,189020.00,50163.00,10/1/2013,10,October,2013 -Enterprise,Canada,Velo,952,125.00,119000.00,7140.00,111860.00,114240.00,-2380.00,2/1/2014,2,February,2014 -Enterprise,United States of America,Velo,2755,125.00,344375.00,20662.50,323712.50,330600.00,-6887.50,2/1/2014,2,February,2014 -Midmarket,Germany,Velo,1530,15.00,22950.00,1377.00,21573.00,15300.00,6273.00,5/1/2014,5,May,2014 -Government,France,Velo,1496,350.00,523600.00,31416.00,492184.00,388960.00,103224.00,6/1/2014,6,June,2014 -Government,Mexico,Velo,1498,7.00,10486.00,629.16,9856.84,7490.00,2366.84,6/1/2014,6,June,2014 -Small Business,France,Velo,1221,300.00,366300.00,21978.00,344322.00,305250.00,39072.00,10/1/2013,10,October,2013 -Government,France,Velo,2076,350.00,726600.00,43596.00,683004.00,539760.00,143244.00,10/1/2013,10,October,2013 -Midmarket,Canada,VTT,2844,15.00,42660.00,2559.60,40100.40,28440.00,11660.40,6/1/2014,6,June,2014 -Government,Mexico,VTT,1498,7.00,10486.00,629.16,9856.84,7490.00,2366.84,6/1/2014,6,June,2014 -Small Business,France,VTT,1221,300.00,366300.00,21978.00,344322.00,305250.00,39072.00,10/1/2013,10,October,2013 -Government,Mexico,VTT,1123,20.00,22460.00,1347.60,21112.40,11230.00,9882.40,11/1/2013,11,November,2013 -Small Business,Canada,VTT,2436,300.00,730800.00,43848.00,686952.00,609000.00,77952.00,12/1/2013,12,December,2013 -Enterprise,France,Amarilla,1987.5,125.00,248437.50,14906.25,233531.25,238500.00,-4968.75,1/1/2014,1,January,2014 -Government,Mexico,Amarilla,1679,350.00,587650.00,35259.00,552391.00,436540.00,115851.00,9/1/2014,9,September,2014 -Government,United States of America,Amarilla,727,350.00,254450.00,15267.00,239183.00,189020.00,50163.00,10/1/2013,10,October,2013 -Government,France,Amarilla,1403,7.00,9821.00,589.26,9231.74,7015.00,2216.74,10/1/2013,10,October,2013 -Government,France,Amarilla,2076,350.00,726600.00,43596.00,683004.00,539760.00,143244.00,10/1/2013,10,October,2013 -Government,France,Montana,1757,20.00,35140.00,2108.40,33031.60,17570.00,15461.60,10/1/2013,10,October,2013 -Midmarket,United States of America,Paseo,2198,15.00,32970.00,1978.20,30991.80,21980.00,9011.80,8/1/2014,8,August,2014 -Midmarket,Germany,Paseo,1743,15.00,26145.00,1568.70,24576.30,17430.00,7146.30,8/1/2014,8,August,2014 -Midmarket,United States of America,Paseo,1153,15.00,17295.00,1037.70,16257.30,11530.00,4727.30,10/1/2014,10,October,2014 -Government,France,Paseo,1757,20.00,35140.00,2108.40,33031.60,17570.00,15461.60,10/1/2013,10,October,2013 -Government,Germany,Velo,1001,20.00,20020.00,1201.20,18818.80,10010.00,8808.80,8/1/2014,8,August,2014 -Government,Mexico,Velo,1333,7.00,9331.00,559.86,8771.14,6665.00,2106.14,11/1/2014,11,November,2014 -Midmarket,United States of America,VTT,1153,15.00,17295.00,1037.70,16257.30,11530.00,4727.30,10/1/2014,10,October,2014 -Channel Partners,Mexico,Carretera,727,12.00,8724.00,610.68,8113.32,2181.00,5932.32,2/1/2014,2,February,2014 -Channel Partners,Canada,Carretera,1884,12.00,22608.00,1582.56,21025.44,5652.00,15373.44,8/1/2014,8,August,2014 -Government,Mexico,Carretera,1834,20.00,36680.00,2567.60,34112.40,18340.00,15772.40,9/1/2013,9,September,2013 -Channel Partners,Mexico,Montana,2340,12.00,28080.00,1965.60,26114.40,7020.00,19094.40,1/1/2014,1,January,2014 -Channel Partners,France,Montana,2342,12.00,28104.00,1967.28,26136.72,7026.00,19110.72,11/1/2014,11,November,2014 -Government,France,Paseo,1031,7.00,7217.00,505.19,6711.81,5155.00,1556.81,9/1/2013,9,September,2013 -Midmarket,Canada,Velo,1262,15.00,18930.00,1325.10,17604.90,12620.00,4984.90,5/1/2014,5,May,2014 -Government,Canada,Velo,1135,7.00,7945.00,556.15,7388.85,5675.00,1713.85,6/1/2014,6,June,2014 -Government,United States of America,Velo,547,7.00,3829.00,268.03,3560.97,2735.00,825.97,11/1/2014,11,November,2014 -Government,Canada,Velo,1582,7.00,11074.00,775.18,10298.82,7910.00,2388.82,12/1/2014,12,December,2014 -Channel Partners,France,VTT,1738.5,12.00,20862.00,1460.34,19401.66,5215.50,14186.16,4/1/2014,4,April,2014 -Channel Partners,Germany,VTT,2215,12.00,26580.00,1860.60,24719.40,6645.00,18074.40,9/1/2013,9,September,2013 -Government,Canada,VTT,1582,7.00,11074.00,775.18,10298.82,7910.00,2388.82,12/1/2014,12,December,2014 -Government,Canada,Amarilla,1135,7.00,7945.00,556.15,7388.85,5675.00,1713.85,6/1/2014,6,June,2014 -Government,United States of America,Carretera,1761,350.00,616350.00,43144.50,573205.50,457860.00,115345.50,3/1/2014,3,March,2014 -Small Business,France,Carretera,448,300.00,134400.00,9408.00,124992.00,112000.00,12992.00,6/1/2014,6,June,2014 -Small Business,France,Carretera,2181,300.00,654300.00,45801.00,608499.00,545250.00,63249.00,10/1/2014,10,October,2014 -Government,France,Montana,1976,20.00,39520.00,2766.40,36753.60,19760.00,16993.60,10/1/2014,10,October,2014 -Small Business,France,Montana,2181,300.00,654300.00,45801.00,608499.00,545250.00,63249.00,10/1/2014,10,October,2014 -Enterprise,Germany,Montana,2500,125.00,312500.00,21875.00,290625.00,300000.00,-9375.00,11/1/2013,11,November,2013 -Small Business,Canada,Paseo,1702,300.00,510600.00,35742.00,474858.00,425500.00,49358.00,5/1/2014,5,May,2014 -Small Business,France,Paseo,448,300.00,134400.00,9408.00,124992.00,112000.00,12992.00,6/1/2014,6,June,2014 -Enterprise,Germany,Paseo,3513,125.00,439125.00,30738.75,408386.25,421560.00,-13173.75,7/1/2014,7,July,2014 -Midmarket,France,Paseo,2101,15.00,31515.00,2206.05,29308.95,21010.00,8298.95,8/1/2014,8,August,2014 -Midmarket,United States of America,Paseo,2931,15.00,43965.00,3077.55,40887.45,29310.00,11577.45,9/1/2013,9,September,2013 -Government,France,Paseo,1535,20.00,30700.00,2149.00,28551.00,15350.00,13201.00,9/1/2014,9,September,2014 -Small Business,Germany,Paseo,1123,300.00,336900.00,23583.00,313317.00,280750.00,32567.00,9/1/2013,9,September,2013 -Small Business,Canada,Paseo,1404,300.00,421200.00,29484.00,391716.00,351000.00,40716.00,11/1/2013,11,November,2013 -Channel Partners,Mexico,Paseo,2763,12.00,33156.00,2320.92,30835.08,8289.00,22546.08,11/1/2013,11,November,2013 -Government,Germany,Paseo,2125,7.00,14875.00,1041.25,13833.75,10625.00,3208.75,12/1/2013,12,December,2013 -Small Business,France,Velo,1659,300.00,497700.00,34839.00,462861.00,414750.00,48111.00,7/1/2014,7,July,2014 -Government,Mexico,Velo,609,20.00,12180.00,852.60,11327.40,6090.00,5237.40,8/1/2014,8,August,2014 -Enterprise,Germany,Velo,2087,125.00,260875.00,18261.25,242613.75,250440.00,-7826.25,9/1/2014,9,September,2014 -Government,France,Velo,1976,20.00,39520.00,2766.40,36753.60,19760.00,16993.60,10/1/2014,10,October,2014 -Government,United States of America,Velo,1421,20.00,28420.00,1989.40,26430.60,14210.00,12220.60,12/1/2013,12,December,2013 -Small Business,United States of America,Velo,1372,300.00,411600.00,28812.00,382788.00,343000.00,39788.00,12/1/2014,12,December,2014 -Government,Germany,Velo,588,20.00,11760.00,823.20,10936.80,5880.00,5056.80,12/1/2013,12,December,2013 -Channel Partners,Canada,VTT,3244.5,12.00,38934.00,2725.38,36208.62,9733.50,26475.12,1/1/2014,1,January,2014 -Small Business,France,VTT,959,300.00,287700.00,20139.00,267561.00,239750.00,27811.00,2/1/2014,2,February,2014 -Small Business,Mexico,VTT,2747,300.00,824100.00,57687.00,766413.00,686750.00,79663.00,2/1/2014,2,February,2014 -Enterprise,Canada,Amarilla,1645,125.00,205625.00,14393.75,191231.25,197400.00,-6168.75,5/1/2014,5,May,2014 -Government,France,Amarilla,2876,350.00,1006600.00,70462.00,936138.00,747760.00,188378.00,9/1/2014,9,September,2014 -Enterprise,Germany,Amarilla,994,125.00,124250.00,8697.50,115552.50,119280.00,-3727.50,9/1/2013,9,September,2013 -Government,Canada,Amarilla,1118,20.00,22360.00,1565.20,20794.80,11180.00,9614.80,11/1/2014,11,November,2014 -Small Business,United States of America,Amarilla,1372,300.00,411600.00,28812.00,382788.00,343000.00,39788.00,12/1/2014,12,December,2014 -Government,Canada,Montana,488,7.00,3416.00,273.28,3142.72,2440.00,702.72,2/1/2014,2,February,2014 -Government,United States of America,Montana,1282,20.00,25640.00,2051.20,23588.80,12820.00,10768.80,6/1/2014,6,June,2014 -Government,Canada,Paseo,257,7.00,1799.00,143.92,1655.08,1285.00,370.08,5/1/2014,5,May,2014 -Government,United States of America,Amarilla,1282,20.00,25640.00,2051.20,23588.80,12820.00,10768.80,6/1/2014,6,June,2014 -Enterprise,Mexico,Carretera,1540,125.00,192500.00,15400.00,177100.00,184800.00,-7700.00,8/1/2014,8,August,2014 -Midmarket,France,Carretera,490,15.00,7350.00,588.00,6762.00,4900.00,1862.00,11/1/2014,11,November,2014 -Government,Mexico,Carretera,1362,350.00,476700.00,38136.00,438564.00,354120.00,84444.00,12/1/2014,12,December,2014 -Midmarket,France,Montana,2501,15.00,37515.00,3001.20,34513.80,25010.00,9503.80,3/1/2014,3,March,2014 -Government,Canada,Montana,708,20.00,14160.00,1132.80,13027.20,7080.00,5947.20,6/1/2014,6,June,2014 -Government,Germany,Montana,645,20.00,12900.00,1032.00,11868.00,6450.00,5418.00,7/1/2014,7,July,2014 -Small Business,France,Montana,1562,300.00,468600.00,37488.00,431112.00,390500.00,40612.00,8/1/2014,8,August,2014 -Small Business,Canada,Montana,1283,300.00,384900.00,30792.00,354108.00,320750.00,33358.00,9/1/2013,9,September,2013 -Midmarket,Germany,Montana,711,15.00,10665.00,853.20,9811.80,7110.00,2701.80,12/1/2014,12,December,2014 -Enterprise,Mexico,Paseo,1114,125.00,139250.00,11140.00,128110.00,133680.00,-5570.00,3/1/2014,3,March,2014 -Government,Germany,Paseo,1259,7.00,8813.00,705.04,8107.96,6295.00,1812.96,4/1/2014,4,April,2014 -Government,Germany,Paseo,1095,7.00,7665.00,613.20,7051.80,5475.00,1576.80,5/1/2014,5,May,2014 -Government,Germany,Paseo,1366,20.00,27320.00,2185.60,25134.40,13660.00,11474.40,6/1/2014,6,June,2014 -Small Business,Mexico,Paseo,2460,300.00,738000.00,59040.00,678960.00,615000.00,63960.00,6/1/2014,6,June,2014 -Government,United States of America,Paseo,678,7.00,4746.00,379.68,4366.32,3390.00,976.32,8/1/2014,8,August,2014 -Government,Germany,Paseo,1598,7.00,11186.00,894.88,10291.12,7990.00,2301.12,8/1/2014,8,August,2014 -Government,Germany,Paseo,2409,7.00,16863.00,1349.04,15513.96,12045.00,3468.96,9/1/2013,9,September,2013 -Government,Germany,Paseo,1934,20.00,38680.00,3094.40,35585.60,19340.00,16245.60,9/1/2014,9,September,2014 -Government,Mexico,Paseo,2993,20.00,59860.00,4788.80,55071.20,29930.00,25141.20,9/1/2014,9,September,2014 -Government,Germany,Paseo,2146,350.00,751100.00,60088.00,691012.00,557960.00,133052.00,11/1/2013,11,November,2013 -Government,Mexico,Paseo,1946,7.00,13622.00,1089.76,12532.24,9730.00,2802.24,12/1/2013,12,December,2013 -Government,Mexico,Paseo,1362,350.00,476700.00,38136.00,438564.00,354120.00,84444.00,12/1/2014,12,December,2014 -Channel Partners,Canada,Velo,598,12.00,7176.00,574.08,6601.92,1794.00,4807.92,3/1/2014,3,March,2014 -Government,United States of America,Velo,2907,7.00,20349.00,1627.92,18721.08,14535.00,4186.08,6/1/2014,6,June,2014 -Government,Germany,Velo,2338,7.00,16366.00,1309.28,15056.72,11690.00,3366.72,6/1/2014,6,June,2014 -Small Business,France,Velo,386,300.00,115800.00,9264.00,106536.00,96500.00,10036.00,11/1/2013,11,November,2013 -Small Business,Mexico,Velo,635,300.00,190500.00,15240.00,175260.00,158750.00,16510.00,12/1/2014,12,December,2014 -Government,France,VTT,574.5,350.00,201075.00,16086.00,184989.00,149370.00,35619.00,4/1/2014,4,April,2014 -Government,Germany,VTT,2338,7.00,16366.00,1309.28,15056.72,11690.00,3366.72,6/1/2014,6,June,2014 -Government,France,VTT,381,350.00,133350.00,10668.00,122682.00,99060.00,23622.00,8/1/2014,8,August,2014 -Government,Germany,VTT,422,350.00,147700.00,11816.00,135884.00,109720.00,26164.00,8/1/2014,8,August,2014 -Small Business,Canada,VTT,2134,300.00,640200.00,51216.00,588984.00,533500.00,55484.00,9/1/2014,9,September,2014 -Small Business,United States of America,VTT,808,300.00,242400.00,19392.00,223008.00,202000.00,21008.00,12/1/2013,12,December,2013 -Government,Canada,Amarilla,708,20.00,14160.00,1132.80,13027.20,7080.00,5947.20,6/1/2014,6,June,2014 -Government,United States of America,Amarilla,2907,7.00,20349.00,1627.92,18721.08,14535.00,4186.08,6/1/2014,6,June,2014 -Government,Germany,Amarilla,1366,20.00,27320.00,2185.60,25134.40,13660.00,11474.40,6/1/2014,6,June,2014 -Small Business,Mexico,Amarilla,2460,300.00,738000.00,59040.00,678960.00,615000.00,63960.00,6/1/2014,6,June,2014 -Government,Germany,Amarilla,1520,20.00,30400.00,2432.00,27968.00,15200.00,12768.00,11/1/2014,11,November,2014 -Midmarket,Germany,Amarilla,711,15.00,10665.00,853.20,9811.80,7110.00,2701.80,12/1/2014,12,December,2014 -Channel Partners,Mexico,Amarilla,1375,12.00,16500.00,1320.00,15180.00,4125.00,11055.00,12/1/2013,12,December,2013 -Small Business,Mexico,Amarilla,635,300.00,190500.00,15240.00,175260.00,158750.00,16510.00,12/1/2014,12,December,2014 -Government,United States of America,VTT,436.5,20.00,8730.00,698.40,8031.60,4365.00,3666.60,7/1/2014,7,July,2014 -Small Business,Canada,Carretera,1094,300.00,328200.00,29538.00,298662.00,273500.00,25162.00,6/1/2014,6,June,2014 -Channel Partners,Mexico,Carretera,367,12.00,4404.00,396.36,4007.64,1101.00,2906.64,10/1/2013,10,October,2013 -Small Business,Canada,Montana,3802.5,300.00,1140750.00,102667.50,1038082.50,950625.00,87457.50,4/1/2014,4,April,2014 -Government,France,Montana,1666,350.00,583100.00,52479.00,530621.00,433160.00,97461.00,5/1/2014,5,May,2014 -Small Business,France,Montana,322,300.00,96600.00,8694.00,87906.00,80500.00,7406.00,9/1/2013,9,September,2013 -Channel Partners,Canada,Montana,2321,12.00,27852.00,2506.68,25345.32,6963.00,18382.32,11/1/2014,11,November,2014 -Enterprise,France,Montana,1857,125.00,232125.00,20891.25,211233.75,222840.00,-11606.25,11/1/2013,11,November,2013 -Government,Canada,Montana,1611,7.00,11277.00,1014.93,10262.07,8055.00,2207.07,12/1/2013,12,December,2013 -Enterprise,United States of America,Montana,2797,125.00,349625.00,31466.25,318158.75,335640.00,-17481.25,12/1/2014,12,December,2014 -Small Business,Germany,Montana,334,300.00,100200.00,9018.00,91182.00,83500.00,7682.00,12/1/2013,12,December,2013 -Small Business,Mexico,Paseo,2565,300.00,769500.00,69255.00,700245.00,641250.00,58995.00,1/1/2014,1,January,2014 -Government,Mexico,Paseo,2417,350.00,845950.00,76135.50,769814.50,628420.00,141394.50,1/1/2014,1,January,2014 -Midmarket,United States of America,Paseo,3675,15.00,55125.00,4961.25,50163.75,36750.00,13413.75,4/1/2014,4,April,2014 -Small Business,Canada,Paseo,1094,300.00,328200.00,29538.00,298662.00,273500.00,25162.00,6/1/2014,6,June,2014 -Midmarket,France,Paseo,1227,15.00,18405.00,1656.45,16748.55,12270.00,4478.55,10/1/2014,10,October,2014 -Channel Partners,Mexico,Paseo,367,12.00,4404.00,396.36,4007.64,1101.00,2906.64,10/1/2013,10,October,2013 -Small Business,France,Paseo,1324,300.00,397200.00,35748.00,361452.00,331000.00,30452.00,11/1/2014,11,November,2014 -Channel Partners,Germany,Paseo,1775,12.00,21300.00,1917.00,19383.00,5325.00,14058.00,11/1/2013,11,November,2013 -Enterprise,United States of America,Paseo,2797,125.00,349625.00,31466.25,318158.75,335640.00,-17481.25,12/1/2014,12,December,2014 -Midmarket,Mexico,Velo,245,15.00,3675.00,330.75,3344.25,2450.00,894.25,5/1/2014,5,May,2014 -Small Business,Canada,Velo,3793.5,300.00,1138050.00,102424.50,1035625.50,948375.00,87250.50,7/1/2014,7,July,2014 -Government,Germany,Velo,1307,350.00,457450.00,41170.50,416279.50,339820.00,76459.50,7/1/2014,7,July,2014 -Enterprise,Canada,Velo,567,125.00,70875.00,6378.75,64496.25,68040.00,-3543.75,9/1/2014,9,September,2014 -Enterprise,Mexico,Velo,2110,125.00,263750.00,23737.50,240012.50,253200.00,-13187.50,9/1/2014,9,September,2014 -Government,Canada,Velo,1269,350.00,444150.00,39973.50,404176.50,329940.00,74236.50,10/1/2014,10,October,2014 -Channel Partners,United States of America,VTT,1956,12.00,23472.00,2112.48,21359.52,5868.00,15491.52,1/1/2014,1,January,2014 -Small Business,Germany,VTT,2659,300.00,797700.00,71793.00,725907.00,664750.00,61157.00,2/1/2014,2,February,2014 -Government,United States of America,VTT,1351.5,350.00,473025.00,42572.25,430452.75,351390.00,79062.75,4/1/2014,4,April,2014 -Channel Partners,Germany,VTT,880,12.00,10560.00,950.40,9609.60,2640.00,6969.60,5/1/2014,5,May,2014 -Small Business,United States of America,VTT,1867,300.00,560100.00,50409.00,509691.00,466750.00,42941.00,9/1/2014,9,September,2014 -Channel Partners,France,VTT,2234,12.00,26808.00,2412.72,24395.28,6702.00,17693.28,9/1/2013,9,September,2013 -Midmarket,France,VTT,1227,15.00,18405.00,1656.45,16748.55,12270.00,4478.55,10/1/2014,10,October,2014 -Enterprise,Mexico,VTT,877,125.00,109625.00,9866.25,99758.75,105240.00,-5481.25,11/1/2014,11,November,2014 -Government,United States of America,Amarilla,2071,350.00,724850.00,65236.50,659613.50,538460.00,121153.50,9/1/2014,9,September,2014 -Government,Canada,Amarilla,1269,350.00,444150.00,39973.50,404176.50,329940.00,74236.50,10/1/2014,10,October,2014 -Midmarket,Germany,Amarilla,970,15.00,14550.00,1309.50,13240.50,9700.00,3540.50,11/1/2013,11,November,2013 -Government,Mexico,Amarilla,1694,20.00,33880.00,3049.20,30830.80,16940.00,13890.80,11/1/2014,11,November,2014 -Government,Germany,Carretera,663,20.00,13260.00,1193.40,12066.60,6630.00,5436.60,5/1/2014,5,May,2014 -Government,Canada,Carretera,819,7.00,5733.00,515.97,5217.03,4095.00,1122.03,7/1/2014,7,July,2014 -Channel Partners,Germany,Carretera,1580,12.00,18960.00,1706.40,17253.60,4740.00,12513.60,9/1/2014,9,September,2014 -Government,Mexico,Carretera,521,7.00,3647.00,328.23,3318.77,2605.00,713.77,12/1/2014,12,December,2014 -Government,United States of America,Paseo,973,20.00,19460.00,1751.40,17708.60,9730.00,7978.60,3/1/2014,3,March,2014 -Government,Mexico,Paseo,1038,20.00,20760.00,1868.40,18891.60,10380.00,8511.60,6/1/2014,6,June,2014 -Government,Germany,Paseo,360,7.00,2520.00,226.80,2293.20,1800.00,493.20,10/1/2014,10,October,2014 -Channel Partners,France,Velo,1967,12.00,23604.00,2124.36,21479.64,5901.00,15578.64,3/1/2014,3,March,2014 -Midmarket,Mexico,Velo,2628,15.00,39420.00,3547.80,35872.20,26280.00,9592.20,4/1/2014,4,April,2014 -Government,Germany,VTT,360,7.00,2520.00,226.80,2293.20,1800.00,493.20,10/1/2014,10,October,2014 -Government,France,VTT,2682,20.00,53640.00,4827.60,48812.40,26820.00,21992.40,11/1/2013,11,November,2013 -Government,Mexico,VTT,521,7.00,3647.00,328.23,3318.77,2605.00,713.77,12/1/2014,12,December,2014 -Government,Mexico,Amarilla,1038,20.00,20760.00,1868.40,18891.60,10380.00,8511.60,6/1/2014,6,June,2014 -Midmarket,Canada,Amarilla,1630.5,15.00,24457.50,2201.18,22256.33,16305.00,5951.33,7/1/2014,7,July,2014 -Channel Partners,France,Amarilla,306,12.00,3672.00,330.48,3341.52,918.00,2423.52,12/1/2013,12,December,2013 -Channel Partners,United States of America,Carretera,386,12.00,4632.00,463.20,4168.80,1158.00,3010.80,10/1/2013,10,October,2013 -Government,United States of America,Montana,2328,7.00,16296.00,1629.60,14666.40,11640.00,3026.40,9/1/2014,9,September,2014 -Channel Partners,United States of America,Paseo,386,12.00,4632.00,463.20,4168.80,1158.00,3010.80,10/1/2013,10,October,2013 -Enterprise,United States of America,Carretera,3445.5,125.00,430687.50,43068.75,387618.75,413460.00,-25841.25,4/1/2014,4,April,2014 -Enterprise,France,Carretera,1482,125.00,185250.00,18525.00,166725.00,177840.00,-11115.00,12/1/2013,12,December,2013 -Government,United States of America,Montana,2313,350.00,809550.00,80955.00,728595.00,601380.00,127215.00,5/1/2014,5,May,2014 -Enterprise,United States of America,Montana,1804,125.00,225500.00,22550.00,202950.00,216480.00,-13530.00,11/1/2013,11,November,2013 -Midmarket,France,Montana,2072,15.00,31080.00,3108.00,27972.00,20720.00,7252.00,12/1/2014,12,December,2014 -Government,France,Paseo,1954,20.00,39080.00,3908.00,35172.00,19540.00,15632.00,3/1/2014,3,March,2014 -Small Business,Mexico,Paseo,591,300.00,177300.00,17730.00,159570.00,147750.00,11820.00,5/1/2014,5,May,2014 -Midmarket,France,Paseo,2167,15.00,32505.00,3250.50,29254.50,21670.00,7584.50,10/1/2013,10,October,2013 -Government,Germany,Paseo,241,20.00,4820.00,482.00,4338.00,2410.00,1928.00,10/1/2014,10,October,2014 -Midmarket,Germany,Velo,681,15.00,10215.00,1021.50,9193.50,6810.00,2383.50,1/1/2014,1,January,2014 -Midmarket,Germany,Velo,510,15.00,7650.00,765.00,6885.00,5100.00,1785.00,4/1/2014,4,April,2014 -Midmarket,United States of America,Velo,790,15.00,11850.00,1185.00,10665.00,7900.00,2765.00,5/1/2014,5,May,2014 -Government,France,Velo,639,350.00,223650.00,22365.00,201285.00,166140.00,35145.00,7/1/2014,7,July,2014 -Enterprise,United States of America,Velo,1596,125.00,199500.00,19950.00,179550.00,191520.00,-11970.00,9/1/2014,9,September,2014 -Small Business,United States of America,Velo,2294,300.00,688200.00,68820.00,619380.00,573500.00,45880.00,10/1/2013,10,October,2013 -Government,Germany,Velo,241,20.00,4820.00,482.00,4338.00,2410.00,1928.00,10/1/2014,10,October,2014 -Government,Germany,Velo,2665,7.00,18655.00,1865.50,16789.50,13325.00,3464.50,11/1/2014,11,November,2014 -Enterprise,Canada,Velo,1916,125.00,239500.00,23950.00,215550.00,229920.00,-14370.00,12/1/2013,12,December,2013 -Small Business,France,Velo,853,300.00,255900.00,25590.00,230310.00,213250.00,17060.00,12/1/2014,12,December,2014 -Enterprise,Mexico,VTT,341,125.00,42625.00,4262.50,38362.50,40920.00,-2557.50,5/1/2014,5,May,2014 -Midmarket,Mexico,VTT,641,15.00,9615.00,961.50,8653.50,6410.00,2243.50,7/1/2014,7,July,2014 -Government,United States of America,VTT,2807,350.00,982450.00,98245.00,884205.00,729820.00,154385.00,8/1/2014,8,August,2014 -Small Business,Mexico,VTT,432,300.00,129600.00,12960.00,116640.00,108000.00,8640.00,9/1/2014,9,September,2014 -Small Business,United States of America,VTT,2294,300.00,688200.00,68820.00,619380.00,573500.00,45880.00,10/1/2013,10,October,2013 -Midmarket,France,VTT,2167,15.00,32505.00,3250.50,29254.50,21670.00,7584.50,10/1/2013,10,October,2013 -Enterprise,Canada,VTT,2529,125.00,316125.00,31612.50,284512.50,303480.00,-18967.50,11/1/2014,11,November,2014 -Government,Germany,VTT,1870,350.00,654500.00,65450.00,589050.00,486200.00,102850.00,12/1/2013,12,December,2013 -Enterprise,United States of America,Amarilla,579,125.00,72375.00,7237.50,65137.50,69480.00,-4342.50,1/1/2014,1,January,2014 -Government,Canada,Amarilla,2240,350.00,784000.00,78400.00,705600.00,582400.00,123200.00,2/1/2014,2,February,2014 -Small Business,United States of America,Amarilla,2993,300.00,897900.00,89790.00,808110.00,748250.00,59860.00,3/1/2014,3,March,2014 -Channel Partners,Canada,Amarilla,3520.5,12.00,42246.00,4224.60,38021.40,10561.50,27459.90,4/1/2014,4,April,2014 -Government,Mexico,Amarilla,2039,20.00,40780.00,4078.00,36702.00,20390.00,16312.00,5/1/2014,5,May,2014 -Channel Partners,Germany,Amarilla,2574,12.00,30888.00,3088.80,27799.20,7722.00,20077.20,8/1/2014,8,August,2014 -Government,Canada,Amarilla,707,350.00,247450.00,24745.00,222705.00,183820.00,38885.00,9/1/2014,9,September,2014 -Midmarket,France,Amarilla,2072,15.00,31080.00,3108.00,27972.00,20720.00,7252.00,12/1/2014,12,December,2014 -Small Business,France,Amarilla,853,300.00,255900.00,25590.00,230310.00,213250.00,17060.00,12/1/2014,12,December,2014 -Channel Partners,France,Carretera,1198,12.00,14376.00,1581.36,12794.64,3594.00,9200.64,10/1/2013,10,October,2013 -Government,France,Paseo,2532,7.00,17724.00,1949.64,15774.36,12660.00,3114.36,4/1/2014,4,April,2014 -Channel Partners,France,Paseo,1198,12.00,14376.00,1581.36,12794.64,3594.00,9200.64,10/1/2013,10,October,2013 -Midmarket,Canada,Velo,384,15.00,5760.00,633.60,5126.40,3840.00,1286.40,1/1/2014,1,January,2014 -Channel Partners,Germany,Velo,472,12.00,5664.00,623.04,5040.96,1416.00,3624.96,10/1/2014,10,October,2014 -Government,United States of America,VTT,1579,7.00,11053.00,1215.83,9837.17,7895.00,1942.17,3/1/2014,3,March,2014 -Channel Partners,Mexico,VTT,1005,12.00,12060.00,1326.60,10733.40,3015.00,7718.40,9/1/2013,9,September,2013 -Midmarket,United States of America,Amarilla,3199.5,15.00,47992.50,5279.18,42713.33,31995.00,10718.33,7/1/2014,7,July,2014 -Channel Partners,Germany,Amarilla,472,12.00,5664.00,623.04,5040.96,1416.00,3624.96,10/1/2014,10,October,2014 -Channel Partners,Canada,Carretera,1937,12.00,23244.00,2556.84,20687.16,5811.00,14876.16,2/1/2014,2,February,2014 -Government,Germany,Carretera,792,350.00,277200.00,30492.00,246708.00,205920.00,40788.00,3/1/2014,3,March,2014 -Small Business,Germany,Carretera,2811,300.00,843300.00,92763.00,750537.00,702750.00,47787.00,7/1/2014,7,July,2014 -Enterprise,France,Carretera,2441,125.00,305125.00,33563.75,271561.25,292920.00,-21358.75,10/1/2014,10,October,2014 -Midmarket,Canada,Carretera,1560,15.00,23400.00,2574.00,20826.00,15600.00,5226.00,11/1/2013,11,November,2013 -Government,Mexico,Carretera,2706,7.00,18942.00,2083.62,16858.38,13530.00,3328.38,11/1/2013,11,November,2013 -Government,Germany,Montana,766,350.00,268100.00,29491.00,238609.00,199160.00,39449.00,1/1/2014,1,January,2014 -Government,Germany,Montana,2992,20.00,59840.00,6582.40,53257.60,29920.00,23337.60,10/1/2013,10,October,2013 -Midmarket,Mexico,Montana,2157,15.00,32355.00,3559.05,28795.95,21570.00,7225.95,12/1/2014,12,December,2014 -Small Business,Canada,Paseo,873,300.00,261900.00,28809.00,233091.00,218250.00,14841.00,1/1/2014,1,January,2014 -Government,Mexico,Paseo,1122,20.00,22440.00,2468.40,19971.60,11220.00,8751.60,3/1/2014,3,March,2014 -Government,Canada,Paseo,2104.5,350.00,736575.00,81023.25,655551.75,547170.00,108381.75,7/1/2014,7,July,2014 -Channel Partners,Canada,Paseo,4026,12.00,48312.00,5314.32,42997.68,12078.00,30919.68,7/1/2014,7,July,2014 -Channel Partners,France,Paseo,2425.5,12.00,29106.00,3201.66,25904.34,7276.50,18627.84,7/1/2014,7,July,2014 -Government,Canada,Paseo,2394,20.00,47880.00,5266.80,42613.20,23940.00,18673.20,8/1/2014,8,August,2014 -Midmarket,Mexico,Paseo,1984,15.00,29760.00,3273.60,26486.40,19840.00,6646.40,8/1/2014,8,August,2014 -Enterprise,France,Paseo,2441,125.00,305125.00,33563.75,271561.25,292920.00,-21358.75,10/1/2014,10,October,2014 -Government,Germany,Paseo,2992,20.00,59840.00,6582.40,53257.60,29920.00,23337.60,10/1/2013,10,October,2013 -Small Business,Canada,Paseo,1366,300.00,409800.00,45078.00,364722.00,341500.00,23222.00,11/1/2014,11,November,2014 -Government,France,Velo,2805,20.00,56100.00,6171.00,49929.00,28050.00,21879.00,9/1/2013,9,September,2013 -Midmarket,Mexico,Velo,655,15.00,9825.00,1080.75,8744.25,6550.00,2194.25,9/1/2013,9,September,2013 -Government,Mexico,Velo,344,350.00,120400.00,13244.00,107156.00,89440.00,17716.00,10/1/2013,10,October,2013 -Government,Canada,Velo,1808,7.00,12656.00,1392.16,11263.84,9040.00,2223.84,11/1/2014,11,November,2014 -Channel Partners,France,VTT,1734,12.00,20808.00,2288.88,18519.12,5202.00,13317.12,1/1/2014,1,January,2014 -Enterprise,Mexico,VTT,554,125.00,69250.00,7617.50,61632.50,66480.00,-4847.50,1/1/2014,1,January,2014 -Government,Canada,VTT,2935,20.00,58700.00,6457.00,52243.00,29350.00,22893.00,11/1/2013,11,November,2013 -Enterprise,Germany,Amarilla,3165,125.00,395625.00,43518.75,352106.25,379800.00,-27693.75,1/1/2014,1,January,2014 -Government,Mexico,Amarilla,2629,20.00,52580.00,5783.80,46796.20,26290.00,20506.20,1/1/2014,1,January,2014 -Enterprise,France,Amarilla,1433,125.00,179125.00,19703.75,159421.25,171960.00,-12538.75,5/1/2014,5,May,2014 -Enterprise,Mexico,Amarilla,947,125.00,118375.00,13021.25,105353.75,113640.00,-8286.25,9/1/2013,9,September,2013 -Government,Mexico,Amarilla,344,350.00,120400.00,13244.00,107156.00,89440.00,17716.00,10/1/2013,10,October,2013 -Midmarket,Mexico,Amarilla,2157,15.00,32355.00,3559.05,28795.95,21570.00,7225.95,12/1/2014,12,December,2014 -Government,United States of America,Paseo,380,7.00,2660.00,292.60,2367.40,1900.00,467.40,9/1/2013,9,September,2013 -Government,Mexico,Carretera,886,350.00,310100.00,37212.00,272888.00,230360.00,42528.00,6/1/2014,6,June,2014 -Enterprise,Canada,Carretera,2416,125.00,302000.00,36240.00,265760.00,289920.00,-24160.00,9/1/2013,9,September,2013 -Enterprise,Mexico,Carretera,2156,125.00,269500.00,32340.00,237160.00,258720.00,-21560.00,10/1/2014,10,October,2014 -Midmarket,Canada,Carretera,2689,15.00,40335.00,4840.20,35494.80,26890.00,8604.80,11/1/2014,11,November,2014 -Midmarket,United States of America,Montana,677,15.00,10155.00,1218.60,8936.40,6770.00,2166.40,3/1/2014,3,March,2014 -Small Business,France,Montana,1773,300.00,531900.00,63828.00,468072.00,443250.00,24822.00,4/1/2014,4,April,2014 -Government,Mexico,Montana,2420,7.00,16940.00,2032.80,14907.20,12100.00,2807.20,9/1/2014,9,September,2014 -Government,Canada,Montana,2734,7.00,19138.00,2296.56,16841.44,13670.00,3171.44,10/1/2014,10,October,2014 -Government,Mexico,Montana,1715,20.00,34300.00,4116.00,30184.00,17150.00,13034.00,10/1/2013,10,October,2013 -Small Business,France,Montana,1186,300.00,355800.00,42696.00,313104.00,296500.00,16604.00,12/1/2013,12,December,2013 -Small Business,United States of America,Paseo,3495,300.00,1048500.00,125820.00,922680.00,873750.00,48930.00,1/1/2014,1,January,2014 -Government,Mexico,Paseo,886,350.00,310100.00,37212.00,272888.00,230360.00,42528.00,6/1/2014,6,June,2014 -Enterprise,Mexico,Paseo,2156,125.00,269500.00,32340.00,237160.00,258720.00,-21560.00,10/1/2014,10,October,2014 -Government,Mexico,Paseo,905,20.00,18100.00,2172.00,15928.00,9050.00,6878.00,10/1/2014,10,October,2014 -Government,Mexico,Paseo,1715,20.00,34300.00,4116.00,30184.00,17150.00,13034.00,10/1/2013,10,October,2013 -Government,France,Paseo,1594,350.00,557900.00,66948.00,490952.00,414440.00,76512.00,11/1/2014,11,November,2014 -Small Business,Germany,Paseo,1359,300.00,407700.00,48924.00,358776.00,339750.00,19026.00,11/1/2014,11,November,2014 -Small Business,Mexico,Paseo,2150,300.00,645000.00,77400.00,567600.00,537500.00,30100.00,11/1/2014,11,November,2014 -Government,Mexico,Paseo,1197,350.00,418950.00,50274.00,368676.00,311220.00,57456.00,11/1/2014,11,November,2014 -Midmarket,Mexico,Paseo,380,15.00,5700.00,684.00,5016.00,3800.00,1216.00,12/1/2013,12,December,2013 -Government,Mexico,Paseo,1233,20.00,24660.00,2959.20,21700.80,12330.00,9370.80,12/1/2014,12,December,2014 -Government,Mexico,Velo,1395,350.00,488250.00,58590.00,429660.00,362700.00,66960.00,7/1/2014,7,July,2014 -Government,United States of America,Velo,986,350.00,345100.00,41412.00,303688.00,256360.00,47328.00,10/1/2014,10,October,2014 -Government,Mexico,Velo,905,20.00,18100.00,2172.00,15928.00,9050.00,6878.00,10/1/2014,10,October,2014 -Channel Partners,Canada,VTT,2109,12.00,25308.00,3036.96,22271.04,6327.00,15944.04,5/1/2014,5,May,2014 -Midmarket,France,VTT,3874.5,15.00,58117.50,6974.10,51143.40,38745.00,12398.40,7/1/2014,7,July,2014 -Government,Canada,VTT,623,350.00,218050.00,26166.00,191884.00,161980.00,29904.00,9/1/2013,9,September,2013 -Government,United States of America,VTT,986,350.00,345100.00,41412.00,303688.00,256360.00,47328.00,10/1/2014,10,October,2014 -Enterprise,United States of America,VTT,2387,125.00,298375.00,35805.00,262570.00,286440.00,-23870.00,11/1/2014,11,November,2014 -Government,Mexico,VTT,1233,20.00,24660.00,2959.20,21700.80,12330.00,9370.80,12/1/2014,12,December,2014 -Government,United States of America,Amarilla,270,350.00,94500.00,11340.00,83160.00,70200.00,12960.00,2/1/2014,2,February,2014 -Government,France,Amarilla,3421.5,7.00,23950.50,2874.06,21076.44,17107.50,3968.94,7/1/2014,7,July,2014 -Government,Canada,Amarilla,2734,7.00,19138.00,2296.56,16841.44,13670.00,3171.44,10/1/2014,10,October,2014 -Midmarket,United States of America,Amarilla,2548,15.00,38220.00,4586.40,33633.60,25480.00,8153.60,11/1/2013,11,November,2013 -Government,France,Carretera,2521.5,20.00,50430.00,6051.60,44378.40,25215.00,19163.40,1/1/2014,1,January,2014 -Channel Partners,Mexico,Montana,2661,12.00,31932.00,3831.84,28100.16,7983.00,20117.16,5/1/2014,5,May,2014 -Government,Germany,Paseo,1531,20.00,30620.00,3674.40,26945.60,15310.00,11635.60,12/1/2014,12,December,2014 -Government,France,VTT,1491,7.00,10437.00,1252.44,9184.56,7455.00,1729.56,3/1/2014,3,March,2014 -Government,Germany,VTT,1531,20.00,30620.00,3674.40,26945.60,15310.00,11635.60,12/1/2014,12,December,2014 -Channel Partners,Canada,Amarilla,2761,12.00,33132.00,3975.84,29156.16,8283.00,20873.16,9/1/2013,9,September,2013 -Midmarket,United States of America,Carretera,2567,15.00,38505.00,5005.65,33499.35,25670.00,7829.35,6/1/2014,6,June,2014 -Midmarket,United States of America,VTT,2567,15.00,38505.00,5005.65,33499.35,25670.00,7829.35,6/1/2014,6,June,2014 -Government,Canada,Carretera,923,350.00,323050.00,41996.50,281053.50,239980.00,41073.50,3/1/2014,3,March,2014 -Government,France,Carretera,1790,350.00,626500.00,81445.00,545055.00,465400.00,79655.00,3/1/2014,3,March,2014 -Government,Germany,Carretera,442,20.00,8840.00,1149.20,7690.80,4420.00,3270.80,9/1/2013,9,September,2013 -Government,United States of America,Montana,982.5,350.00,343875.00,44703.75,299171.25,255450.00,43721.25,1/1/2014,1,January,2014 -Government,United States of America,Montana,1298,7.00,9086.00,1181.18,7904.82,6490.00,1414.82,2/1/2014,2,February,2014 -Channel Partners,Mexico,Montana,604,12.00,7248.00,942.24,6305.76,1812.00,4493.76,6/1/2014,6,June,2014 -Government,Mexico,Montana,2255,20.00,45100.00,5863.00,39237.00,22550.00,16687.00,7/1/2014,7,July,2014 -Government,Canada,Montana,1249,20.00,24980.00,3247.40,21732.60,12490.00,9242.60,10/1/2014,10,October,2014 -Government,United States of America,Paseo,1438.5,7.00,10069.50,1309.04,8760.47,7192.50,1567.97,1/1/2014,1,January,2014 -Small Business,Germany,Paseo,807,300.00,242100.00,31473.00,210627.00,201750.00,8877.00,1/1/2014,1,January,2014 -Government,United States of America,Paseo,2641,20.00,52820.00,6866.60,45953.40,26410.00,19543.40,2/1/2014,2,February,2014 -Government,Germany,Paseo,2708,20.00,54160.00,7040.80,47119.20,27080.00,20039.20,2/1/2014,2,February,2014 -Government,Canada,Paseo,2632,350.00,921200.00,119756.00,801444.00,684320.00,117124.00,6/1/2014,6,June,2014 -Enterprise,Canada,Paseo,1583,125.00,197875.00,25723.75,172151.25,189960.00,-17808.75,6/1/2014,6,June,2014 -Channel Partners,Mexico,Paseo,571,12.00,6852.00,890.76,5961.24,1713.00,4248.24,7/1/2014,7,July,2014 -Government,France,Paseo,2696,7.00,18872.00,2453.36,16418.64,13480.00,2938.64,8/1/2014,8,August,2014 -Midmarket,Canada,Paseo,1565,15.00,23475.00,3051.75,20423.25,15650.00,4773.25,10/1/2014,10,October,2014 -Government,Canada,Paseo,1249,20.00,24980.00,3247.40,21732.60,12490.00,9242.60,10/1/2014,10,October,2014 -Government,Germany,Paseo,357,350.00,124950.00,16243.50,108706.50,92820.00,15886.50,11/1/2014,11,November,2014 -Channel Partners,Germany,Paseo,1013,12.00,12156.00,1580.28,10575.72,3039.00,7536.72,12/1/2014,12,December,2014 -Midmarket,France,Velo,3997.5,15.00,59962.50,7795.13,52167.38,39975.00,12192.38,1/1/2014,1,January,2014 -Government,Canada,Velo,2632,350.00,921200.00,119756.00,801444.00,684320.00,117124.00,6/1/2014,6,June,2014 -Government,France,Velo,1190,7.00,8330.00,1082.90,7247.10,5950.00,1297.10,6/1/2014,6,June,2014 -Channel Partners,Mexico,Velo,604,12.00,7248.00,942.24,6305.76,1812.00,4493.76,6/1/2014,6,June,2014 -Midmarket,Germany,Velo,660,15.00,9900.00,1287.00,8613.00,6600.00,2013.00,9/1/2013,9,September,2013 -Channel Partners,Mexico,Velo,410,12.00,4920.00,639.60,4280.40,1230.00,3050.40,10/1/2014,10,October,2014 -Small Business,Mexico,Velo,2605,300.00,781500.00,101595.00,679905.00,651250.00,28655.00,11/1/2013,11,November,2013 -Channel Partners,Germany,Velo,1013,12.00,12156.00,1580.28,10575.72,3039.00,7536.72,12/1/2014,12,December,2014 -Enterprise,Canada,VTT,1583,125.00,197875.00,25723.75,172151.25,189960.00,-17808.75,6/1/2014,6,June,2014 -Midmarket,Canada,VTT,1565,15.00,23475.00,3051.75,20423.25,15650.00,4773.25,10/1/2014,10,October,2014 -Enterprise,Canada,Amarilla,1659,125.00,207375.00,26958.75,180416.25,199080.00,-18663.75,1/1/2014,1,January,2014 -Government,France,Amarilla,1190,7.00,8330.00,1082.90,7247.10,5950.00,1297.10,6/1/2014,6,June,2014 -Channel Partners,Mexico,Amarilla,410,12.00,4920.00,639.60,4280.40,1230.00,3050.40,10/1/2014,10,October,2014 -Channel Partners,Germany,Amarilla,1770,12.00,21240.00,2761.20,18478.80,5310.00,13168.80,12/1/2013,12,December,2013 -Government,Mexico,Carretera,2579,20.00,51580.00,7221.20,44358.80,25790.00,18568.80,4/1/2014,4,April,2014 -Government,United States of America,Carretera,1743,20.00,34860.00,4880.40,29979.60,17430.00,12549.60,5/1/2014,5,May,2014 -Government,United States of America,Carretera,2996,7.00,20972.00,2936.08,18035.92,14980.00,3055.92,10/1/2013,10,October,2013 -Government,Germany,Carretera,280,7.00,1960.00,274.40,1685.60,1400.00,285.60,12/1/2014,12,December,2014 -Government,France,Montana,293,7.00,2051.00,287.14,1763.86,1465.00,298.86,2/1/2014,2,February,2014 -Government,United States of America,Montana,2996,7.00,20972.00,2936.08,18035.92,14980.00,3055.92,10/1/2013,10,October,2013 -Midmarket,Germany,Paseo,278,15.00,4170.00,583.80,3586.20,2780.00,806.20,2/1/2014,2,February,2014 -Government,Canada,Paseo,2428,20.00,48560.00,6798.40,41761.60,24280.00,17481.60,3/1/2014,3,March,2014 -Midmarket,United States of America,Paseo,1767,15.00,26505.00,3710.70,22794.30,17670.00,5124.30,9/1/2014,9,September,2014 -Channel Partners,France,Paseo,1393,12.00,16716.00,2340.24,14375.76,4179.00,10196.76,10/1/2014,10,October,2014 -Government,Germany,VTT,280,7.00,1960.00,274.40,1685.60,1400.00,285.60,12/1/2014,12,December,2014 -Channel Partners,France,Amarilla,1393,12.00,16716.00,2340.24,14375.76,4179.00,10196.76,10/1/2014,10,October,2014 -Channel Partners,United States of America,Amarilla,2015,12.00,24180.00,3385.20,20794.80,6045.00,14749.80,12/1/2013,12,December,2013 -Small Business,Mexico,Carretera,801,300.00,240300.00,33642.00,206658.00,200250.00,6408.00,7/1/2014,7,July,2014 -Enterprise,France,Carretera,1023,125.00,127875.00,17902.50,109972.50,122760.00,-12787.50,9/1/2013,9,September,2013 -Small Business,Canada,Carretera,1496,300.00,448800.00,62832.00,385968.00,374000.00,11968.00,10/1/2014,10,October,2014 -Small Business,United States of America,Carretera,1010,300.00,303000.00,42420.00,260580.00,252500.00,8080.00,10/1/2014,10,October,2014 -Midmarket,Germany,Carretera,1513,15.00,22695.00,3177.30,19517.70,15130.00,4387.70,11/1/2014,11,November,2014 -Midmarket,Canada,Carretera,2300,15.00,34500.00,4830.00,29670.00,23000.00,6670.00,12/1/2014,12,December,2014 -Enterprise,Mexico,Carretera,2821,125.00,352625.00,49367.50,303257.50,338520.00,-35262.50,12/1/2013,12,December,2013 -Government,Canada,Montana,2227.5,350.00,779625.00,109147.50,670477.50,579150.00,91327.50,1/1/2014,1,January,2014 -Government,Germany,Montana,1199,350.00,419650.00,58751.00,360899.00,311740.00,49159.00,4/1/2014,4,April,2014 -Government,Canada,Montana,200,350.00,70000.00,9800.00,60200.00,52000.00,8200.00,5/1/2014,5,May,2014 -Government,Canada,Montana,388,7.00,2716.00,380.24,2335.76,1940.00,395.76,9/1/2014,9,September,2014 -Government,Mexico,Montana,1727,7.00,12089.00,1692.46,10396.54,8635.00,1761.54,10/1/2013,10,October,2013 -Midmarket,Canada,Montana,2300,15.00,34500.00,4830.00,29670.00,23000.00,6670.00,12/1/2014,12,December,2014 -Government,Mexico,Paseo,260,20.00,5200.00,728.00,4472.00,2600.00,1872.00,2/1/2014,2,February,2014 -Midmarket,Canada,Paseo,2470,15.00,37050.00,5187.00,31863.00,24700.00,7163.00,9/1/2013,9,September,2013 -Midmarket,Canada,Paseo,1743,15.00,26145.00,3660.30,22484.70,17430.00,5054.70,10/1/2013,10,October,2013 -Channel Partners,United States of America,Paseo,2914,12.00,34968.00,4895.52,30072.48,8742.00,21330.48,10/1/2014,10,October,2014 -Government,France,Paseo,1731,7.00,12117.00,1696.38,10420.62,8655.00,1765.62,10/1/2014,10,October,2014 -Government,Canada,Paseo,700,350.00,245000.00,34300.00,210700.00,182000.00,28700.00,11/1/2014,11,November,2014 -Channel Partners,Canada,Paseo,2222,12.00,26664.00,3732.96,22931.04,6666.00,16265.04,11/1/2013,11,November,2013 -Government,United States of America,Paseo,1177,350.00,411950.00,57673.00,354277.00,306020.00,48257.00,11/1/2014,11,November,2014 -Government,France,Paseo,1922,350.00,672700.00,94178.00,578522.00,499720.00,78802.00,11/1/2013,11,November,2013 -Enterprise,Mexico,Velo,1575,125.00,196875.00,27562.50,169312.50,189000.00,-19687.50,2/1/2014,2,February,2014 -Government,United States of America,Velo,606,20.00,12120.00,1696.80,10423.20,6060.00,4363.20,4/1/2014,4,April,2014 -Small Business,United States of America,Velo,2460,300.00,738000.00,103320.00,634680.00,615000.00,19680.00,7/1/2014,7,July,2014 -Small Business,Canada,Velo,269,300.00,80700.00,11298.00,69402.00,67250.00,2152.00,10/1/2013,10,October,2013 -Small Business,Germany,Velo,2536,300.00,760800.00,106512.00,654288.00,634000.00,20288.00,11/1/2013,11,November,2013 -Government,Mexico,VTT,2903,7.00,20321.00,2844.94,17476.06,14515.00,2961.06,3/1/2014,3,March,2014 -Small Business,United States of America,VTT,2541,300.00,762300.00,106722.00,655578.00,635250.00,20328.00,8/1/2014,8,August,2014 -Small Business,Canada,VTT,269,300.00,80700.00,11298.00,69402.00,67250.00,2152.00,10/1/2013,10,October,2013 -Small Business,Canada,VTT,1496,300.00,448800.00,62832.00,385968.00,374000.00,11968.00,10/1/2014,10,October,2014 -Small Business,United States of America,VTT,1010,300.00,303000.00,42420.00,260580.00,252500.00,8080.00,10/1/2014,10,October,2014 -Government,France,VTT,1281,350.00,448350.00,62769.00,385581.00,333060.00,52521.00,12/1/2013,12,December,2013 -Small Business,Canada,Amarilla,888,300.00,266400.00,37296.00,229104.00,222000.00,7104.00,3/1/2014,3,March,2014 -Enterprise,United States of America,Amarilla,2844,125.00,355500.00,49770.00,305730.00,341280.00,-35550.00,5/1/2014,5,May,2014 -Channel Partners,France,Amarilla,2475,12.00,29700.00,4158.00,25542.00,7425.00,18117.00,8/1/2014,8,August,2014 -Midmarket,Canada,Amarilla,1743,15.00,26145.00,3660.30,22484.70,17430.00,5054.70,10/1/2013,10,October,2013 -Channel Partners,United States of America,Amarilla,2914,12.00,34968.00,4895.52,30072.48,8742.00,21330.48,10/1/2014,10,October,2014 -Government,France,Amarilla,1731,7.00,12117.00,1696.38,10420.62,8655.00,1765.62,10/1/2014,10,October,2014 -Government,Mexico,Amarilla,1727,7.00,12089.00,1692.46,10396.54,8635.00,1761.54,10/1/2013,10,October,2013 -Midmarket,Mexico,Amarilla,1870,15.00,28050.00,3927.00,24123.00,18700.00,5423.00,11/1/2013,11,November,2013 -Enterprise,France,Carretera,1174,125.00,146750.00,22012.50,124737.50,140880.00,-16142.50,8/1/2014,8,August,2014 -Enterprise,Germany,Carretera,2767,125.00,345875.00,51881.25,293993.75,332040.00,-38046.25,8/1/2014,8,August,2014 -Enterprise,Germany,Carretera,1085,125.00,135625.00,20343.75,115281.25,130200.00,-14918.75,10/1/2014,10,October,2014 -Small Business,Mexico,Montana,546,300.00,163800.00,24570.00,139230.00,136500.00,2730.00,10/1/2014,10,October,2014 -Government,Germany,Paseo,1158,20.00,23160.00,3474.00,19686.00,11580.00,8106.00,3/1/2014,3,March,2014 -Midmarket,Canada,Paseo,1614,15.00,24210.00,3631.50,20578.50,16140.00,4438.50,4/1/2014,4,April,2014 -Government,Mexico,Paseo,2535,7.00,17745.00,2661.75,15083.25,12675.00,2408.25,4/1/2014,4,April,2014 -Government,Mexico,Paseo,2851,350.00,997850.00,149677.50,848172.50,741260.00,106912.50,5/1/2014,5,May,2014 -Midmarket,Canada,Paseo,2559,15.00,38385.00,5757.75,32627.25,25590.00,7037.25,8/1/2014,8,August,2014 -Government,United States of America,Paseo,267,20.00,5340.00,801.00,4539.00,2670.00,1869.00,10/1/2013,10,October,2013 -Enterprise,Germany,Paseo,1085,125.00,135625.00,20343.75,115281.25,130200.00,-14918.75,10/1/2014,10,October,2014 -Midmarket,Germany,Paseo,1175,15.00,17625.00,2643.75,14981.25,11750.00,3231.25,10/1/2014,10,October,2014 -Government,United States of America,Paseo,2007,350.00,702450.00,105367.50,597082.50,521820.00,75262.50,11/1/2013,11,November,2013 -Government,Mexico,Paseo,2151,350.00,752850.00,112927.50,639922.50,559260.00,80662.50,11/1/2013,11,November,2013 -Channel Partners,United States of America,Paseo,914,12.00,10968.00,1645.20,9322.80,2742.00,6580.80,12/1/2014,12,December,2014 -Government,France,Paseo,293,20.00,5860.00,879.00,4981.00,2930.00,2051.00,12/1/2014,12,December,2014 -Channel Partners,Mexico,Velo,500,12.00,6000.00,900.00,5100.00,1500.00,3600.00,3/1/2014,3,March,2014 -Midmarket,France,Velo,2826,15.00,42390.00,6358.50,36031.50,28260.00,7771.50,5/1/2014,5,May,2014 -Enterprise,France,Velo,663,125.00,82875.00,12431.25,70443.75,79560.00,-9116.25,9/1/2014,9,September,2014 -Small Business,United States of America,Velo,2574,300.00,772200.00,115830.00,656370.00,643500.00,12870.00,11/1/2013,11,November,2013 -Enterprise,United States of America,Velo,2438,125.00,304750.00,45712.50,259037.50,292560.00,-33522.50,12/1/2013,12,December,2013 -Channel Partners,United States of America,Velo,914,12.00,10968.00,1645.20,9322.80,2742.00,6580.80,12/1/2014,12,December,2014 -Government,Canada,VTT,865.5,20.00,17310.00,2596.50,14713.50,8655.00,6058.50,7/1/2014,7,July,2014 -Midmarket,Germany,VTT,492,15.00,7380.00,1107.00,6273.00,4920.00,1353.00,7/1/2014,7,July,2014 -Government,United States of America,VTT,267,20.00,5340.00,801.00,4539.00,2670.00,1869.00,10/1/2013,10,October,2013 -Midmarket,Germany,VTT,1175,15.00,17625.00,2643.75,14981.25,11750.00,3231.25,10/1/2014,10,October,2014 -Enterprise,Canada,VTT,2954,125.00,369250.00,55387.50,313862.50,354480.00,-40617.50,11/1/2013,11,November,2013 -Enterprise,Germany,VTT,552,125.00,69000.00,10350.00,58650.00,66240.00,-7590.00,11/1/2014,11,November,2014 -Government,France,VTT,293,20.00,5860.00,879.00,4981.00,2930.00,2051.00,12/1/2014,12,December,2014 -Small Business,France,Amarilla,2475,300.00,742500.00,111375.00,631125.00,618750.00,12375.00,3/1/2014,3,March,2014 -Small Business,Mexico,Amarilla,546,300.00,163800.00,24570.00,139230.00,136500.00,2730.00,10/1/2014,10,October,2014 -Government,Mexico,Montana,1368,7.00,9576.00,1436.40,8139.60,6840.00,1299.60,2/1/2014,2,February,2014 -Government,Canada,Paseo,723,7.00,5061.00,759.15,4301.85,3615.00,686.85,4/1/2014,4,April,2014 -Channel Partners,United States of America,VTT,1806,12.00,21672.00,3250.80,18421.20,5418.00,13003.20,5/1/2014,5,May,2014 diff --git a/dotnet/samples/ConceptsV2/Resources/travelinfo.txt b/dotnet/samples/ConceptsV2/Resources/travelinfo.txt deleted file mode 100644 index 21665c82198e..000000000000 --- a/dotnet/samples/ConceptsV2/Resources/travelinfo.txt +++ /dev/null @@ -1,217 +0,0 @@ -Invoice Booking Reference LMNOPQ Trip ID - 11110011111 -Passenger Name(s) -MARKS/SAM ALBERT Agent W2 - - -MICROSOFT CORPORATION 14820 NE 36TH STREET REDMOND WA US 98052 - -American Express Global Business Travel Microsoft Travel -14711 NE 29th Place, Suite 215 -Bellevue, WA 98007 -Phone: +1 (669) 210-8041 - - - - -BILLING CODE : 1010-10010110 -Invoice Information - - - - - - -Invoice Details -Ticket Number - - - - - - - -0277993883295 - - - - - - -Charges -Ticket Base Fare - - - - - - - -306.29 - -Airline Name - -ALASKA AIRLINES - -Ticket Tax Fare 62.01 - -Passenger Name Flight Details - -MARKS/SAM ALBERT -11 Sep 2023 ALASKA AIRLINES -0572 H Class -SEATTLE-TACOMA,WA/RALEIGH DURHAM,NC -13 Sep 2023 ALASKA AIRLINES -0491 M Class -RALEIGH DURHAM,NC/SEATTLE- TACOMA,WA - -Total (USD) Ticket Amount - -368.30 - -Credit Card Information -Charged to Card - - - -AX XXXXXXXXXXX4321 - - - -368.30 - - - - -Payment Details - - - -Charged by Airline -Total Invoice Charge - - - -USD - - - -368.30 -368.30 - -Monday 11 September 2023 - -10:05 AM - -Seattle (SEA) to Durham (RDU) -Airline Booking Ref: ABCXYZ - -Carrier: ALASKA AIRLINES - -Flight: AS 572 - -Status: Confirmed - -Operated By: ALASKA AIRLINES -Origin: Seattle, WA, Seattle-Tacoma International Apt (SEA) - -Departing: Monday 11 September 2023 at 10:05 AM Destination: Durham, Raleigh, Raleigh (RDU) Arriving: Monday 11 September 2023 at 06:15 PM -Additional Information - -Departure Terminal: Not Applicable - -Arrival Terminal: TERMINAL 2 - - -Class: ECONOMY -Aircraft Type: Boeing 737-900 -Meal Service: Not Applicable -Frequent Flyer Number: Not Applicable -Number of Stops: 0 -Greenhouse Gas Emissions: 560 kg CO2e / person - - -Distance: 2354 Miles Estimated Time: 05 hours 10 minutes -Seat: 24A - - -THE WESTIN RALEIGH DURHAM AP -Address: 3931 Macaw Street, Raleigh, NC, 27617, US -Phone: (1) 919-224-1400 Fax: (1) 919-224-1401 -Check In Date: Monday 11 September 2023 Check Out Date: Wednesday 13 September 2023 Number Of Nights: 2 -Rate: USD 280.00 per night may be subject to local taxes and service charges -Guaranteed to: AX XXXXXXXXXXX4321 - -Reference Number: 987654 -Additional Information -Membership ID: 123456789 -CANCEL PERMITTED UP TO 1 DAYS BEFORE CHECKIN - -Status: Confirmed - - -Corporate Id: Not Applicable - -Number Of Rooms: 1 - -Wednesday 13 September 2023 - -07:15 PM - -Durham (RDU) to Seattle (SEA) -Airline Booking Ref: ABCXYZ - -Carrier: ALASKA AIRLINES - -Flight: AS 491 - -Status: Confirmed - -Operated By: ALASKA AIRLINES -Origin: Durham, Raleigh, Raleigh (RDU) -Departing: Wednesday 13 September 2023 at 07:15 PM - - - -Departure Terminal: TERMINAL 2 - -Destination: Seattle, WA, Seattle-Tacoma International Apt (SEA) -Arriving: Wednesday 13 September 2023 at 09:59 PM Arrival Terminal: Not Applicable -Additional Information - - -Class: ECONOMY -Aircraft Type: Boeing 737-900 -Meal Service: Not Applicable -Frequent Flyer Number: Not Applicable -Number of Stops: 0 -Greenhouse Gas Emissions: 560 kg CO2e / person - - -Distance: 2354 Miles Estimated Time: 05 hours 44 minutes -Seat: 16A - - - -Greenhouse Gas Emissions -Total Greenhouse Gas Emissions for this trip is: 1120 kg CO2e / person -Air Fare Information - -Routing : ONLINE RESERVATION -Total Fare : USD 368.30 -Additional Messages -FOR 24X7 Travel Reservations Please Call 1-669-210-8041 Unable To Use Requested As Frequent Flyer Program Invalid Use Of Frequent Flyer Number 0123XYZ Please Contact Corresponding Frequent Travel Program Support Desk For Assistance -Trip Name-Trip From Seattle To Raleigh/Durham -This Ticket Is Nonrefundable. Changes Or Cancellations Must Be Made Prior To Scheduled Flight Departure -All Changes Must Be Made On Same Carrier And Will Be Subject To Service Fee And Difference In Airfare -******************************************************* -Please Be Advised That Certain Mandatory Hotel-Imposed Charges Including But Not Limited To Daily Resort Or Facility Fees May Be Applicable To Your Stay And Payable To The Hotel Operator At Check-Out From The Property. You May Wish To Inquire With The Hotel Before Your Trip Regarding The Existence And Amount Of Such Charges. -******************************************************* -Hotel Cancel Policies Vary Depending On The Property And Date. If You Have Questions Regarding Cancellation Fees Please Call The Travel Office. -Important Information -COVID-19 Updates: Click here to access Travel Vitals https://travelvitals.amexgbt.com for the latest information and advisories compiled by American Express Global Business Travel. - -Carbon Emissions: The total emissions value for this itinerary includes air travel only. Emissions for each individual flight are displayed in the flight details section. For more information on carbon emissions please refer to https://www.amexglobalbusinesstravel.com/sustainable-products-and-platforms. - -For important information regarding your booking in relation to the conditions applying to your booking, managing your booking and travel advisory, please refer to www.amexglobalbusinesstravel.com/booking-info. - -GBT Travel Services UK Limited (GBT UK) and its authorized sublicensees (including Ovation Travel Group and Egencia) use certain trademarks and service marks of American Express Company or its subsidiaries (American Express) in the American Express Global Business Travel and American Express Meetings & Events brands and in connection with its business for permitted uses only under a limited license from American Express (Licensed Marks). The Licensed Marks are trademarks or service marks of, and the property of, American Express. GBT UK is a subsidiary of Global Business Travel Group, Inc. (NYSE: GBTG). American Express holds a minority interest in GBTG, which operates as a separate company from American Express. From 4992cd3ec12afac1ff454fcf2a43044c626ae08e Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 09:18:51 -0700 Subject: [PATCH 034/121] Namespace --- dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs index d75e4fad7026..81b2914ade3b 100644 --- a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs +++ b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -using Azure.AI.OpenAI; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; From daed2f840cbf6edd9147385bc08e3e53765f425d Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 09:23:22 -0700 Subject: [PATCH 035/121] Whitespace --- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 182acac7c9c2..c301237b917b 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -263,7 +263,7 @@ protected override async Task CreateChannelAsync(CancellationToken this.Logger.LogInformation("[{MethodName}] Created assistant thread: {ThreadId}", nameof(CreateChannelAsync), thread.Id); OpenAIAssistantChannel channel = - new (this._client, thread.Id) + new(this._client, thread.Id) { Logger = this.LoggerFactory.CreateLogger() }; From 6bd518c5cb54f39385035be87f6f59cbcf4e5728 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 10:49:41 -0700 Subject: [PATCH 036/121] Resolve OpenAIV2 dependency --- .../GettingStartedWithAgents.csproj | 1 + .../InternalUtilities/samples/InternalUtilities/BaseTest.cs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj index b95bbd546d34..99d086787951 100644 --- a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj +++ b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj @@ -39,6 +39,7 @@ + diff --git a/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs b/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs index 4e41ebf6bf24..d71d3c1f0032 100644 --- a/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs +++ b/dotnet/src/InternalUtilities/samples/InternalUtilities/BaseTest.cs @@ -43,9 +43,9 @@ protected Kernel CreateKernelWithChatCompletion() if (this.UseOpenAIConfig) { - //builder.AddOpenAIChatCompletion( // %%% CONNECTOR - // TestConfiguration.OpenAI.ChatModelId, - // TestConfiguration.OpenAI.ApiKey); + builder.AddOpenAIChatCompletion( + TestConfiguration.OpenAI.ChatModelId, + TestConfiguration.OpenAI.ApiKey); } else { From 51f80c5964fc872340e946b0151adeb4289afc90 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 12:59:27 -0700 Subject: [PATCH 037/121] Remove file-service usage --- .../Concepts/Agents/Legacy_AgentCharts.cs | 36 +++++++-------- .../Concepts/Agents/Legacy_AgentTools.cs | 45 +++++++++---------- .../OpenAIAssistant_FileManipulation.cs | 33 +++++++++----- .../Agents/OpenAIAssistant_FileSearch.cs | 27 ++++++++--- dotnet/samples/Concepts/Concepts.csproj | 1 + 5 files changed, 84 insertions(+), 58 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs index 877ba0971710..957b06e51f96 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. using System.Diagnostics; -using Microsoft.SemanticKernel.Connectors.OpenAI; +using Azure.AI.OpenAI; using Microsoft.SemanticKernel.Experimental.Agents; +using OpenAI; +using OpenAI.Files; namespace Agents; @@ -18,13 +20,6 @@ public sealed class Legacy_AgentCharts(ITestOutputHelper output) : BaseTest(outp /// private const string OpenAIFunctionEnabledModel = "gpt-4-1106-preview"; - /// - /// Flag to force usage of OpenAI configuration if both - /// and are defined. - /// If 'false', Azure takes precedence. - /// - private new const bool ForceOpenAI = false; - /// /// Create a chart and retrieve by file_id. /// @@ -33,7 +28,7 @@ public async Task CreateChartAsync() { Console.WriteLine("======== Using CodeInterpreter tool ========"); - var fileService = CreateFileService(); + FileClient fileClient = CreateFileClient(); var agent = await CreateAgentBuilder().WithCodeInterpreter().BuildAsync(); @@ -69,11 +64,12 @@ async Task InvokeAgentAsync(IAgentThread thread, string imageName, string questi { var filename = $"{imageName}.jpg"; var path = Path.Combine(Environment.CurrentDirectory, filename); - Console.WriteLine($"# {message.Role}: {message.Content}"); + var fileId = message.Content; + Console.WriteLine($"# {message.Role}: {fileId}"); Console.WriteLine($"# {message.Role}: {path}"); - var content = await fileService.GetFileContentAsync(message.Content); + BinaryData content = await fileClient.DownloadFileAsync(fileId); await using var outputStream = File.OpenWrite(filename); - await outputStream.WriteAsync(content.Data!.Value); + await outputStream.WriteAsync(content.ToArray()); Process.Start( new ProcessStartInfo { @@ -91,18 +87,20 @@ async Task InvokeAgentAsync(IAgentThread thread, string imageName, string questi } } - private static OpenAIFileService CreateFileService() + private FileClient CreateFileClient() { - return - ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? - new OpenAIFileService(TestConfiguration.OpenAI.ApiKey) : - new OpenAIFileService(new Uri(TestConfiguration.AzureOpenAI.Endpoint), apiKey: TestConfiguration.AzureOpenAI.ApiKey); + OpenAIClient client = + this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? + new OpenAIClient(TestConfiguration.OpenAI.ApiKey) : + new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), TestConfiguration.AzureOpenAI.ApiKey); + + return client.GetFileClient(); } - private static AgentBuilder CreateAgentBuilder() + private AgentBuilder CreateAgentBuilder() { return - ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? + this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? new AgentBuilder().WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) : new AgentBuilder().WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey); } diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs index c285810fcb74..baf5b249dd33 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Connectors.OpenAI; +using Azure.AI.OpenAI; using Microsoft.SemanticKernel.Experimental.Agents; +using OpenAI; +using OpenAI.Files; using Resources; namespace Agents; @@ -19,16 +20,6 @@ public sealed class Legacy_AgentTools(ITestOutputHelper output) : BaseTest(outpu /// private const string OpenAIFunctionEnabledModel = "gpt-4-1106-preview"; - /// - /// Flag to force usage of OpenAI configuration if both - /// and are defined. - /// If 'false', Azure takes precedence. - /// - /// - /// NOTE: Retrieval tools is not currently available on Azure. - /// - private new const bool ForceOpenAI = true; - // Track agents for clean-up private readonly List _agents = []; @@ -79,12 +70,13 @@ public async Task RunRetrievalToolAsync() return; } - Kernel kernel = CreateFileEnabledKernel(); - var fileService = kernel.GetRequiredService(); - var result = - await fileService.UploadContentAsync( - new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"), - new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants)); + FileClient fileClient = CreateFileClient(); + + OpenAIFileInfo result = + await fileClient.UploadFileAsync( + new BinaryData(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!), + "travelinfo.txt", + FileUploadPurpose.Assistants); var fileId = result.Id; Console.WriteLine($"! {fileId}"); @@ -110,7 +102,7 @@ await ChatAsync( } finally { - await Task.WhenAll(this._agents.Select(a => a.DeleteAsync()).Append(fileService.DeleteFileAsync(fileId))); + await Task.WhenAll(this._agents.Select(a => a.DeleteAsync()).Append(fileClient.DeleteFileAsync(fileId))); } } @@ -165,13 +157,20 @@ async Task InvokeAgentAsync(IAgent agent, string question) } } - private static Kernel CreateFileEnabledKernel() => - Kernel.CreateBuilder().AddOpenAIFiles(TestConfiguration.OpenAI.ApiKey).Build(); + private FileClient CreateFileClient() + { + OpenAIClient client = + this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? + new OpenAIClient(TestConfiguration.OpenAI.ApiKey) : + new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), TestConfiguration.AzureOpenAI.ApiKey); + + return client.GetFileClient(); + } - private static AgentBuilder CreateAgentBuilder() + private AgentBuilder CreateAgentBuilder() { return - ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? + this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? new AgentBuilder().WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) : new AgentBuilder().WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey); } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index a0fa5a074694..0272ed1eb8de 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -1,10 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. using System.Text; +using Azure.AI.OpenAI; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI; +using OpenAI.Files; using Resources; namespace Agents; @@ -22,11 +24,13 @@ public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTe [Fact] public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() { - OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); - OpenAIFileReference uploadFile = - await fileService.UploadContentAsync( - new BinaryContent(await EmbeddedResource.ReadAllAsync("sales.csv"), mimeType: "text/plain"), - new OpenAIFileUploadExecutionSettings("sales.csv", OpenAIFilePurpose.Assistants)); + FileClient fileClient = CreateFileClient(); + + OpenAIFileInfo uploadFile = + await fileClient.UploadFileAsync( + new BinaryData(await EmbeddedResource.ReadAllAsync("sales.csv")!), + "sales.csv", + FileUploadPurpose.Assistants); // Define the agent OpenAIAssistantAgent agent = @@ -53,7 +57,7 @@ await OpenAIAssistantAgent.CreateAsync( finally { await agent.DeleteAsync(); - await fileService.DeleteFileAsync(uploadFile.Id); + await fileClient.DeleteFileAsync(uploadFile.Id); } // Local function to invoke agent and display the conversation messages. @@ -70,9 +74,8 @@ async Task InvokeAgentAsync(string input) foreach (AnnotationContent annotation in message.Items.OfType()) { Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}"); - BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!); - byte[] byteContent = fileContent.Data?.ToArray() ?? []; - Console.WriteLine(Encoding.Default.GetString(byteContent)); + BinaryData content = await fileClient.DownloadFileAsync(annotation.FileId!); + Console.WriteLine(Encoding.Default.GetString(content.ToArray())); } } } @@ -83,4 +86,14 @@ private OpenAIServiceConfiguration GetOpenAIConfiguration() this.UseOpenAIConfig ? OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + + private FileClient CreateFileClient() + { + OpenAIClient client = + this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? + new OpenAIClient(TestConfiguration.OpenAI.ApiKey) : + new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), TestConfiguration.AzureOpenAI.ApiKey); + + return client.GetFileClient(); + } } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs index 9fef5a0c5830..157fb671ac13 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. +using Azure.AI.OpenAI; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI; +using OpenAI.Files; using OpenAI.VectorStores; using Resources; @@ -22,10 +24,13 @@ public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseTest(out [Fact] public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync() { - OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); - OpenAIFileReference uploadFile = - await fileService.UploadContentAsync(new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"), - new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants)); + FileClient fileClient = CreateFileClient(); + + OpenAIFileInfo uploadFile = + await fileClient.UploadFileAsync( + new BinaryData(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!), + "travelinfo.txt", + FileUploadPurpose.Assistants); VectorStore vectorStore = await new OpenAIVectorStoreBuilder(GetOpenAIConfiguration()) @@ -59,7 +64,7 @@ await OpenAIAssistantAgent.CreateAsync( { await agent.DeleteAsync(); await openAIStore.DeleteAsync(); - await fileService.DeleteFileAsync(uploadFile.Id); + await fileClient.DeleteFileAsync(uploadFile.Id); } // Local function to invoke agent and display the conversation messages. @@ -81,4 +86,14 @@ private OpenAIServiceConfiguration GetOpenAIConfiguration() this.UseOpenAIConfig ? OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + + private FileClient CreateFileClient() + { + OpenAIClient client = + this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? + new OpenAIClient(TestConfiguration.OpenAI.ApiKey) : + new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), TestConfiguration.AzureOpenAI.ApiKey); + + return client.GetFileClient(); + } } diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index 7233a8131715..edeb850eafd8 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -105,6 +105,7 @@ + From 9e7e2dc289deaab7aed8d29f154511adeaf80edd Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 13:07:28 -0700 Subject: [PATCH 038/121] Fix concepts resources --- dotnet/samples/Concepts/Concepts.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index edeb850eafd8..87b9ccfec4fc 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -103,6 +103,9 @@ Always + + Always + From 37363fd041534ff5320f12802cd13ef284a75ea4 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 13:18:51 -0700 Subject: [PATCH 039/121] Fix sample migration --- .../Concepts/Agents/Legacy_AgentAuthoring.cs | 16 ++++------ .../Concepts/Agents/Legacy_AgentCharts.cs | 10 ++----- .../Agents/Legacy_AgentCollaboration.cs | 29 +++++-------------- .../Concepts/Agents/Legacy_AgentDelegation.cs | 14 ++------- .../Concepts/Agents/Legacy_AgentTools.cs | 9 ++---- .../samples/Concepts/Agents/Legacy_Agents.cs | 21 +++----------- 6 files changed, 24 insertions(+), 75 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs index 062262fe8a8c..32f8c83d0e6e 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs @@ -9,16 +9,10 @@ namespace Agents; /// public class Legacy_AgentAuthoring(ITestOutputHelper output) : BaseTest(output) { - /// - /// Specific model is required that supports agents and parallel function calling. - /// Currently this is limited to Open AI hosted services. - /// - private const string OpenAIFunctionEnabledModel = "gpt-4-1106-preview"; - // Track agents for clean-up private static readonly List s_agents = []; - [Fact(Skip = "This test take more than 2 minutes to execute")] + [Fact/*(Skip = "This test take more than 2 minutes to execute")*/] public async Task RunAgentAsync() { Console.WriteLine($"======== {nameof(Legacy_AgentAuthoring)} ========"); @@ -40,7 +34,7 @@ public async Task RunAgentAsync() } } - [Fact(Skip = "This test take more than 2 minutes to execute")] + [Fact/*(Skip = "This test take more than 2 minutes to execute")*/] public async Task RunAsPluginAsync() { Console.WriteLine($"======== {nameof(Legacy_AgentAuthoring)} ========"); @@ -72,7 +66,7 @@ private static async Task CreateArticleGeneratorAsync() return Track( await new AgentBuilder() - .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) .WithInstructions("You write concise opinionated articles that are published online. Use an outline to generate an article with one section of prose for each top-level outline element. Each section is based on research with a maximum of 120 words.") .WithName("Article Author") .WithDescription("Author an article on a given topic.") @@ -87,7 +81,7 @@ private static async Task CreateOutlineGeneratorAsync() return Track( await new AgentBuilder() - .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) .WithInstructions("Produce an single-level outline (no child elements) based on the given topic with at most 3 sections.") .WithName("Outline Generator") .WithDescription("Generate an outline.") @@ -100,7 +94,7 @@ private static async Task CreateResearchGeneratorAsync() return Track( await new AgentBuilder() - .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) .WithInstructions("Provide insightful research that supports the given topic based on your knowledge of the outline topic.") .WithName("Researcher") .WithDescription("Author research summary.") diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs index 957b06e51f96..0f93c701683f 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs @@ -14,16 +14,10 @@ namespace Agents; /// public sealed class Legacy_AgentCharts(ITestOutputHelper output) : BaseTest(output) { - /// - /// Specific model is required that supports agents and parallel function calling. - /// Currently this is limited to Open AI hosted services. - /// - private const string OpenAIFunctionEnabledModel = "gpt-4-1106-preview"; - /// /// Create a chart and retrieve by file_id. /// - [Fact(Skip = "Launches external processes")] + [Fact/*(Skip = "Launches external processes")*/] public async Task CreateChartAsync() { Console.WriteLine("======== Using CodeInterpreter tool ========"); @@ -101,7 +95,7 @@ private AgentBuilder CreateAgentBuilder() { return this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? - new AgentBuilder().WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) : + new AgentBuilder().WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) : new AgentBuilder().WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey); } } diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs index 53ae0c07662a..22ccc749f3eb 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs @@ -9,28 +9,15 @@ namespace Agents; /// public class Legacy_AgentCollaboration(ITestOutputHelper output) : BaseTest(output) { - /// - /// Specific model is required that supports agents and function calling. - /// Currently this is limited to Open AI hosted services. - /// - private const string OpenAIFunctionEnabledModel = "gpt-4-turbo-preview"; - - /// - /// Set this to 'true' to target OpenAI instead of Azure OpenAI. - /// - private const bool UseOpenAI = false; - // Track agents for clean-up private static readonly List s_agents = []; /// /// Show how two agents are able to collaborate as agents on a single thread. /// - [Fact(Skip = "This test take more than 5 minutes to execute")] + [Fact/*(Skip = "This test take more than 5 minutes to execute")*/] public async Task RunCollaborationAsync() { - Console.WriteLine($"======== Example72:Collaboration:{(UseOpenAI ? "OpenAI" : "AzureAI")} ========"); - IAgentThread? thread = null; try { @@ -79,11 +66,9 @@ public async Task RunCollaborationAsync() /// While this may achieve an equivalent result to , /// it is not using shared thread state for agent interaction. /// - [Fact(Skip = "This test take more than 2 minutes to execute")] + [Fact/*(Skip = "This test take more than 2 minutes to execute")*/] public async Task RunAsPluginsAsync() { - Console.WriteLine($"======== Example72:AsPlugins:{(UseOpenAI ? "OpenAI" : "AzureAI")} ========"); - try { // Create copy-writer agent to generate ideas @@ -113,7 +98,7 @@ await CreateAgentBuilder() } } - private static async Task CreateCopyWriterAsync(IAgent? agent = null) + private async Task CreateCopyWriterAsync(IAgent? agent = null) { return Track( @@ -125,7 +110,7 @@ await CreateAgentBuilder() .BuildAsync()); } - private static async Task CreateArtDirectorAsync() + private async Task CreateArtDirectorAsync() { return Track( @@ -136,13 +121,13 @@ await CreateAgentBuilder() .BuildAsync()); } - private static AgentBuilder CreateAgentBuilder() + private AgentBuilder CreateAgentBuilder() { var builder = new AgentBuilder(); return - UseOpenAI ? - builder.WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) : + this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? + builder.WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) : builder.WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey); } diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs index 86dacb9c256d..08f7e99096f0 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs @@ -12,12 +12,6 @@ namespace Agents; /// public class Legacy_AgentDelegation(ITestOutputHelper output) : BaseTest(output) { - /// - /// Specific model is required that supports agents and function calling. - /// Currently this is limited to Open AI hosted services. - /// - private const string OpenAIFunctionEnabledModel = "gpt-3.5-turbo-1106"; - // Track agents for clean-up private static readonly List s_agents = []; @@ -27,8 +21,6 @@ public class Legacy_AgentDelegation(ITestOutputHelper output) : BaseTest(output) [Fact] public async Task RunAsync() { - Console.WriteLine("======== Example71_AgentDelegation ========"); - if (TestConfiguration.OpenAI.ApiKey is null) { Console.WriteLine("OpenAI apiKey not found. Skipping example."); @@ -43,7 +35,7 @@ public async Task RunAsync() var menuAgent = Track( await new AgentBuilder() - .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) .FromTemplate(EmbeddedResource.Read("Agents.ToolAgent.yaml")) .WithDescription("Answer questions about how the menu uses the tool.") .WithPlugin(plugin) @@ -52,14 +44,14 @@ public async Task RunAsync() var parrotAgent = Track( await new AgentBuilder() - .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) .FromTemplate(EmbeddedResource.Read("Agents.ParrotAgent.yaml")) .BuildAsync()); var toolAgent = Track( await new AgentBuilder() - .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) .FromTemplate(EmbeddedResource.Read("Agents.ToolAgent.yaml")) .WithPlugin(parrotAgent.AsPlugin()) .WithPlugin(menuAgent.AsPlugin()) diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs index baf5b249dd33..00af8faab617 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs @@ -14,11 +14,8 @@ namespace Agents; /// public sealed class Legacy_AgentTools(ITestOutputHelper output) : BaseTest(output) { - /// - /// Specific model is required that supports agents and parallel function calling. - /// Currently this is limited to Open AI hosted services. - /// - private const string OpenAIFunctionEnabledModel = "gpt-4-1106-preview"; + /// + protected override bool ForceOpenAI => true; // Track agents for clean-up private readonly List _agents = []; @@ -171,7 +168,7 @@ private AgentBuilder CreateAgentBuilder() { return this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? - new AgentBuilder().WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) : + new AgentBuilder().WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) : new AgentBuilder().WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey); } diff --git a/dotnet/samples/Concepts/Agents/Legacy_Agents.cs b/dotnet/samples/Concepts/Agents/Legacy_Agents.cs index 5af10987bb3a..0a03d4c10809 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_Agents.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_Agents.cs @@ -13,19 +13,6 @@ namespace Agents; /// public class Legacy_Agents(ITestOutputHelper output) : BaseTest(output) { - /// - /// Specific model is required that supports agents and function calling. - /// Currently this is limited to Open AI hosted services. - /// - private const string OpenAIFunctionEnabledModel = "gpt-3.5-turbo-1106"; - - /// - /// Flag to force usage of OpenAI configuration if both - /// and are defined. - /// If 'false', Azure takes precedence. - /// - private new const bool ForceOpenAI = false; - /// /// Chat using the "Parrot" agent. /// Tools/functions: None @@ -114,7 +101,7 @@ public async Task RunAsFunctionAsync() // Create parrot agent, same as the other cases. var agent = await new AgentBuilder() - .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) .FromTemplate(EmbeddedResource.Read("Agents.ParrotAgent.yaml")) .BuildAsync(); @@ -187,11 +174,11 @@ await Task.WhenAll( } } - private static AgentBuilder CreateAgentBuilder() + private AgentBuilder CreateAgentBuilder() { return - ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? - new AgentBuilder().WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) : + this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? + new AgentBuilder().WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) : new AgentBuilder().WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey); } } From 1c986cf5f3ebe142efd0f4d77281f4c7131919c4 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 13:24:16 -0700 Subject: [PATCH 040/121] Restore skipped samples --- dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs | 4 ++-- dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs | 2 +- dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs index 32f8c83d0e6e..53276c75a24d 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs @@ -12,7 +12,7 @@ public class Legacy_AgentAuthoring(ITestOutputHelper output) : BaseTest(output) // Track agents for clean-up private static readonly List s_agents = []; - [Fact/*(Skip = "This test take more than 2 minutes to execute")*/] + [Fact(Skip = "This test take more than 2 minutes to execute")] public async Task RunAgentAsync() { Console.WriteLine($"======== {nameof(Legacy_AgentAuthoring)} ========"); @@ -34,7 +34,7 @@ public async Task RunAgentAsync() } } - [Fact/*(Skip = "This test take more than 2 minutes to execute")*/] + [Fact(Skip = "This test take more than 2 minutes to execute")] public async Task RunAsPluginAsync() { Console.WriteLine($"======== {nameof(Legacy_AgentAuthoring)} ========"); diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs index 0f93c701683f..0f1485e9c9be 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs @@ -17,7 +17,7 @@ public sealed class Legacy_AgentCharts(ITestOutputHelper output) : BaseTest(outp /// /// Create a chart and retrieve by file_id. /// - [Fact/*(Skip = "Launches external processes")*/] + [Fact] public async Task CreateChartAsync() { Console.WriteLine("======== Using CodeInterpreter tool ========"); diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs index 22ccc749f3eb..fa257d2764b3 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs @@ -15,7 +15,7 @@ public class Legacy_AgentCollaboration(ITestOutputHelper output) : BaseTest(outp /// /// Show how two agents are able to collaborate as agents on a single thread. /// - [Fact/*(Skip = "This test take more than 5 minutes to execute")*/] + [Fact(Skip = "This test take more than 5 minutes to execute")] public async Task RunCollaborationAsync() { IAgentThread? thread = null; @@ -66,7 +66,7 @@ public async Task RunCollaborationAsync() /// While this may achieve an equivalent result to , /// it is not using shared thread state for agent interaction. /// - [Fact/*(Skip = "This test take more than 2 minutes to execute")*/] + [Fact(Skip = "This test take more than 2 minutes to execute")] public async Task RunAsPluginsAsync() { try From 448af97d22d95663e80da6b7d4299e879abbbf03 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 12 Jul 2024 13:32:15 -0700 Subject: [PATCH 041/121] /sigh --- dotnet/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index ac2156a1b8bf..47a1a49b4c68 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -5,7 +5,7 @@ true - + From c92f87fa29feab934edbc3984d9ec8f947ddd2cd Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 17 Jul 2024 10:21:57 -0700 Subject: [PATCH 042/121] Fix merge --- dotnet/samples/Concepts/Concepts.csproj | 1 + dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index 61b47a1c62eb..2858e6d54429 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -153,6 +153,7 @@ + diff --git a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs b/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs index b0affa1e68a2..eb5ed86a77e3 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs @@ -4,6 +4,7 @@ using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AzureOpenAI; +using Microsoft.SemanticKernel.Connectors.OpenAI; namespace GettingStarted; @@ -26,7 +27,7 @@ public async Task UseChatCompletionWithPluginAgentAsync() Instructions = HostInstructions, Name = HostName, Kernel = this.CreateKernelWithChatCompletion(), - ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = AzureOpenAIToolCallBehavior.AutoInvokeKernelFunctions }, + ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, }; // Initialize plugin and add to the agent's Kernel (same as direct Kernel usage). From 9205cec1dbf03d3e8948426297b5b08c8aa6e6b3 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 17 Jul 2024 11:20:16 -0700 Subject: [PATCH 043/121] Fix build --- .../OpenAI/Internal/AssistantThreadActions.cs | 8 ++++---- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 2 +- .../Agents/ChatCompletionAgentTests.cs | 15 +-------------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index ca012ca9b268..901017e94fe7 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -42,10 +42,10 @@ internal static class AssistantThreadActions /// The message to add /// The to monitor for cancellation requests. The default is . /// if a system message is present, without taking any other action + public static async Task CreateMessageAsync(AssistantClient client, string threadId, ChatMessageContent message, CancellationToken cancellationToken) + { if (string.IsNullOrEmpty(message.Content) || message.Items.Any(i => i is FunctionCallContent)) - - if (string.IsNullOrWhiteSpace(message.Content)) { return; } @@ -224,10 +224,10 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist ChatMessageContent? content = null; // Process code-interpreter content + if (toolCall.ToolKind == RunStepToolCallKind.CodeInterpreter) + { content = GenerateCodeInterpreterContent(agent.GetName(), toolCall.CodeInterpreterInput); isVisible = true; - { - content = GenerateCodeInterpreterContent(agent.GetName(), toolCodeInterpreter); } // Process function result content else if (toolCall.ToolKind == RunStepToolCallKind.Function) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 00ad90c05422..f6555ebff6d7 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -247,7 +247,7 @@ public async IAsyncEnumerable InvokeAsync( { this.ThrowIfDeleted(); - await foreach ((bool isVisible, ChatMessageContent message) in AssistantThreadActions.InvokeAsync(this, this._client, threadId, this._config.Polling, this.Logger, cancellationToken).ConfigureAwait(false)) + await foreach ((bool isVisible, ChatMessageContent message) in AssistantThreadActions.InvokeAsync(this, this._client, threadId, settings, this.Logger, cancellationToken).ConfigureAwait(false)) { if (isVisible) { diff --git a/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs b/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs index 91796c1970b0..fa4f75a34331 100644 --- a/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs +++ b/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs @@ -5,20 +5,18 @@ using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using SemanticKernel.IntegrationTests.TestSettings; using Xunit; -using Xunit.Abstractions; namespace SemanticKernel.IntegrationTests.Agents.OpenAI; #pragma warning disable xUnit1004 // Contains test methods used in manual verification. Disable warning for this file only. -public sealed class ChatCompletionAgentTests(ITestOutputHelper output) : IDisposable +public sealed class ChatCompletionAgentTests() { private readonly IKernelBuilder _kernelBuilder = Kernel.CreateBuilder(); private readonly IConfigurationRoot _configuration = new ConfigurationBuilder() @@ -42,8 +40,6 @@ public async Task AzureChatCompletionAgentAsync(string input, string expectedAns KernelPlugin plugin = KernelPluginFactory.CreateFromType(); - this._kernelBuilder.Services.AddSingleton(this._logger); - this._kernelBuilder.AddAzureOpenAIChatCompletion( configuration.ChatDeploymentName!, configuration.Endpoint, @@ -94,15 +90,6 @@ public async Task AzureChatCompletionAgentAsync(string input, string expectedAns Assert.Contains(expectedAnswerContains, messages.Single().Content, StringComparison.OrdinalIgnoreCase); } - private readonly XunitLogger _logger = new(output); - private readonly RedirectOutput _testOutputHelper = new(output); - - public void Dispose() - { - this._logger.Dispose(); - this._testOutputHelper.Dispose(); - } - public sealed class MenuPlugin { [KernelFunction, Description("Provides a list of specials from the menu.")] From 712c826b9022fee5e88c48ecc61d189d2f87f5e0 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 17 Jul 2024 11:35:25 -0700 Subject: [PATCH 044/121] Fix merge --- .../Concepts/Agents/ChatCompletion_FunctionTermination.cs | 5 +++-- .../IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs index f344dae432b9..f90f38587131 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs @@ -3,6 +3,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.AzureOpenAI; using Microsoft.SemanticKernel.Connectors.OpenAI; namespace Agents; @@ -22,7 +23,7 @@ public async Task UseAutoFunctionInvocationFilterWithAgentInvocationAsync() { Instructions = "Answer questions about the menu.", Kernel = CreateKernelWithChatCompletion(), - ExecutionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, + ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, }; KernelPlugin plugin = KernelPluginFactory.CreateFromType(); @@ -75,7 +76,7 @@ public async Task UseAutoFunctionInvocationFilterWithAgentChatAsync() { Instructions = "Answer questions about the menu.", Kernel = CreateKernelWithChatCompletion(), - ExecutionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, + ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, }; KernelPlugin plugin = KernelPluginFactory.CreateFromType(); diff --git a/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs b/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs index fa4f75a34331..e6f06b766053 100644 --- a/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs +++ b/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs @@ -8,6 +8,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.AzureOpenAI; using Microsoft.SemanticKernel.Connectors.OpenAI; using SemanticKernel.IntegrationTests.TestSettings; using Xunit; @@ -59,7 +60,7 @@ public async Task AzureChatCompletionAgentAsync(string input, string expectedAns { Kernel = kernel, Instructions = "Answer questions about the menu.", - ExecutionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, + ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, }; AgentGroupChat chat = new(); From 58d01e760b1d71db3b76b57f0c862ed5f725f961 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 08:51:18 -0700 Subject: [PATCH 045/121] Definition property update --- dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index 53546d44fb5f..f07983a755a5 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -11,7 +11,7 @@ public sealed class OpenAIAssistantDefinition /// /// Identifies the AI model targeted by the agent. /// - public string? ModelName { get; init; } + public string ModelName { get; init; } = string.Empty; /// /// The description of the assistant. @@ -21,7 +21,7 @@ public sealed class OpenAIAssistantDefinition /// /// The assistant's unique id. (Ignored on create.) /// - public string? Id { get; init; } + public string Id { get; init; } = string.Empty; /// /// The system instructions for the assistant to use. From 080d21e295c2fb054264e07583799e8313a4f230 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 08:59:08 -0700 Subject: [PATCH 046/121] UT update --- .../Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index e692166986eb..f56f6da193aa 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -18,7 +18,7 @@ public void VerifyOpenAIAssistantDefinitionInitialState() { OpenAIAssistantDefinition definition = new(); - Assert.Null(definition.Id); + Assert.Equal(string.Empty, definition.Id); Assert.Null(definition.Name); Assert.Null(definition.ModelName); Assert.Null(definition.Instructions); From 31e30517da92dd6fc79be0ea974e9a471157803a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 08:59:47 -0700 Subject: [PATCH 047/121] More UT --- .../Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index f56f6da193aa..2cf5abb3f48e 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -19,8 +19,8 @@ public void VerifyOpenAIAssistantDefinitionInitialState() OpenAIAssistantDefinition definition = new(); Assert.Equal(string.Empty, definition.Id); + Assert.Equal(string.Empty, definition.ModelName); Assert.Null(definition.Name); - Assert.Null(definition.ModelName); Assert.Null(definition.Instructions); Assert.Null(definition.Description); Assert.Null(definition.Metadata); From a33dc5e4c8013d4839da28d7132fd8bf974fb655 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 09:21:47 -0700 Subject: [PATCH 048/121] Rename --- .../OpenAI/Internal/AssistantThreadActions.cs | 16 ++++++++-------- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 16 ++++++++-------- .../Agents/OpenAI/OpenAIAssistantDefinition.cs | 4 ++-- ...ngs.cs => OpenAIAssistantExecutionOptions.cs} | 6 +++--- ...gs.cs => OpenAIAssistantInvocationOptions.cs} | 4 ++-- ...ettings.cs => OpenAIThreadCreationOptions.cs} | 4 ++-- ...lingConfiguration.cs => RunPollingOptions.cs} | 2 +- .../OpenAI/OpenAIAssistantAgentTests.cs | 2 +- .../OpenAI/OpenAIAssistantDefinitionTests.cs | 14 +++++++------- ... => OpenAIAssistantInvocationOptionsTests.cs} | 8 ++++---- ...ts.cs => OpenAIThreadCreationOptionsTests.cs} | 8 ++++---- 11 files changed, 42 insertions(+), 42 deletions(-) rename dotnet/src/Agents/OpenAI/{OpenAIAssistantExecutionSettings.cs => OpenAIAssistantExecutionOptions.cs} (83%) rename dotnet/src/Agents/OpenAI/{OpenAIAssistantInvocationSettings.cs => OpenAIAssistantInvocationOptions.cs} (94%) rename dotnet/src/Agents/OpenAI/{OpenAIThreadCreationSettings.cs => OpenAIThreadCreationOptions.cs} (93%) rename dotnet/src/Agents/OpenAI/{RunPollingConfiguration.cs => RunPollingOptions.cs} (97%) rename dotnet/src/Agents/UnitTests/OpenAI/{OpenAIAssistantInvocationSettingsTests.cs => OpenAIAssistantInvocationOptionsTests.cs} (90%) rename dotnet/src/Agents/UnitTests/OpenAI/{OpenAIThreadCreationSettingsTests.cs => OpenAIThreadCreationOptionsTests.cs} (86%) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 901017e94fe7..4cde6904b44a 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -145,7 +145,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist OpenAIAssistantAgent agent, AssistantClient client, string threadId, - OpenAIAssistantInvocationSettings? invocationSettings, + OpenAIAssistantInvocationOptions? invocationSettings, ILogger logger, [EnumeratorCancellation] CancellationToken cancellationToken) { @@ -286,7 +286,7 @@ async Task PollRunStatusAsync() do { // Reduce polling frequency after a couple attempts - await Task.Delay(count >= 2 ? agent.Polling.RunPollingInterval : agent.Polling.RunPollingBackoff, cancellationToken).ConfigureAwait(false); + await Task.Delay(count >= 2 ? agent.PollingOptions.RunPollingInterval : agent.PollingOptions.RunPollingBackoff, cancellationToken).ConfigureAwait(false); ++count; #pragma warning disable CA1031 // Do not catch general exception types @@ -355,7 +355,7 @@ IEnumerable ParseFunctionStep(OpenAIAssistantAgent agent, R if (retry) { - await Task.Delay(agent.Polling.MessageSynchronizationDelay, cancellationToken).ConfigureAwait(false); + await Task.Delay(agent.PollingOptions.MessageSynchronizationDelay, cancellationToken).ConfigureAwait(false); } ++count; @@ -489,18 +489,18 @@ private static ToolOutput[] GenerateToolOutputs(FunctionResultContent[] function return toolOutputs; } - private static RunCreationOptions GenerateRunCreationOptions(OpenAIAssistantAgent agent, OpenAIAssistantInvocationSettings? invocationSettings) + private static RunCreationOptions GenerateRunCreationOptions(OpenAIAssistantAgent agent, OpenAIAssistantInvocationOptions? invocationSettings) { - int? truncationMessageCount = ResolveExecutionSetting(invocationSettings?.TruncationMessageCount, agent.Definition.ExecutionSettings?.TruncationMessageCount); + int? truncationMessageCount = ResolveExecutionSetting(invocationSettings?.TruncationMessageCount, agent.Definition.ExecutionOptions?.TruncationMessageCount); RunCreationOptions options = new() { - MaxCompletionTokens = ResolveExecutionSetting(invocationSettings?.MaxCompletionTokens, agent.Definition.ExecutionSettings?.MaxCompletionTokens), - MaxPromptTokens = ResolveExecutionSetting(invocationSettings?.MaxPromptTokens, agent.Definition.ExecutionSettings?.MaxPromptTokens), + MaxCompletionTokens = ResolveExecutionSetting(invocationSettings?.MaxCompletionTokens, agent.Definition.ExecutionOptions?.MaxCompletionTokens), + MaxPromptTokens = ResolveExecutionSetting(invocationSettings?.MaxPromptTokens, agent.Definition.ExecutionOptions?.MaxPromptTokens), ModelOverride = invocationSettings?.ModelName, NucleusSamplingFactor = ResolveExecutionSetting(invocationSettings?.TopP, agent.Definition.TopP), - ParallelToolCallsEnabled = ResolveExecutionSetting(invocationSettings?.ParallelToolCallsEnabled, agent.Definition.ExecutionSettings?.ParallelToolCallsEnabled), + ParallelToolCallsEnabled = ResolveExecutionSetting(invocationSettings?.ParallelToolCallsEnabled, agent.Definition.ExecutionOptions?.ParallelToolCallsEnabled), ResponseFormat = ResolveExecutionSetting(invocationSettings?.EnableJsonResponse, agent.Definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, Temperature = ResolveExecutionSetting(invocationSettings?.Temperature, agent.Definition.Temperature), //ToolConstraint // %%% TODO diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index f6555ebff6d7..48c17a03f745 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -36,7 +36,7 @@ public sealed class OpenAIAssistantAgent : KernelAgent /// /// Defines polling behavior for run processing /// - public RunPollingConfiguration Polling { get; } = new(); + public RunPollingOptions PollingOptions { get; } = new(); /// /// Expose predefined tools merged with available kernel functions. @@ -139,7 +139,7 @@ public Task CreateThreadAsync(CancellationToken cancellationToken = defa /// %%% /// The to monitor for cancellation requests. The default is . /// The thread identifier - public async Task CreateThreadAsync(OpenAIThreadCreationSettings? settings, CancellationToken cancellationToken = default) + public async Task CreateThreadAsync(OpenAIThreadCreationOptions? settings, CancellationToken cancellationToken = default) { ThreadCreationOptions options = new() @@ -242,7 +242,7 @@ public IAsyncEnumerable InvokeAsync( /// Asynchronous enumeration of messages. public async IAsyncEnumerable InvokeAsync( string threadId, - OpenAIAssistantInvocationSettings? settings, + OpenAIAssistantInvocationOptions? settings, [EnumeratorCancellation] CancellationToken cancellationToken = default) { this.ThrowIfDeleted(); @@ -309,11 +309,11 @@ private OpenAIAssistantAgent( private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant model) { - OpenAIAssistantExecutionSettings? settings = null; + OpenAIAssistantExecutionOptions? settings = null; if (model.Metadata.TryGetValue(SettingsMetadataKey, out string? settingsJson)) { - settings = JsonSerializer.Deserialize(settingsJson); + settings = JsonSerializer.Deserialize(settingsJson); } IReadOnlyList? fileIds = (IReadOnlyList?)model.ToolResources?.CodeInterpreter?.FileIds; @@ -335,7 +335,7 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod TopP = model.NucleusSamplingFactor, Temperature = model.Temperature, VectorStoreId = vectorStoreId, - ExecutionSettings = settings, + ExecutionOptions = settings, }; } @@ -361,9 +361,9 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss } } - if (definition.ExecutionSettings != null) + if (definition.ExecutionOptions != null) { - string settingsJson = JsonSerializer.Serialize(definition.ExecutionSettings); + string settingsJson = JsonSerializer.Serialize(definition.ExecutionOptions); assistantCreationOptions.Metadata[SettingsMetadataKey] = settingsJson; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index f07983a755a5..ae66aab1502b 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -76,7 +76,7 @@ public sealed class OpenAIAssistantDefinition public string? VectorStoreId { get; init; } /// - /// Default execution settings for each agent invocation. + /// Default execution options for each agent invocation. /// - public OpenAIAssistantExecutionSettings? ExecutionSettings { get; init; } + public OpenAIAssistantExecutionOptions? ExecutionOptions { get; init; } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs similarity index 83% rename from dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs rename to dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs index 6969310ad83c..48f327a46489 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionSettings.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs @@ -2,12 +2,12 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// Defines agent execution settings for each invocation. +/// Defines agent execution options for each invocation. /// /// -/// These settings are persisted as a single entry of the agent's metadata with key: "__settings" +/// These options are persisted as a single entry of the agent's metadata with key: "__settings" /// -public sealed class OpenAIAssistantExecutionSettings +public sealed class OpenAIAssistantExecutionOptions { /// /// The maximum number of completion tokens that may be used over the course of the run. diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs similarity index 94% rename from dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs rename to dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs index 2e9c61eb05e3..2fee5dd84503 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationSettings.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs @@ -4,12 +4,12 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// Defines per invocation execution settings that override the assistant's default settings. +/// Defines per invocation execution options that override the assistant definition. /// /// /// Not applicable to usage. /// -public sealed class OpenAIAssistantInvocationSettings +public sealed class OpenAIAssistantInvocationOptions { /// /// Override the AI model targeted by the agent. diff --git a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs similarity index 93% rename from dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs rename to dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs index 614ad64f6ba2..56b3aa167284 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationSettings.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs @@ -4,9 +4,9 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// Thread creation settings. +/// Thread creation options. /// -public sealed class OpenAIThreadCreationSettings +public sealed class OpenAIThreadCreationOptions { /// /// Optional file-ids made available to the code_interpreter tool, if enabled. diff --git a/dotnet/src/Agents/OpenAI/RunPollingConfiguration.cs b/dotnet/src/Agents/OpenAI/RunPollingOptions.cs similarity index 97% rename from dotnet/src/Agents/OpenAI/RunPollingConfiguration.cs rename to dotnet/src/Agents/OpenAI/RunPollingOptions.cs index e534128a4e49..7dd71cfdd8b2 100644 --- a/dotnet/src/Agents/OpenAI/RunPollingConfiguration.cs +++ b/dotnet/src/Agents/OpenAI/RunPollingOptions.cs @@ -6,7 +6,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Configuration and defaults associated with polling behavior for Assistant API run processing. /// -public sealed class RunPollingConfiguration +public sealed class RunPollingOptions { /// /// The default polling interval when monitoring thread-run status. diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index 3d67537cb590..8eab72d37805 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -164,7 +164,7 @@ public async Task VerifyOpenAIAssistantAgentCreationEverything3Async() // %%% NA EnableJsonResponse = true, CodeInterpterFileIds = ["file1", "file2"], Metadata = new Dictionary() { { "a", "1" } }, - ExecutionSettings = new(), + ExecutionOptions = new(), }; this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentWithEverything); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index 2cf5abb3f48e..bf38207c3026 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -24,7 +24,7 @@ public void VerifyOpenAIAssistantDefinitionInitialState() Assert.Null(definition.Instructions); Assert.Null(definition.Description); Assert.Null(definition.Metadata); - Assert.Null(definition.ExecutionSettings); + Assert.Null(definition.ExecutionOptions); Assert.Null(definition.Temperature); Assert.Null(definition.TopP); Assert.Null(definition.VectorStoreId); @@ -51,7 +51,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Metadata = new Dictionary() { { "a", "1" } }, Temperature = 2, TopP = 0, - ExecutionSettings = + ExecutionOptions = new() { MaxCompletionTokens = 1000, @@ -72,11 +72,11 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Assert.Equal("#vs", definition.VectorStoreId); Assert.Equal(2, definition.Temperature); Assert.Equal(0, definition.TopP); - Assert.NotNull(definition.ExecutionSettings); - Assert.Equal(1000, definition.ExecutionSettings.MaxCompletionTokens); - Assert.Equal(1000, definition.ExecutionSettings.MaxPromptTokens); - Assert.Equal(12, definition.ExecutionSettings.TruncationMessageCount); - Assert.False(definition.ExecutionSettings.ParallelToolCallsEnabled); + Assert.NotNull(definition.ExecutionOptions); + Assert.Equal(1000, definition.ExecutionOptions.MaxCompletionTokens); + Assert.Equal(1000, definition.ExecutionOptions.MaxPromptTokens); + Assert.Equal(12, definition.ExecutionOptions.TruncationMessageCount); + Assert.False(definition.ExecutionOptions.ParallelToolCallsEnabled); Assert.Single(definition.Metadata); Assert.Single(definition.CodeInterpterFileIds); Assert.True(definition.EnableCodeInterpreter); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationSettingsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs similarity index 90% rename from dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationSettingsTests.cs rename to dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs index ac9ae051bf4d..8a8669e30f07 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationSettingsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs @@ -6,9 +6,9 @@ namespace SemanticKernel.Agents.UnitTests.OpenAI; /// -/// Unit testing of . +/// Unit testing of . /// -public class OpenAIAssistantInvocationSettingsTests +public class OpenAIAssistantInvocationOptionsTests { /// /// Verify initial state. @@ -16,7 +16,7 @@ public class OpenAIAssistantInvocationSettingsTests [Fact] public void OpenAIAssistantInvocationSettingsInitialState() { - OpenAIAssistantInvocationSettings settings = new(); + OpenAIAssistantInvocationOptions settings = new(); Assert.Null(settings.ModelName); Assert.Null(settings.Metadata); @@ -37,7 +37,7 @@ public void OpenAIAssistantInvocationSettingsInitialState() [Fact] public void OpenAIAssistantInvocationSettingsAssignment() { - OpenAIAssistantInvocationSettings settings = + OpenAIAssistantInvocationOptions settings = new() { ModelName = "testmodel", diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationSettingsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs similarity index 86% rename from dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationSettingsTests.cs rename to dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs index 0c096c0ba62e..545a338f5217 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationSettingsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs @@ -8,9 +8,9 @@ namespace SemanticKernel.Agents.UnitTests.OpenAI; /// -/// Unit testing of . +/// Unit testing of . /// -public class OpenAIThreadCreationSettingsTests +public class OpenAIThreadCreationOptionsTests { /// /// Verify initial state. @@ -18,7 +18,7 @@ public class OpenAIThreadCreationSettingsTests [Fact] public void OpenAIThreadCreationSettingsInitialState() { - OpenAIThreadCreationSettings settings = new(); + OpenAIThreadCreationOptions settings = new(); Assert.Null(settings.Messages); Assert.Null(settings.Metadata); @@ -33,7 +33,7 @@ public void OpenAIThreadCreationSettingsInitialState() [Fact] public void OpenAIThreadCreationSettingsAssignment() { - OpenAIThreadCreationSettings definition = + OpenAIThreadCreationOptions definition = new() { Messages = [new ChatMessageContent(AuthorRole.User, "test")], From 84c0a1f2bdd283d2663c4932ce6d9b83502f97d6 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 11:46:28 -0700 Subject: [PATCH 049/121] Rename and UT stubs --- .../OpenAIAssistant_FileManipulation.cs | 17 +- .../Agents/OpenAIAssistant_FileSearch.cs | 34 +-- dotnet/samples/Concepts/Concepts.csproj | 2 +- .../OpenAIServiceConfigurationExtensions.cs | 36 +++ .../Extensions/RunPollingOptionsExtensions.cs | 24 ++ .../Internal/AssistantRunOptionsFactory.cs | 12 + .../OpenAI/Internal/AssistantThreadActions.cs | 36 +-- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 40 +-- .../Agents/OpenAI/OpenAIAssistantChannel.cs | 2 +- .../OpenAI/OpenAIAssistantExecutionOptions.cs | 2 +- dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs | 88 ------- .../Agents/OpenAI/OpenAIVectorStoreBuilder.cs | 142 ----------- ...enAIServiceConfigurationExtensionsTests.cs | 15 ++ .../AssistantRunOptionsFactoryTests.cs | 15 ++ .../OpenAIAssistantInvocationOptionsTests.cs | 52 ++-- .../OpenAIThreadCreationOptionsTests.cs | 16 +- .../OpenAI/OpenAIVectorStoreBuilderTests.cs | 129 ---------- .../OpenAI/OpenAIVectorStoreTests.cs | 241 ------------------ .../OpenAI/RunPollingOptionsTests.cs | 52 ++++ 19 files changed, 249 insertions(+), 706 deletions(-) create mode 100644 dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs create mode 100644 dotnet/src/Agents/OpenAI/Extensions/RunPollingOptionsExtensions.cs create mode 100644 dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs delete mode 100644 dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs delete mode 100644 dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs delete mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreBuilderTests.cs delete mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index 0272ed1eb8de..e325a672ac5e 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -4,6 +4,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI; using OpenAI.Files; @@ -24,7 +25,9 @@ public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTe [Fact] public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() { - FileClient fileClient = CreateFileClient(); + OpenAIServiceConfiguration config = GetOpenAIConfiguration(); + + FileClient fileClient = config.CreateFileClient(); OpenAIFileInfo uploadFile = await fileClient.UploadFileAsync( @@ -36,7 +39,7 @@ await fileClient.UploadFileAsync( OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: GetOpenAIConfiguration(), + config, new() { CodeInterpterFileIds = [uploadFile.Id], @@ -86,14 +89,4 @@ private OpenAIServiceConfiguration GetOpenAIConfiguration() this.UseOpenAIConfig ? OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); - - private FileClient CreateFileClient() - { - OpenAIClient client = - this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? - new OpenAIClient(TestConfiguration.OpenAI.ApiKey) : - new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), TestConfiguration.AzureOpenAI.ApiKey); - - return client.GetFileClient(); - } } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs index 157fb671ac13..091ce3f52a3f 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs @@ -3,6 +3,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI; using OpenAI.Files; @@ -24,7 +25,9 @@ public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseTest(out [Fact] public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync() { - FileClient fileClient = CreateFileClient(); + OpenAIServiceConfiguration config = GetOpenAIConfiguration(); + + FileClient fileClient = config.CreateFileClient(); OpenAIFileInfo uploadFile = await fileClient.UploadFileAsync( @@ -32,18 +35,19 @@ await fileClient.UploadFileAsync( "travelinfo.txt", FileUploadPurpose.Assistants); - VectorStore vectorStore = - await new OpenAIVectorStoreBuilder(GetOpenAIConfiguration()) - .AddFile(uploadFile.Id) - .CreateAsync(); - - OpenAIVectorStore openAIStore = new(vectorStore.Id, GetOpenAIConfiguration()); + VectorStoreClient vectorStoreClient = config.CreateVectorStoreClient(); + VectorStoreCreationOptions vectorStoreOptions = + new() + { + FileIds = [uploadFile.Id] + }; + VectorStore vectorStore = await vectorStoreClient.CreateVectorStoreAsync(vectorStoreOptions); // Define the agent OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: GetOpenAIConfiguration(), + config, new() { ModelName = this.Model, @@ -63,8 +67,8 @@ await OpenAIAssistantAgent.CreateAsync( finally { await agent.DeleteAsync(); - await openAIStore.DeleteAsync(); - await fileClient.DeleteFileAsync(uploadFile.Id); + await vectorStoreClient.DeleteVectorStoreAsync(vectorStore); + await fileClient.DeleteFileAsync(uploadFile); } // Local function to invoke agent and display the conversation messages. @@ -86,14 +90,4 @@ private OpenAIServiceConfiguration GetOpenAIConfiguration() this.UseOpenAIConfig ? OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); - - private FileClient CreateFileClient() - { - OpenAIClient client = - this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ? - new OpenAIClient(TestConfiguration.OpenAI.ApiKey) : - new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), TestConfiguration.AzureOpenAI.ApiKey); - - return client.GetFileClient(); - } } diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index 854d52f7eadb..5001100fae7d 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -8,7 +8,7 @@ false true - $(NoWarn);CS8618,IDE0009,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110 + $(NoWarn);CS8618,IDE0009,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110,OPENAI001 Library 5ee045b0-aea3-4f08-8d31-32d1a6f8fed0 diff --git a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs new file mode 100644 index 000000000000..79a60452d2fd --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. +using OpenAI; +using OpenAI.Files; +using OpenAI.VectorStores; + +namespace Microsoft.SemanticKernel.Agents.OpenAI.Extensions; + +/// +/// %%% +/// +public static class OpenAIServiceConfigurationExtensions +{ + /// + /// %%% + /// + /// + /// + public static FileClient CreateFileClient(this OpenAIServiceConfiguration configuration) + { + OpenAIClient client = OpenAIClientFactory.CreateClient(configuration); + + return client.GetFileClient(); + } + + /// + /// %%% + /// + /// + /// + public static VectorStoreClient CreateVectorStoreClient(this OpenAIServiceConfiguration configuration) + { + OpenAIClient client = OpenAIClientFactory.CreateClient(configuration); + + return client.GetVectorStoreClient(); + } +} diff --git a/dotnet/src/Agents/OpenAI/Extensions/RunPollingOptionsExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/RunPollingOptionsExtensions.cs new file mode 100644 index 000000000000..0bbe3d17ac97 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Extensions/RunPollingOptionsExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.SemanticKernel.Agents.OpenAI.Extensions; + +internal static class RunPollingOptionsExtensions +{ + /// + /// %%% + /// + /// + /// + /// + /// + public async static Task WaitAsync(this RunPollingOptions pollingOptions, int pollIterationCount, CancellationToken cancellationToken) + { + await Task.Delay( + pollIterationCount >= 2 ? + pollingOptions.RunPollingInterval : + pollingOptions.RunPollingBackoff, + cancellationToken).ConfigureAwait(false); + } +} diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs new file mode 100644 index 000000000000..d28574bf2bcb --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; + +internal class AssistantRunOptionsFactory +{ +} diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 4cde6904b44a..c5ebd60e6236 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Azure; using Microsoft.Extensions.Logging; +using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI; using OpenAI.Assistants; @@ -137,7 +138,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist /// The assistant agent to interact with the thread. /// The assistant client /// The thread identifier - /// Optional settings to utilize for the invocation + /// Options to utilize for the invocation /// The logger to utilize (might be agent or channel scoped) /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. @@ -145,7 +146,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist OpenAIAssistantAgent agent, AssistantClient client, string threadId, - OpenAIAssistantInvocationOptions? invocationSettings, + OpenAIAssistantInvocationOptions? invocationOptions, ILogger logger, [EnumeratorCancellation] CancellationToken cancellationToken) { @@ -156,7 +157,10 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist logger.LogOpenAIAssistantCreatingRun(nameof(InvokeAsync), threadId); - RunCreationOptions options = GenerateRunCreationOptions(agent, invocationSettings); + RunCreationOptions options = GenerateRunCreationOptions(agent.Definition, invocationOptions); + + options.ToolsOverride.AddRange(agent.Tools); // %%% + ThreadRun run = await client.CreateRunAsync(threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); logger.LogOpenAIAssistantCreatedRun(nameof(InvokeAsync), run.Id, threadId); @@ -286,7 +290,7 @@ async Task PollRunStatusAsync() do { // Reduce polling frequency after a couple attempts - await Task.Delay(count >= 2 ? agent.PollingOptions.RunPollingInterval : agent.PollingOptions.RunPollingBackoff, cancellationToken).ConfigureAwait(false); + await agent.PollingOptions.WaitAsync(count, cancellationToken).ConfigureAwait(false); ++count; #pragma warning disable CA1031 // Do not catch general exception types @@ -489,29 +493,27 @@ private static ToolOutput[] GenerateToolOutputs(FunctionResultContent[] function return toolOutputs; } - private static RunCreationOptions GenerateRunCreationOptions(OpenAIAssistantAgent agent, OpenAIAssistantInvocationOptions? invocationSettings) + private static RunCreationOptions GenerateRunCreationOptions(OpenAIAssistantDefinition definition, OpenAIAssistantInvocationOptions? invocationOptions) { - int? truncationMessageCount = ResolveExecutionSetting(invocationSettings?.TruncationMessageCount, agent.Definition.ExecutionOptions?.TruncationMessageCount); + int? truncationMessageCount = ResolveExecutionSetting(invocationOptions?.TruncationMessageCount, definition.ExecutionOptions?.TruncationMessageCount); RunCreationOptions options = new() { - MaxCompletionTokens = ResolveExecutionSetting(invocationSettings?.MaxCompletionTokens, agent.Definition.ExecutionOptions?.MaxCompletionTokens), - MaxPromptTokens = ResolveExecutionSetting(invocationSettings?.MaxPromptTokens, agent.Definition.ExecutionOptions?.MaxPromptTokens), - ModelOverride = invocationSettings?.ModelName, - NucleusSamplingFactor = ResolveExecutionSetting(invocationSettings?.TopP, agent.Definition.TopP), - ParallelToolCallsEnabled = ResolveExecutionSetting(invocationSettings?.ParallelToolCallsEnabled, agent.Definition.ExecutionOptions?.ParallelToolCallsEnabled), - ResponseFormat = ResolveExecutionSetting(invocationSettings?.EnableJsonResponse, agent.Definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, - Temperature = ResolveExecutionSetting(invocationSettings?.Temperature, agent.Definition.Temperature), + MaxCompletionTokens = ResolveExecutionSetting(invocationOptions?.MaxCompletionTokens, definition.ExecutionOptions?.MaxCompletionTokens), + MaxPromptTokens = ResolveExecutionSetting(invocationOptions?.MaxPromptTokens, definition.ExecutionOptions?.MaxPromptTokens), + ModelOverride = invocationOptions?.ModelName, + NucleusSamplingFactor = ResolveExecutionSetting(invocationOptions?.TopP, definition.TopP), + ParallelToolCallsEnabled = ResolveExecutionSetting(invocationOptions?.ParallelToolCallsEnabled, definition.ExecutionOptions?.ParallelToolCallsEnabled), + ResponseFormat = ResolveExecutionSetting(invocationOptions?.EnableJsonResponse, definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, + Temperature = ResolveExecutionSetting(invocationOptions?.Temperature, definition.Temperature), //ToolConstraint // %%% TODO TruncationStrategy = truncationMessageCount.HasValue ? RunTruncationStrategy.CreateLastMessagesStrategy(truncationMessageCount.Value) : null, }; - options.ToolsOverride.AddRange(agent.Tools); - - if (invocationSettings?.Metadata != null) + if (invocationOptions?.Metadata != null) { - foreach (var metadata in invocationSettings.Metadata) + foreach (var metadata in invocationOptions.Metadata) { options.Metadata.Add(metadata.Key, metadata.Value ?? string.Empty); } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 48c17a03f745..59df7c9d439d 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -16,7 +16,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// public sealed class OpenAIAssistantAgent : KernelAgent { - private const string SettingsMetadataKey = "__settings"; + private const string OptionsMetadataKey = "__run_options"; private readonly Assistant _assistant; private readonly AssistantClient _client; @@ -131,33 +131,33 @@ public static async Task RetrieveAsync( /// The to monitor for cancellation requests. The default is . /// The thread identifier public Task CreateThreadAsync(CancellationToken cancellationToken = default) - => this.CreateThreadAsync(settings: null, cancellationToken); + => this.CreateThreadAsync(options: null, cancellationToken); /// /// Create a new assistant thread. /// - /// %%% + /// %%% /// The to monitor for cancellation requests. The default is . /// The thread identifier - public async Task CreateThreadAsync(OpenAIThreadCreationOptions? settings, CancellationToken cancellationToken = default) + public async Task CreateThreadAsync(OpenAIThreadCreationOptions? options, CancellationToken cancellationToken = default) { - ThreadCreationOptions options = + ThreadCreationOptions createOptions = new() { - ToolResources = GenerateToolResources(settings?.VectorStoreId, settings?.CodeInterpterFileIds), + ToolResources = GenerateToolResources(options?.VectorStoreId, options?.CodeInterpterFileIds), }; //options.InitialMessages, // %%% TODO - if (settings?.Metadata != null) + if (options?.Metadata != null) { - foreach (KeyValuePair item in settings.Metadata) + foreach (KeyValuePair item in options.Metadata) { - options.Metadata[item.Key] = item.Value; + createOptions.Metadata[item.Key] = item.Value; } } - AssistantThread thread = await this._client.CreateThreadAsync(options, cancellationToken).ConfigureAwait(false); + AssistantThread thread = await this._client.CreateThreadAsync(createOptions, cancellationToken).ConfigureAwait(false); return thread.Id; } @@ -231,23 +231,23 @@ public async Task DeleteAsync(CancellationToken cancellationToken = defaul public IAsyncEnumerable InvokeAsync( string threadId, CancellationToken cancellationToken = default) - => this.InvokeAsync(threadId, settings: null, cancellationToken); + => this.InvokeAsync(threadId, options: null, cancellationToken); /// /// Invoke the assistant on the specified thread. /// /// The thread identifier - /// Optional invocation settings + /// Optional invocation options /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. public async IAsyncEnumerable InvokeAsync( string threadId, - OpenAIAssistantInvocationOptions? settings, + OpenAIAssistantInvocationOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken = default) { this.ThrowIfDeleted(); - await foreach ((bool isVisible, ChatMessageContent message) in AssistantThreadActions.InvokeAsync(this, this._client, threadId, settings, this.Logger, cancellationToken).ConfigureAwait(false)) + await foreach ((bool isVisible, ChatMessageContent message) in AssistantThreadActions.InvokeAsync(this, this._client, threadId, options, this.Logger, cancellationToken).ConfigureAwait(false)) { if (isVisible) { @@ -309,11 +309,11 @@ private OpenAIAssistantAgent( private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant model) { - OpenAIAssistantExecutionOptions? settings = null; + OpenAIAssistantExecutionOptions? options = null; - if (model.Metadata.TryGetValue(SettingsMetadataKey, out string? settingsJson)) + if (model.Metadata.TryGetValue(OptionsMetadataKey, out string? optionsJson)) { - settings = JsonSerializer.Deserialize(settingsJson); + options = JsonSerializer.Deserialize(optionsJson); } IReadOnlyList? fileIds = (IReadOnlyList?)model.ToolResources?.CodeInterpreter?.FileIds; @@ -335,7 +335,7 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod TopP = model.NucleusSamplingFactor, Temperature = model.Temperature, VectorStoreId = vectorStoreId, - ExecutionOptions = settings, + ExecutionOptions = options, }; } @@ -363,8 +363,8 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss if (definition.ExecutionOptions != null) { - string settingsJson = JsonSerializer.Serialize(definition.ExecutionOptions); - assistantCreationOptions.Metadata[SettingsMetadataKey] = settingsJson; + string optionsJson = JsonSerializer.Serialize(definition.ExecutionOptions); + assistantCreationOptions.Metadata[OptionsMetadataKey] = optionsJson; } if (definition.EnableCodeInterpreter) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs index 49a808bcf356..8531178543e4 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs @@ -31,7 +31,7 @@ protected override async Task ReceiveAsync(IEnumerable histo { agent.ThrowIfDeleted(); - return AssistantThreadActions.InvokeAsync(agent, this._client, this._threadId, invocationSettings: null, this.Logger, cancellationToken); + return AssistantThreadActions.InvokeAsync(agent, this._client, this._threadId, invocationOptions: null, this.Logger, cancellationToken); } /// diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs index 48f327a46489..9208d8184d82 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs @@ -5,7 +5,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// Defines agent execution options for each invocation. /// /// -/// These options are persisted as a single entry of the agent's metadata with key: "__settings" +/// These options are persisted as a single entry of the agent's metadata with key: "__run_options" /// public sealed class OpenAIAssistantExecutionOptions { diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs deleted file mode 100644 index a882729cde14..000000000000 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStore.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using OpenAI; -using OpenAI.VectorStores; - -namespace Microsoft.SemanticKernel.Agents.OpenAI; - -/// -/// Supports management operations for a >. -/// -public sealed class OpenAIVectorStore -{ - private readonly VectorStoreClient _client; - - /// - /// The identifier of the targeted vector store - /// - public string VectorStoreId { get; } - - /// - /// List all vector stores. - /// - /// Configuration for accessing the vector-store service. - /// The to monitor for cancellation requests. The default is . - /// An enumeration of models. - public static IAsyncEnumerable GetVectorStoresAsync(OpenAIServiceConfiguration config, CancellationToken cancellationToken = default) - { - OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); - VectorStoreClient client = openAIClient.GetVectorStoreClient(); - - return client.GetVectorStoresAsync(ListOrder.NewestFirst, cancellationToken); - } - - /// - /// Initializes a new instance of the class. - /// - /// The identifier of the targeted vector store - /// Configuration for accessing the vector-store service. - public OpenAIVectorStore(string vectorStoreId, OpenAIServiceConfiguration config) - { - OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); - this._client = openAIClient.GetVectorStoreClient(); - - this.VectorStoreId = vectorStoreId; - } - - /// - /// Add a file from the vector store. - /// - /// The file to add, by identifier. - /// The to monitor for cancellation requests. The default is . - /// - public async Task AddFileAsync(string fileId, CancellationToken cancellationToken = default) => - await this._client.AddFileToVectorStoreAsync(this.VectorStoreId, fileId, cancellationToken).ConfigureAwait(false); - - /// - /// Deletes the entire vector store. - /// - /// The to monitor for cancellation requests. The default is . - /// - public async Task DeleteAsync(CancellationToken cancellationToken = default) => - await this._client.DeleteVectorStoreAsync(this.VectorStoreId, cancellationToken).ConfigureAwait(false); - - /// - /// List the files (by identifier) in the vector store. - /// - /// The to monitor for cancellation requests. The default is . - /// - public async IAsyncEnumerable GetFilesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await foreach (VectorStoreFileAssociation file in this._client.GetFileAssociationsAsync(this.VectorStoreId, ListOrder.NewestFirst, filter: null, cancellationToken).ConfigureAwait(false)) - { - yield return file.FileId; - } - } - - /// - /// Remove a file from the vector store. - /// - /// The file to remove, by identifier. - /// The to monitor for cancellation requests. The default is . - /// - public async Task RemoveFileAsync(string fileId, CancellationToken cancellationToken = default) => - await this._client.RemoveFileFromStoreAsync(this.VectorStoreId, fileId, cancellationToken).ConfigureAwait(false); -} diff --git a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs b/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs deleted file mode 100644 index 65e9bf5c7496..000000000000 --- a/dotnet/src/Agents/OpenAI/OpenAIVectorStoreBuilder.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using OpenAI; -using OpenAI.VectorStores; - -namespace Microsoft.SemanticKernel.Agents.OpenAI; - -/// -/// Fluent builder for creating a new . -/// -/// Configuration for accessing the vector-store service. -public sealed class OpenAIVectorStoreBuilder(OpenAIServiceConfiguration config) -{ - private string? _name; - private FileChunkingStrategy? _chunkingStrategy; - private VectorStoreExpirationPolicy? _expirationPolicy; - private readonly List _fileIds = []; - private readonly Dictionary _metadata = []; - - /// - /// Added a file (by identifier) to the vector store. - /// - /// - public OpenAIVectorStoreBuilder AddFile(string fileId) - { - this._fileIds.Add(fileId); - - return this; - } - - /// - /// Added files (by identifier) to the vector store. - /// - /// - public OpenAIVectorStoreBuilder AddFiles(string[] fileIds) - { - this._fileIds.AddRange(fileIds); - - return this; - } - - /// - /// Define the vector store chunking strategy (if not default). - /// - /// The maximum number of tokens in each chunk. - /// The number of tokens that overlap between chunks. - public OpenAIVectorStoreBuilder WithChunkingStrategy(int maxTokensPerChunk, int overlappingTokenCount) - { - this._chunkingStrategy = FileChunkingStrategy.CreateStaticStrategy(maxTokensPerChunk, overlappingTokenCount); - - return this; - } - - /// - /// The number of days of from the last use until vector store will expire. - /// - /// The duration (in days) from the last usage. - public OpenAIVectorStoreBuilder WithExpiration(TimeSpan duration) - { - this._expirationPolicy = new VectorStoreExpirationPolicy(VectorStoreExpirationAnchor.LastActiveAt, duration.Days); - - return this; - } - - /// - /// Adds a single key/value pair to the metadata. - /// - /// The metadata key - /// The metadata value - /// - /// The metadata is a set of up to 16 key/value pairs that can be attached to an agent, used for - /// storing additional information about that object in a structured format.Keys - /// may be up to 64 characters in length and values may be up to 512 characters in length. - /// > - public OpenAIVectorStoreBuilder WithMetadata(string key, string value) - { - this._metadata[key] = value; - - return this; - } - - /// - /// A set of up to 16 key/value pairs that can be attached to an agent, used for - /// storing additional information about that object in a structured format.Keys - /// may be up to 64 characters in length and values may be up to 512 characters in length. - /// - /// The metadata - public OpenAIVectorStoreBuilder WithMetadata(IDictionary metadata) - { - foreach (KeyValuePair item in this._metadata) - { - this._metadata[item.Key] = item.Value; - } - - return this; - } - - /// - /// Defines the name of the vector store when not anonymous. - /// - /// The store name. - public OpenAIVectorStoreBuilder WithName(string name) - { - this._name = name; - - return this; - } - - /// - /// Creates a as defined. - /// - /// The to monitor for cancellation requests. The default is . - public async Task CreateAsync(CancellationToken cancellationToken = default) - { - OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); - VectorStoreClient client = openAIClient.GetVectorStoreClient(); - - VectorStoreCreationOptions options = - new() - { - FileIds = this._fileIds, - ChunkingStrategy = this._chunkingStrategy, - ExpirationPolicy = this._expirationPolicy, - Name = this._name, - }; - - if (this._metadata != null) - { - foreach (KeyValuePair item in this._metadata) - { - options.Metadata.Add(item.Key, item.Value); - } - } - - VectorStore store = await client.CreateVectorStoreAsync(options, cancellationToken).ConfigureAwait(false); - - return store; - } -} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs new file mode 100644 index 000000000000..69767ee42410 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SemanticKernel.Agents.UnitTests.OpenAI.Extensions; + +/// +/// %%% +/// +public class OpenAIServiceConfigurationExtensionsTests +{ +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs new file mode 100644 index 000000000000..991cbcb2d86f --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SemanticKernel.Agents.UnitTests.OpenAI.Internal; + +/// +/// %%% +/// +public class AssistantRunOptionsFactoryTests +{ +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs index 8a8669e30f07..1d63a6e2e9c0 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs @@ -14,30 +14,30 @@ public class OpenAIAssistantInvocationOptionsTests /// Verify initial state. /// [Fact] - public void OpenAIAssistantInvocationSettingsInitialState() + public void OpenAIAssistantInvocationOptionsInitialState() { - OpenAIAssistantInvocationOptions settings = new(); + OpenAIAssistantInvocationOptions options = new(); - Assert.Null(settings.ModelName); - Assert.Null(settings.Metadata); - Assert.Null(settings.Temperature); - Assert.Null(settings.TopP); - Assert.Null(settings.ParallelToolCallsEnabled); - Assert.Null(settings.MaxCompletionTokens); - Assert.Null(settings.MaxPromptTokens); - Assert.Null(settings.TruncationMessageCount); - Assert.Null(settings.EnableJsonResponse); - Assert.False(settings.EnableCodeInterpreter); - Assert.False(settings.EnableFileSearch); + Assert.Null(options.ModelName); + Assert.Null(options.Metadata); + Assert.Null(options.Temperature); + Assert.Null(options.TopP); + Assert.Null(options.ParallelToolCallsEnabled); + Assert.Null(options.MaxCompletionTokens); + Assert.Null(options.MaxPromptTokens); + Assert.Null(options.TruncationMessageCount); + Assert.Null(options.EnableJsonResponse); + Assert.False(options.EnableCodeInterpreter); + Assert.False(options.EnableFileSearch); } /// /// Verify initialization. /// [Fact] - public void OpenAIAssistantInvocationSettingsAssignment() + public void OpenAIAssistantInvocationOptionsAssignment() { - OpenAIAssistantInvocationOptions settings = + OpenAIAssistantInvocationOptions options = new() { ModelName = "testmodel", @@ -53,16 +53,16 @@ public void OpenAIAssistantInvocationSettingsAssignment() EnableFileSearch = true, }; - Assert.Equal("testmodel", settings.ModelName); - Assert.Equal(2, settings.Temperature); - Assert.Equal(0, settings.TopP); - Assert.Equal(1000, settings.MaxCompletionTokens); - Assert.Equal(1000, settings.MaxPromptTokens); - Assert.Equal(12, settings.TruncationMessageCount); - Assert.False(settings.ParallelToolCallsEnabled); - Assert.Single(settings.Metadata); - Assert.True(settings.EnableCodeInterpreter); - Assert.True(settings.EnableJsonResponse); - Assert.True(settings.EnableFileSearch); + Assert.Equal("testmodel", options.ModelName); + Assert.Equal(2, options.Temperature); + Assert.Equal(0, options.TopP); + Assert.Equal(1000, options.MaxCompletionTokens); + Assert.Equal(1000, options.MaxPromptTokens); + Assert.Equal(12, options.TruncationMessageCount); + Assert.False(options.ParallelToolCallsEnabled); + Assert.Single(options.Metadata); + Assert.True(options.EnableCodeInterpreter); + Assert.True(options.EnableJsonResponse); + Assert.True(options.EnableFileSearch); } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs index 545a338f5217..a90050623599 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs @@ -16,22 +16,22 @@ public class OpenAIThreadCreationOptionsTests /// Verify initial state. /// [Fact] - public void OpenAIThreadCreationSettingsInitialState() + public void OpenAIThreadCreationOptionsInitialState() { - OpenAIThreadCreationOptions settings = new(); + OpenAIThreadCreationOptions options = new(); - Assert.Null(settings.Messages); - Assert.Null(settings.Metadata); - Assert.Null(settings.VectorStoreId); - Assert.Null(settings.CodeInterpterFileIds); - Assert.False(settings.EnableCodeInterpreter); + Assert.Null(options.Messages); + Assert.Null(options.Metadata); + Assert.Null(options.VectorStoreId); + Assert.Null(options.CodeInterpterFileIds); + Assert.False(options.EnableCodeInterpreter); } /// /// Verify initialization. /// [Fact] - public void OpenAIThreadCreationSettingsAssignment() + public void OpenAIThreadCreationOptionsAssignment() { OpenAIThreadCreationOptions definition = new() diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreBuilderTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreBuilderTests.cs deleted file mode 100644 index dd6734e8cb71..000000000000 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreBuilderTests.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Agents.OpenAI; -using OpenAI.VectorStores; -using Xunit; - -namespace SemanticKernel.Agents.UnitTests.OpenAI; - -/// -/// Unit testing of . -/// -public sealed class OpenAIVectorStoreBuilderTests : IDisposable -{ - private readonly HttpMessageHandlerStub _messageHandlerStub; - private readonly HttpClient _httpClient; - - /// - /// %%% - /// - [Fact] - public async Task VerifyOpenAIVectorStoreBuilderEmptyAsync() - { - this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateStore); - - VectorStore store = - await new OpenAIVectorStoreBuilder(this.CreateTestConfiguration()) - .CreateAsync(); - - Assert.NotNull(store); - } - - /// - /// %%% - /// - [Fact] - public async Task VerifyOpenAIVectorStoreBuilderFluentAsync() - { - this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateStore); - - Dictionary metadata = new() { { "key2", "value2" } }; - - VectorStore store = - await new OpenAIVectorStoreBuilder(this.CreateTestConfiguration()) - .WithName("my_vector_store") - .AddFile("#file_1") - .AddFiles(["#file_2", "#file_3"]) - .AddFiles(["#file_4", "#file_5"]) - .WithChunkingStrategy(1000, 400) - .WithExpiration(TimeSpan.FromDays(30)) - .WithMetadata("key1", "value1") - .WithMetadata(metadata) - .CreateAsync(); - - Assert.NotNull(store); - } - - /// - public void Dispose() - { - this._messageHandlerStub.Dispose(); - this._httpClient.Dispose(); - } - - /// - /// Initializes a new instance of the class. - /// - public OpenAIVectorStoreBuilderTests() - { - this._messageHandlerStub = new HttpMessageHandlerStub(); - this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false); - } - - private OpenAIServiceConfiguration CreateTestConfiguration(bool targetAzure = false) - => targetAzure ? - OpenAIServiceConfiguration.ForAzureOpenAI(apiKey: "fakekey", endpoint: new Uri("https://localhost"), this._httpClient) : - OpenAIServiceConfiguration.ForOpenAI(apiKey: "fakekey", endpoint: null, this._httpClient); - - private void SetupResponse(HttpStatusCode statusCode, string content) - { - this._messageHandlerStub.ResponseToReturn = - new(statusCode) - { - Content = new StringContent(content) - }; - } - - private void SetupResponses(HttpStatusCode statusCode, params string[] content) - { - foreach (var item in content) - { -#pragma warning disable CA2000 // Dispose objects before losing scope - this._messageHandlerStub.ResponseQueue.Enqueue( - new(statusCode) - { - Content = new StringContent(item) - }); -#pragma warning restore CA2000 // Dispose objects before losing scope - } - } - - private static class ResponseContent - { - public const string CreateStore = - """ - { - "id": "vs_123", - "object": "vector_store", - "created_at": 1698107661, - "usage_bytes": 123456, - "last_active_at": 1698107661, - "name": "my_vector_store", - "status": "completed", - "file_counts": { - "in_progress": 0, - "completed": 5, - "cancelled": 0, - "failed": 0, - "total": 5 - }, - "metadata": {}, - "last_used_at": 1698107661 - } - """; - } -} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs deleted file mode 100644 index dbcdef23f8b7..000000000000 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIVectorStoreTests.cs +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Agents.OpenAI; -using OpenAI.VectorStores; -using Xunit; - -namespace SemanticKernel.Agents.UnitTests.OpenAI; - -/// -/// Unit testing of . -/// -public sealed class OpenAIVectorStoreTests : IDisposable -{ - private readonly HttpMessageHandlerStub _messageHandlerStub; - private readonly HttpClient _httpClient; - - /// - /// %%% - /// - [Fact] - public void VerifyOpenAIVectorStoreInitialization() - { - OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); - Assert.Equal("#vs1", store.VectorStoreId); - } - - /// - /// %%% - /// - [Fact] - public async Task VerifyOpenAIVectorStoreDeleteAsync() - { - this.SetupResponse(HttpStatusCode.OK, ResponseContent.DeleteStore); - - OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); - bool isDeleted = await store.DeleteAsync(); - - Assert.True(isDeleted); - } - - /// - /// %%% - /// - [Fact] - public async Task VerifyOpenAIVectorStoreListAsync() - { - this.SetupResponse(HttpStatusCode.OK, ResponseContent.ListStores); - - VectorStore[] stores = await OpenAIVectorStore.GetVectorStoresAsync(this.CreateTestConfiguration()).ToArrayAsync(); - - Assert.Equal(2, stores.Length); - } - - /// - /// %%% - /// - [Fact] - public async Task VerifyOpenAIVectorStoreAddFileAsync() - { - this.SetupResponse(HttpStatusCode.OK, ResponseContent.AddFile); - - OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); - await store.AddFileAsync("#file_1"); - - // %%% VERIFY - } - - /// - /// %%% - /// - [Fact] - public async Task VerifyOpenAIVectorStoreRemoveFileAsync() - { - this.SetupResponse(HttpStatusCode.OK, ResponseContent.DeleteFile); - - OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); - bool isDeleted = await store.RemoveFileAsync("#file_1"); - - Assert.True(isDeleted); - } - - /// - /// %%% - /// - [Fact] - public async Task VerifyOpenAIVectorStoreGetFilesAsync() - { - this.SetupResponse(HttpStatusCode.OK, ResponseContent.ListFiles); - - OpenAIVectorStore store = new("#vs1", this.CreateTestConfiguration()); - string[] files = await store.GetFilesAsync().ToArrayAsync(); - - Assert.Equal(2, files.Length); - } - - /// - public void Dispose() - { - this._messageHandlerStub.Dispose(); - this._httpClient.Dispose(); - } - - /// - /// Initializes a new instance of the class. - /// - public OpenAIVectorStoreTests() - { - this._messageHandlerStub = new HttpMessageHandlerStub(); - this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false); - } - - private OpenAIServiceConfiguration CreateTestConfiguration(bool targetAzure = false) - => targetAzure ? - OpenAIServiceConfiguration.ForAzureOpenAI(apiKey: "fakekey", endpoint: new Uri("https://localhost"), this._httpClient) : - OpenAIServiceConfiguration.ForOpenAI(apiKey: "fakekey", endpoint: null, this._httpClient); - - private void SetupResponse(HttpStatusCode statusCode, string content) - { - this._messageHandlerStub.ResponseToReturn = - new(statusCode) - { - Content = new StringContent(content) - }; - } - - private void SetupResponses(HttpStatusCode statusCode, params string[] content) - { - foreach (var item in content) - { -#pragma warning disable CA2000 // Dispose objects before losing scope - this._messageHandlerStub.ResponseQueue.Enqueue( - new(statusCode) - { - Content = new StringContent(item) - }); -#pragma warning restore CA2000 // Dispose objects before losing scope - } - } - - private static class ResponseContent - { - public const string AddFile = - """ - { - "id": "#file_1", - "object": "vector_store.file", - "created_at": 1699061776, - "usage_bytes": 1234, - "vector_store_id": "vs_abcd", - "status": "completed", - "last_error": null - } - """; - - public const string DeleteFile = - """ - { - "id": "#file_1", - "object": "vector_store.file.deleted", - "deleted": true - } - """; - - public const string DeleteStore = - """ - { - "id": "vs_abc123", - "object": "vector_store.deleted", - "deleted": true - } - """; - - public const string ListFiles = - """ - { - "object": "list", - "data": [ - { - "id": "file-abc123", - "object": "vector_store.file", - "created_at": 1699061776, - "vector_store_id": "vs_abc123" - }, - { - "id": "file-abc456", - "object": "vector_store.file", - "created_at": 1699061776, - "vector_store_id": "vs_abc123" - } - ], - "first_id": "file-abc123", - "last_id": "file-abc456", - "has_more": false - } - """; - - public const string ListStores = - """ - { - "object": "list", - "data": [ - { - "id": "vs_abc123", - "object": "vector_store", - "created_at": 1699061776, - "name": "Support FAQ", - "bytes": 139920, - "file_counts": { - "in_progress": 0, - "completed": 3, - "failed": 0, - "cancelled": 0, - "total": 3 - } - }, - { - "id": "vs_abc456", - "object": "vector_store", - "created_at": 1699061776, - "name": "Support FAQ v2", - "bytes": 139920, - "file_counts": { - "in_progress": 0, - "completed": 3, - "failed": 0, - "cancelled": 0, - "total": 3 - } - } - ], - "first_id": "vs_abc123", - "last_id": "vs_abc456", - "has_more": false - } - """; - } -} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs new file mode 100644 index 000000000000..e87688791df3 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI; + +/// +/// Unit testing of . +/// +public class RunPollingOptionsTests +{ + /// + /// Verify initial state. + /// + [Fact] + public void RunPollingOptionsInitialState() + { + OpenAIThreadCreationOptions options = new(); + + Assert.Null(options.Messages); + Assert.Null(options.Metadata); + Assert.Null(options.VectorStoreId); + Assert.Null(options.CodeInterpterFileIds); + Assert.False(options.EnableCodeInterpreter); + } + + /// s + /// Verify initialization. + /// + [Fact] + public void RunPollingOptionsAssignment() + { + OpenAIThreadCreationOptions definition = + new() + { + Messages = [new ChatMessageContent(AuthorRole.User, "test")], + VectorStoreId = "#vs", + Metadata = new Dictionary() { { "a", "1" } }, + CodeInterpterFileIds = ["file1"], + EnableCodeInterpreter = true, + }; + + Assert.Single(definition.Messages); + Assert.Single(definition.Metadata); + Assert.Equal("#vs", definition.VectorStoreId); + Assert.Single(definition.CodeInterpterFileIds); + Assert.True(definition.EnableCodeInterpreter); + } +} From e09013b715bcff443bff8798ec6cbbc40b52639c Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 11:51:51 -0700 Subject: [PATCH 050/121] Namespace --- .../samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs | 2 -- dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index e325a672ac5e..ee6c887c14db 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -1,12 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. using System.Text; -using Azure.AI.OpenAI; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; using Microsoft.SemanticKernel.ChatCompletion; -using OpenAI; using OpenAI.Files; using Resources; diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs index 091ce3f52a3f..9189214701c6 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. -using Azure.AI.OpenAI; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; using Microsoft.SemanticKernel.ChatCompletion; -using OpenAI; using OpenAI.Files; using OpenAI.VectorStores; using Resources; From a48aa120c198ecdee74bf9669dbed78aeb95634f Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 16:18:54 -0700 Subject: [PATCH 051/121] Checkpoint --- .../OpenAIServiceConfigurationExtensions.cs | 12 +- .../Extensions/RunPollingOptionsExtensions.cs | 24 -- .../Internal/AssistantMessageAdapter.cs | 56 +++++ .../Internal/AssistantRunOptionsFactory.cs | 47 +++- .../OpenAI/Internal/AssistantThreadActions.cs | 76 +------ .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 4 +- .../OpenAI/OpenAIThreadCreationOptions.cs | 5 - dotnet/src/Agents/OpenAI/RunPollingOptions.cs | 17 ++ .../Agents/UnitTests/Agents.UnitTests.csproj | 2 +- ...enAIServiceConfigurationExtensionsTests.cs | 36 ++- .../Internal/AssistantMessageAdapterTests.cs | 209 ++++++++++++++++++ .../OpenAIClientFactoryTests.cs | 2 +- .../OpenAIThreadCreationOptionsTests.cs | 3 - .../OpenAI/RunPollingOptionsTests.cs | 58 +++-- .../Contents/AnnotationContent.cs | 2 +- .../Contents/FileReferenceContent.cs | 2 +- .../Contents/MessageAttachmentContent.cs | 42 ++++ 17 files changed, 449 insertions(+), 148 deletions(-) delete mode 100644 dotnet/src/Agents/OpenAI/Extensions/RunPollingOptionsExtensions.cs create mode 100644 dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs rename dotnet/src/Agents/UnitTests/OpenAI/{ => Internal}/OpenAIClientFactoryTests.cs (97%) create mode 100644 dotnet/src/SemanticKernel.Abstractions/Contents/MessageAttachmentContent.cs diff --git a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs index 79a60452d2fd..76000128ce3a 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs @@ -6,15 +6,14 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI.Extensions; /// -/// %%% +/// Extension method for creating OpenAI clients from a . /// public static class OpenAIServiceConfigurationExtensions { /// - /// %%% + /// Provide a newly created based on the specified configuration. /// - /// - /// + /// The configuration public static FileClient CreateFileClient(this OpenAIServiceConfiguration configuration) { OpenAIClient client = OpenAIClientFactory.CreateClient(configuration); @@ -23,10 +22,9 @@ public static FileClient CreateFileClient(this OpenAIServiceConfiguration config } /// - /// %%% + /// Provide a newly created based on the specified configuration. /// - /// - /// + /// The configuration public static VectorStoreClient CreateVectorStoreClient(this OpenAIServiceConfiguration configuration) { OpenAIClient client = OpenAIClientFactory.CreateClient(configuration); diff --git a/dotnet/src/Agents/OpenAI/Extensions/RunPollingOptionsExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/RunPollingOptionsExtensions.cs deleted file mode 100644 index 0bbe3d17ac97..000000000000 --- a/dotnet/src/Agents/OpenAI/Extensions/RunPollingOptionsExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.SemanticKernel.Agents.OpenAI.Extensions; - -internal static class RunPollingOptionsExtensions -{ - /// - /// %%% - /// - /// - /// - /// - /// - public async static Task WaitAsync(this RunPollingOptions pollingOptions, int pollIterationCount, CancellationToken cancellationToken) - { - await Task.Delay( - pollIterationCount >= 2 ? - pollingOptions.RunPollingInterval : - pollingOptions.RunPollingBackoff, - cancellationToken).ConfigureAwait(false); - } -} diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs new file mode 100644 index 000000000000..99aeb53f1ebe --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; +using OpenAI.Assistants; + +namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; + +internal static class AssistantMessageAdapter +{ + public static MessageCreationOptions CreateOptions(ChatMessageContent message) + { + MessageCreationOptions options = new(); + + if (message.Metadata != null) + { + foreach (var metadata in message.Metadata) + { + options.Metadata.Add(metadata.Key, metadata.Value?.ToString() ?? string.Empty); + } + } + + return options; + } + + public static IEnumerable GetMessageContents(ChatMessageContent message, MessageCreationOptions options) + { + foreach (KernelContent content in message.Items) + { + if (content is TextContent textContent) + { + yield return MessageContent.FromText(content.ToString()); + } + else if (content is ImageContent imageContent) + { + if (imageContent.Uri != null) + { + yield return MessageContent.FromImageUrl(imageContent.Uri); + } + //else if (string.IsNullOrWhiteSpace(imageContent.DataUri)) + //{ + // %%% BUG: https://github.com/openai/openai-dotnet/issues/135 + // URI does not accept the format used for `DataUri` + // Approach is inefficient anyway... + // yield return MessageContent.FromImageUrl(new Uri(imageContent.DataUri!)); + //} + } + else if (content is MessageAttachmentContent attachmentContent) + { + options.Attachments.Add(new MessageCreationAttachment(attachmentContent.FileId, [ToolDefinition.CreateCodeInterpreter()])); // %%% TODO: Tool Type + } + else if (content is FileReferenceContent fileContent) + { + yield return MessageContent.FromImageFileId(fileContent.FileId); + } + } + } +} diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs index d28574bf2bcb..40b9ef329691 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs @@ -1,12 +1,49 @@ // Copyright (c) Microsoft. All rights reserved. -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; -internal class AssistantRunOptionsFactory +internal static class AssistantRunOptionsFactory { + /// + /// %%% + /// + /// + /// + /// + public static RunCreationOptions GenerateOptions(OpenAIAssistantDefinition definition, OpenAIAssistantInvocationOptions? invocationOptions) + { + int? truncationMessageCount = ResolveExecutionSetting(invocationOptions?.TruncationMessageCount, definition.ExecutionOptions?.TruncationMessageCount); + + RunCreationOptions options = + new() + { + MaxCompletionTokens = ResolveExecutionSetting(invocationOptions?.MaxCompletionTokens, definition.ExecutionOptions?.MaxCompletionTokens), + MaxPromptTokens = ResolveExecutionSetting(invocationOptions?.MaxPromptTokens, definition.ExecutionOptions?.MaxPromptTokens), + ModelOverride = invocationOptions?.ModelName, + NucleusSamplingFactor = ResolveExecutionSetting(invocationOptions?.TopP, definition.TopP), + ParallelToolCallsEnabled = ResolveExecutionSetting(invocationOptions?.ParallelToolCallsEnabled, definition.ExecutionOptions?.ParallelToolCallsEnabled), + ResponseFormat = ResolveExecutionSetting(invocationOptions?.EnableJsonResponse, definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, + Temperature = ResolveExecutionSetting(invocationOptions?.Temperature, definition.Temperature), + //ToolConstraint // %%% TODO ISSUE + TruncationStrategy = truncationMessageCount.HasValue ? RunTruncationStrategy.CreateLastMessagesStrategy(truncationMessageCount.Value) : null, + }; + + if (invocationOptions?.Metadata != null) + { + foreach (var metadata in invocationOptions.Metadata) + { + options.Metadata.Add(metadata.Key, metadata.Value ?? string.Empty); + } + } + + return options; + } + + private static TValue? ResolveExecutionSetting(TValue? setting, TValue? agentSetting) where TValue : struct + => + setting.HasValue && (!agentSetting.HasValue || !EqualityComparer.Default.Equals(setting.Value, agentSetting.Value)) ? + setting.Value : + null; } diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index c5ebd60e6236..b3ceaee7ac77 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -10,6 +10,7 @@ using Azure; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI; using OpenAI.Assistants; @@ -51,45 +52,13 @@ public static async Task CreateMessageAsync(AssistantClient client, string threa return; } - MessageCreationOptions options = - new() - { - //Role = message.Role.ToMessageRole(), // %%% BUG: ASSIGNABLE (Allow assistant or user) - }; - - if (message.Metadata != null) - { - foreach (var metadata in message.Metadata) - { - options.Metadata.Add(metadata.Key, metadata.Value?.ToString() ?? string.Empty); - } - } + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); await client.CreateMessageAsync( threadId, - GetMessageContents(), + AssistantMessageAdapter.GetMessageContents(message, options), options, cancellationToken).ConfigureAwait(false); - - IEnumerable GetMessageContents() - { - foreach (KernelContent content in message.Items) - { - if (content is TextContent textContent) - { - yield return MessageContent.FromText(content.ToString()); - } - else if (content is ImageContent imageContent && imageContent.Data.HasValue) - { - yield return MessageContent.FromImageUrl( - imageContent.Uri ?? new Uri(Convert.ToBase64String(imageContent.Data.Value.ToArray()))); - } - else if (content is FileReferenceContent fileContent) - { - options.Attachments.Add(new MessageCreationAttachment(fileContent.FileId, [new CodeInterpreterToolDefinition()])); - } - } - } } /// @@ -157,7 +126,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist logger.LogOpenAIAssistantCreatingRun(nameof(InvokeAsync), threadId); - RunCreationOptions options = GenerateRunCreationOptions(agent.Definition, invocationOptions); + RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(agent.Definition, invocationOptions); options.ToolsOverride.AddRange(agent.Tools); // %%% @@ -290,7 +259,7 @@ async Task PollRunStatusAsync() do { // Reduce polling frequency after a couple attempts - await agent.PollingOptions.WaitAsync(count, cancellationToken).ConfigureAwait(false); + await Task.Delay(agent.PollingOptions.GetPollingInterval(count), cancellationToken).ConfigureAwait(false); ++count; #pragma warning disable CA1031 // Do not catch general exception types @@ -492,39 +461,4 @@ private static ToolOutput[] GenerateToolOutputs(FunctionResultContent[] function return toolOutputs; } - - private static RunCreationOptions GenerateRunCreationOptions(OpenAIAssistantDefinition definition, OpenAIAssistantInvocationOptions? invocationOptions) - { - int? truncationMessageCount = ResolveExecutionSetting(invocationOptions?.TruncationMessageCount, definition.ExecutionOptions?.TruncationMessageCount); - - RunCreationOptions options = - new() - { - MaxCompletionTokens = ResolveExecutionSetting(invocationOptions?.MaxCompletionTokens, definition.ExecutionOptions?.MaxCompletionTokens), - MaxPromptTokens = ResolveExecutionSetting(invocationOptions?.MaxPromptTokens, definition.ExecutionOptions?.MaxPromptTokens), - ModelOverride = invocationOptions?.ModelName, - NucleusSamplingFactor = ResolveExecutionSetting(invocationOptions?.TopP, definition.TopP), - ParallelToolCallsEnabled = ResolveExecutionSetting(invocationOptions?.ParallelToolCallsEnabled, definition.ExecutionOptions?.ParallelToolCallsEnabled), - ResponseFormat = ResolveExecutionSetting(invocationOptions?.EnableJsonResponse, definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, - Temperature = ResolveExecutionSetting(invocationOptions?.Temperature, definition.Temperature), - //ToolConstraint // %%% TODO - TruncationStrategy = truncationMessageCount.HasValue ? RunTruncationStrategy.CreateLastMessagesStrategy(truncationMessageCount.Value) : null, - }; - - if (invocationOptions?.Metadata != null) - { - foreach (var metadata in invocationOptions.Metadata) - { - options.Metadata.Add(metadata.Key, metadata.Value ?? string.Empty); - } - } - - return options; - } - - private static TValue? ResolveExecutionSetting(TValue? setting, TValue? agentSetting) where TValue : struct - => - setting.HasValue && (!agentSetting.HasValue || !EqualityComparer.Default.Equals(setting.Value, agentSetting.Value)) ? - setting.Value : - null; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 59df7c9d439d..294330d30da5 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -369,12 +369,12 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss if (definition.EnableCodeInterpreter) { - assistantCreationOptions.Tools.Add(new CodeInterpreterToolDefinition()); + assistantCreationOptions.Tools.Add(ToolDefinition.CreateCodeInterpreter()); } if (!string.IsNullOrWhiteSpace(definition.VectorStoreId)) { - assistantCreationOptions.Tools.Add(new FileSearchToolDefinition()); + assistantCreationOptions.Tools.Add(ToolDefinition.CreateFileSearch()); } return assistantCreationOptions; diff --git a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs index 56b3aa167284..4f596e000153 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs @@ -13,11 +13,6 @@ public sealed class OpenAIThreadCreationOptions /// public IReadOnlyList? CodeInterpterFileIds { get; init; } - /// - /// Set if code-interpreter is enabled. - /// - public bool EnableCodeInterpreter { get; init; } - /// /// Optional messages to initialize thread with.. /// diff --git a/dotnet/src/Agents/OpenAI/RunPollingOptions.cs b/dotnet/src/Agents/OpenAI/RunPollingOptions.cs index 7dd71cfdd8b2..756ba689131c 100644 --- a/dotnet/src/Agents/OpenAI/RunPollingOptions.cs +++ b/dotnet/src/Agents/OpenAI/RunPollingOptions.cs @@ -18,6 +18,11 @@ public sealed class RunPollingOptions /// public static TimeSpan DefaultPollingBackoff { get; } = TimeSpan.FromSeconds(1); + /// + /// The default number of polling iterations before using . + /// + public static int DefaultPollingBackoffThreshold { get; } = 2; + /// /// The default polling delay when retrying message retrieval due to a 404/NotFound from synchronization lag. /// @@ -33,8 +38,20 @@ public sealed class RunPollingOptions /// public TimeSpan RunPollingBackoff { get; set; } = DefaultPollingBackoff; + /// + /// The number of polling iterations before using . + /// + public int RunPollingBackoffThreshold { get; set; } = DefaultPollingBackoffThreshold; + /// /// The polling delay when retrying message retrieval due to a 404/NotFound from synchronization lag. /// public TimeSpan MessageSynchronizationDelay { get; set; } = DefaultMessageSynchronizationDelay; + + /// + /// Gets the polling interval for the specified iteration count. + /// + /// The number of polling iterations already attempted + public TimeSpan GetPollingInterval(int iterationCount) => + iterationCount > this.RunPollingBackoffThreshold ? this.RunPollingBackoff : this.RunPollingInterval; } diff --git a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj index d46a4ee0cd1e..1bff8161a9b5 100644 --- a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj +++ b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj @@ -8,7 +8,7 @@ true false 12 - $(NoWarn);CA2007,CA1812,CA1861,CA1063,VSTHRD111,SKEXP0001,SKEXP0050,SKEXP0110 + $(NoWarn);CA2007,CA1812,CA1861,CA1063,VSTHRD111,SKEXP0001,SKEXP0050,SKEXP0110;OPENAI001 diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs index 69767ee42410..288c0b2304f2 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs @@ -1,15 +1,41 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; +using OpenAI.Files; +using OpenAI.VectorStores; +using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI.Extensions; /// -/// %%% +/// Unit testing of . /// public class OpenAIServiceConfigurationExtensionsTests { + /// + /// Verify can produce a + /// + [Fact] + public void OpenAIServiceConfigurationExtensionsCreateFileClientTest() + { + OpenAIServiceConfiguration configOpenAI = OpenAIServiceConfiguration.ForOpenAI("key", new Uri("https://localhost")); + Assert.IsType(configOpenAI.CreateFileClient()); + + OpenAIServiceConfiguration configAzure = OpenAIServiceConfiguration.ForAzureOpenAI("key", new Uri("https://localhost")); + Assert.IsType(configOpenAI.CreateFileClient()); + } + + /// + /// Verify can produce a + /// + [Fact] + public void OpenAIServiceConfigurationExtensionsCreateVectorStoreTest() + { + OpenAIServiceConfiguration configOpenAI = OpenAIServiceConfiguration.ForOpenAI("key", new Uri("https://localhost")); + Assert.IsType(configOpenAI.CreateVectorStoreClient()); + + OpenAIServiceConfiguration configAzure = OpenAIServiceConfiguration.ForAzureOpenAI("key", new Uri("https://localhost")); + Assert.IsType(configOpenAI.CreateVectorStoreClient()); + } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs new file mode 100644 index 000000000000..b8f93db1107b --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; +using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI.Assistants; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI.Internal; + +/// +/// Unit testing of . +/// +public class AssistantMessageAdapterTests +{ + /// + /// Verify options creation. + /// + [Fact] + public void VerifyAssistantMessageAdapterCreateOptionsDefault() + { + // Setup message with null metadata + ChatMessageContent message = new(AuthorRole.User, "test"); + + // Create options + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + + // Validate + Assert.NotNull(options); + Assert.Empty(options.Metadata); + } + + /// + /// Verify options creation. + /// + [Fact] + public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataEmpty() + { + // Setup message with empty metadata + ChatMessageContent message = + new(AuthorRole.User, "test") + { + Metadata = new Dictionary() + }; + + // Create options + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + + // Validate + Assert.NotNull(options); + Assert.Empty(options.Metadata); + } + + /// + /// Verify options creation. + /// + [Fact] + public void VerifyAssistantMessageAdapterCreateOptionsWithMetadata() + { + // Setup message with metadata + ChatMessageContent message = + new(AuthorRole.User, "test") + { + Metadata = + new Dictionary() + { + { "a", 1 }, + { "b", "2" }, + } + }; + + // Create options + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + + // Validate + Assert.NotNull(options); + Assert.NotEmpty(options.Metadata); + Assert.Equal(2, options.Metadata.Count); + Assert.Equal("1", options.Metadata["a"]); + Assert.Equal("2", options.Metadata["b"]); + } + + /// + /// Verify options creation. + /// + [Fact] + public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataNull() + { + // Setup message with null metadata value + ChatMessageContent message = + new(AuthorRole.User, "test") + { + Metadata = + new Dictionary() + { + { "a", null }, + { "b", "2" }, + } + }; + + // Create options + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + + // Validate + Assert.NotNull(options); + Assert.NotEmpty(options.Metadata); + Assert.Equal(2, options.Metadata.Count); + Assert.Equal(string.Empty, options.Metadata["a"]); + Assert.Equal("2", options.Metadata["b"]); + } + + /// + /// Verify options creation. + /// + [Fact] + public void VerifyAssistantMessageAdapterGetMessageContentsWithText() + { + ChatMessageContent message = new(AuthorRole.User, items: [new TextContent("test")]); + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + Assert.NotNull(contents); + Assert.Single(contents); + Assert.NotNull(contents.Single().Text); + } + + /// + /// Verify options creation. + /// + [Fact] + public void VerifyAssistantMessageAdapterGetMessageWithImageUrl() + { + ChatMessageContent message = new(AuthorRole.User, items: [new ImageContent(new Uri("https://localhost/myimage.png"))]); + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + Assert.NotNull(contents); + Assert.Single(contents); + Assert.NotNull(contents.Single().ImageUrl); + } + + /// + /// Verify options creation. + /// + [Fact(Skip = "API bug with data Uri construction")] + public void VerifyAssistantMessageAdapterGetMessageWithImageData() + { + ChatMessageContent message = new(AuthorRole.User, items: [new ImageContent(new byte[] { 1, 2, 3 }, "image/png")]); + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + Assert.NotNull(contents); + Assert.Single(contents); + Assert.NotNull(contents.Single().ImageUrl); + } + + /// + /// Verify options creation. + /// + [Fact] + public void VerifyAssistantMessageAdapterGetMessageWithImageFile() + { + ChatMessageContent message = new(AuthorRole.User, items: [new FileReferenceContent("file-id")]); + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + Assert.NotNull(contents); + Assert.Single(contents); + Assert.NotNull(contents.Single().ImageFileId); + } + + /// + /// Verify options creation. + /// + [Fact] + public void VerifyAssistantMessageAdapterGetMessageWithAttachment() + { + ChatMessageContent message = new(AuthorRole.User, items: [new MessageAttachmentContent("file-id")]); + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + Assert.NotNull(options.Attachments); + Assert.Single(options.Attachments); + Assert.NotNull(options.Attachments.Single().FileId); + } + + /// + /// Verify options creation. + /// + [Fact] + public void VerifyAssistantMessageAdapterGetMessageWithAll() + { + ChatMessageContent message = + new( + AuthorRole.User, + items: + [ + new TextContent("test"), + new ImageContent(new Uri("https://localhost/myimage.png")), // %%% + new FileReferenceContent("file-id"), + new MessageAttachmentContent("file-id"), + ]); + MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + Assert.NotNull(contents); + Assert.Equal(3, contents.Length); + Assert.NotNull(options.Attachments); + Assert.Single(options.Attachments); + Assert.NotNull(options.Attachments.Single().FileId); + } +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs similarity index 97% rename from dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientFactoryTests.cs rename to dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs index 46b45e419213..a2ce548d5e0e 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs @@ -7,7 +7,7 @@ using OpenAI; using Xunit; -namespace SemanticKernel.Agents.UnitTests.OpenAI; +namespace SemanticKernel.Agents.UnitTests.OpenAI.Internal; /// /// Unit testing of . diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs index a90050623599..ba4992304227 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs @@ -24,7 +24,6 @@ public void OpenAIThreadCreationOptionsInitialState() Assert.Null(options.Metadata); Assert.Null(options.VectorStoreId); Assert.Null(options.CodeInterpterFileIds); - Assert.False(options.EnableCodeInterpreter); } /// @@ -40,13 +39,11 @@ public void OpenAIThreadCreationOptionsAssignment() VectorStoreId = "#vs", Metadata = new Dictionary() { { "a", "1" } }, CodeInterpterFileIds = ["file1"], - EnableCodeInterpreter = true, }; Assert.Single(definition.Messages); Assert.Single(definition.Metadata); Assert.Equal("#vs", definition.VectorStoreId); Assert.Single(definition.CodeInterpterFileIds); - Assert.True(definition.EnableCodeInterpreter); } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs index e87688791df3..eece5208ed1b 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Collections.Generic; -using Microsoft.SemanticKernel; +using System; using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.ChatCompletion; using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI; @@ -16,37 +14,53 @@ public class RunPollingOptionsTests /// Verify initial state. /// [Fact] - public void RunPollingOptionsInitialState() + public void RunPollingOptionsInitialStateTest() { - OpenAIThreadCreationOptions options = new(); + RunPollingOptions options = new(); - Assert.Null(options.Messages); - Assert.Null(options.Metadata); - Assert.Null(options.VectorStoreId); - Assert.Null(options.CodeInterpterFileIds); - Assert.False(options.EnableCodeInterpreter); + Assert.Equal(RunPollingOptions.DefaultPollingInterval, options.RunPollingInterval); + Assert.Equal(RunPollingOptions.DefaultPollingBackoff, options.RunPollingBackoff); + Assert.Equal(RunPollingOptions.DefaultMessageSynchronizationDelay, options.MessageSynchronizationDelay); + Assert.Equal(RunPollingOptions.DefaultPollingBackoffThreshold, options.RunPollingBackoffThreshold); } /// s /// Verify initialization. /// [Fact] - public void RunPollingOptionsAssignment() + public void RunPollingOptionsAssignmentTest() { - OpenAIThreadCreationOptions definition = + RunPollingOptions options = new() { - Messages = [new ChatMessageContent(AuthorRole.User, "test")], - VectorStoreId = "#vs", - Metadata = new Dictionary() { { "a", "1" } }, - CodeInterpterFileIds = ["file1"], - EnableCodeInterpreter = true, + RunPollingInterval = TimeSpan.FromSeconds(3), + RunPollingBackoff = TimeSpan.FromSeconds(4), + RunPollingBackoffThreshold = 8, + MessageSynchronizationDelay = TimeSpan.FromSeconds(5), }; - Assert.Single(definition.Messages); - Assert.Single(definition.Metadata); - Assert.Equal("#vs", definition.VectorStoreId); - Assert.Single(definition.CodeInterpterFileIds); - Assert.True(definition.EnableCodeInterpreter); + Assert.Equal(3, options.RunPollingInterval.TotalSeconds); + Assert.Equal(4, options.RunPollingBackoff.TotalSeconds); + Assert.Equal(5, options.MessageSynchronizationDelay.TotalSeconds); + Assert.Equal(8, options.RunPollingBackoffThreshold); + } + + + /// s + /// Verify initialization. + /// + [Fact] + public void RunPollingOptionsGetIntervalTest() + { + RunPollingOptions options = + new() + { + RunPollingInterval = TimeSpan.FromSeconds(3), + RunPollingBackoff = TimeSpan.FromSeconds(4), + RunPollingBackoffThreshold = 8, + }; + + Assert.Equal(options.RunPollingInterval, options.GetPollingInterval(8)); + Assert.Equal(options.RunPollingBackoff, options.GetPollingInterval(9)); } } diff --git a/dotnet/src/SemanticKernel.Abstractions/Contents/AnnotationContent.cs b/dotnet/src/SemanticKernel.Abstractions/Contents/AnnotationContent.cs index f9e6f9f3d71f..fd27b35a4b0f 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Contents/AnnotationContent.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Contents/AnnotationContent.cs @@ -44,7 +44,7 @@ public AnnotationContent() /// Initializes a new instance of the class. /// /// The model ID used to generate the content. - /// Inner content, + /// Inner content /// Additional metadata public AnnotationContent( string? modelId = null, diff --git a/dotnet/src/SemanticKernel.Abstractions/Contents/FileReferenceContent.cs b/dotnet/src/SemanticKernel.Abstractions/Contents/FileReferenceContent.cs index 16ac0cd7828e..925d74d0c731 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Contents/FileReferenceContent.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Contents/FileReferenceContent.cs @@ -28,7 +28,7 @@ public FileReferenceContent() /// /// The identifier of the referenced file. /// The model ID used to generate the content. - /// Inner content, + /// Inner content /// Additional metadata public FileReferenceContent( string fileId, diff --git a/dotnet/src/SemanticKernel.Abstractions/Contents/MessageAttachmentContent.cs b/dotnet/src/SemanticKernel.Abstractions/Contents/MessageAttachmentContent.cs new file mode 100644 index 000000000000..6f4f55f9af4a --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Contents/MessageAttachmentContent.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace Microsoft.SemanticKernel; + +/// +/// Content type to support message attachment. +/// +[Experimental("SKEXP0110")] +public class MessageAttachmentContent : FileReferenceContent +{ + /// + /// The associated tool. + /// + public string Tool { get; init; } = string.Empty; + + /// + /// Initializes a new instance of the class. + /// + [JsonConstructor] + public MessageAttachmentContent() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The identifier of the referenced file. + /// The model ID used to generate the content. + /// Inner content + /// Additional metadata + public MessageAttachmentContent( + string fileId, + string? modelId = null, + object? innerContent = null, + IReadOnlyDictionary? metadata = null) + : base(fileId, modelId, innerContent, metadata) + { + // %%% TOOL TYPE + } +} From 991dd59b3968236aaa4e64e2079c7d692b2473f2 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 16:22:33 -0700 Subject: [PATCH 052/121] Blank line --- dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs index eece5208ed1b..9ec3567c0987 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs @@ -45,7 +45,6 @@ public void RunPollingOptionsAssignmentTest() Assert.Equal(8, options.RunPollingBackoffThreshold); } - /// s /// Verify initialization. /// From 6bb0613642556148334605850d2ab7d3b1e9cf4c Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 16:31:39 -0700 Subject: [PATCH 053/121] Checkpoint --- .../Concepts/Agents/OpenAIAssistant_FileManipulation.cs | 1 - dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs | 1 - .../OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs | 3 ++- dotnet/src/Agents/OpenAI/Internal/AddHeaderRequestPolicy.cs | 2 +- dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs | 4 +--- dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs | 2 +- .../OpenAI/Logging/AssistantThreadActionsLogMessages.cs | 1 + dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 1 + dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs | 1 + .../UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs | 2 +- .../Extensions/OpenAIServiceConfigurationExtensionsTests.cs | 1 - .../UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs | 1 + 12 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index ee6c887c14db..ff4f26a92ba6 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -3,7 +3,6 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI.Files; using Resources; diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs index 9189214701c6..9fe775e0b50e 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs @@ -2,7 +2,6 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI.Files; using OpenAI.VectorStores; diff --git a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs index 76000128ce3a..29e5e36c6f4c 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs @@ -1,9 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using OpenAI; using OpenAI.Files; using OpenAI.VectorStores; -namespace Microsoft.SemanticKernel.Agents.OpenAI.Extensions; +namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Extension method for creating OpenAI clients from a . diff --git a/dotnet/src/Agents/OpenAI/Internal/AddHeaderRequestPolicy.cs b/dotnet/src/Agents/OpenAI/Internal/AddHeaderRequestPolicy.cs index 1fb0698ffa77..d017fb403f23 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AddHeaderRequestPolicy.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AddHeaderRequestPolicy.cs @@ -2,7 +2,7 @@ using Azure.Core; using Azure.Core.Pipeline; -namespace Microsoft.SemanticKernel.Agents.OpenAI; +namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; /// /// Helper class to inject headers into Azure SDK HTTP pipeline diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index b3ceaee7ac77..289a1074795e 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -9,13 +8,12 @@ using System.Threading.Tasks; using Azure; using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI; using OpenAI.Assistants; -namespace Microsoft.SemanticKernel.Agents.OpenAI; +namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; /// /// Actions associated with an Open Assistant thread. diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs index ddfd8aef8c31..0c89ea1b3c2b 100644 --- a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs @@ -6,7 +6,7 @@ using Microsoft.SemanticKernel.Http; using OpenAI; -namespace Microsoft.SemanticKernel.Agents.OpenAI; +namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; internal static class OpenAIClientFactory { diff --git a/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs b/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs index 288abadde2dd..3a39c314c5c3 100644 --- a/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs +++ b/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 294330d30da5..336e7ed001d1 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using OpenAI; using OpenAI.Assistants; diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs index 8531178543e4..dabc0bae424b 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantChannel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs index 0a4076055fae..3c2945ad0fb9 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs @@ -2,7 +2,7 @@ using System.Linq; using Azure.Core; using Azure.Core.Pipeline; -using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI.Azure; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs index 288c0b2304f2..650503a94e5e 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.Agents.OpenAI.Extensions; using OpenAI.Files; using OpenAI.VectorStores; using Xunit; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs index a2ce548d5e0e..7fa2ebca23c8 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs @@ -3,6 +3,7 @@ using System.Net.Http; using Azure.Core; using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using Moq; using OpenAI; using Xunit; From 1709575e8e62381e586c27aacfe32bd88edae44a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 17:05:15 -0700 Subject: [PATCH 054/121] Checkpoint --- .../Internal/AssistantRunOptionsFactory.cs | 9 +- .../OpenAI/Internal/AssistantThreadActions.cs | 2 +- .../OpenAI/OpenAIAssistantExecutionOptions.cs | 3 - .../OpenAIAssistantInvocationOptions.cs | 3 - .../Internal/AssistantMessageAdapterTests.cs | 2 +- .../AssistantRunOptionsFactoryTests.cs | 119 +++++++++++++++++- 6 files changed, 120 insertions(+), 18 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs index 40b9ef329691..97132677e8c7 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs @@ -7,11 +7,10 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; internal static class AssistantRunOptionsFactory { /// - /// %%% + /// Produce by reconciling and . /// - /// - /// - /// + /// The assistant definition + /// The run specific options public static RunCreationOptions GenerateOptions(OpenAIAssistantDefinition definition, OpenAIAssistantInvocationOptions? invocationOptions) { int? truncationMessageCount = ResolveExecutionSetting(invocationOptions?.TruncationMessageCount, definition.ExecutionOptions?.TruncationMessageCount); @@ -26,7 +25,7 @@ public static RunCreationOptions GenerateOptions(OpenAIAssistantDefinition defin ParallelToolCallsEnabled = ResolveExecutionSetting(invocationOptions?.ParallelToolCallsEnabled, definition.ExecutionOptions?.ParallelToolCallsEnabled), ResponseFormat = ResolveExecutionSetting(invocationOptions?.EnableJsonResponse, definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, Temperature = ResolveExecutionSetting(invocationOptions?.Temperature, definition.Temperature), - //ToolConstraint // %%% TODO ISSUE + //ToolConstraint - Not Supported: https://github.com/microsoft/semantic-kernel/issues/6795 TruncationStrategy = truncationMessageCount.HasValue ? RunTruncationStrategy.CreateLastMessagesStrategy(truncationMessageCount.Value) : null, }; diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 289a1074795e..cea8dbaf9664 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -126,7 +126,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(agent.Definition, invocationOptions); - options.ToolsOverride.AddRange(agent.Tools); // %%% + options.ToolsOverride.AddRange(agent.Tools); ThreadRun run = await client.CreateRunAsync(threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs index 9208d8184d82..2f87d326eb75 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs @@ -25,9 +25,6 @@ public sealed class OpenAIAssistantExecutionOptions /// public bool? ParallelToolCallsEnabled { get; init; } - //public ToolConstraint? RequiredTool { get; init; } // %%% ENUM ??? - //public KernelFunction? RequiredToolFunction { get; init; } // %%% PLUGIN ??? - /// /// When set, the thread will be truncated to the N most recent messages in the thread. /// diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs index 2fee5dd84503..1aa0c3ffa745 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs @@ -47,9 +47,6 @@ public sealed class OpenAIAssistantInvocationOptions /// public bool? ParallelToolCallsEnabled { get; init; } - //public ToolConstraint? RequiredTool { get; init; } // %%% ENUM ??? - //public KernelFunction? RequiredToolFunction { get; init; } // %%% PLUGIN ??? - /// /// When set, the thread will be truncated to the N most recent messages in the thread. /// diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs index b8f93db1107b..1a60f1014bf0 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs @@ -194,7 +194,7 @@ public void VerifyAssistantMessageAdapterGetMessageWithAll() items: [ new TextContent("test"), - new ImageContent(new Uri("https://localhost/myimage.png")), // %%% + new ImageContent(new Uri("https://localhost/myimage.png")), new FileReferenceContent("file-id"), new MessageAttachmentContent("file-id"), ]); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs index 991cbcb2d86f..325e93969f20 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs @@ -1,15 +1,124 @@ // Copyright (c) Microsoft. All rights reserved. -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; +using OpenAI.Assistants; +using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI.Internal; /// -/// %%% +/// Unit testing of . /// public class AssistantRunOptionsFactoryTests { + /// + /// Verify run options generation with null . + /// + [Fact] + public void AssistantRunOptionsFactoryExecutionOptionsNullTest() + { + OpenAIAssistantDefinition definition = + new() + { + Temperature = 0.5F, + }; + + RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(definition, null); + Assert.NotNull(options); + Assert.Null(options.Temperature); + Assert.Null(options.NucleusSamplingFactor); + Assert.Empty(options.Metadata); + } + + /// + /// Verify run options generation with equivalent . + /// + [Fact] + public void AssistantRunOptionsFactoryExecutionOptionsEquivalentTest() + { + OpenAIAssistantDefinition definition = + new() + { + Temperature = 0.5F, + }; + + OpenAIAssistantInvocationOptions invocationOptions = + new() + { + Temperature = 0.5F, + }; + + RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(definition, invocationOptions); + Assert.NotNull(options); + Assert.Null(options.Temperature); + Assert.Null(options.NucleusSamplingFactor); + } + + /// + /// Verify run options generation with override. + /// + [Fact] + public void AssistantRunOptionsFactoryExecutionOptionsOverrideTest() + { + OpenAIAssistantDefinition definition = + new() + { + Temperature = 0.5F, + ExecutionOptions = + new() + { + TruncationMessageCount = 5, + }, + }; + + OpenAIAssistantInvocationOptions invocationOptions = + new() + { + Temperature = 0.9F, + TruncationMessageCount = 8, + EnableJsonResponse = true, + }; + + RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(definition, invocationOptions); + Assert.NotNull(options); + Assert.Equal(0.9F, options.Temperature); + Assert.Equal(8, options.TruncationStrategy.LastMessages); + Assert.Equal(AssistantResponseFormat.JsonObject, options.ResponseFormat); + Assert.Null(options.NucleusSamplingFactor); + } + + /// + /// Verify run options generation with metadata. + /// + [Fact] + public void AssistantRunOptionsFactoryExecutionOptionsMetadataTest() + { + OpenAIAssistantDefinition definition = + new() + { + Temperature = 0.5F, + ExecutionOptions = + new() + { + TruncationMessageCount = 5, + }, + }; + + OpenAIAssistantInvocationOptions invocationOptions = + new() + { + Metadata = new Dictionary + { + { "key1", "value" }, + { "key2", null! }, + }, + }; + + RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(definition, invocationOptions); + + Assert.Equal(2, options.Metadata.Count); + Assert.Equal("value", options.Metadata["key1"]); + Assert.Equal(string.Empty, options.Metadata["key2"]); + } } From f6e8c7befc727f8e2f9ceb1d74facdd77201987f Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 17:22:06 -0700 Subject: [PATCH 055/121] More testing --- .../OpenAI/Internal/AssistantThreadActions.cs | 1 - .../OpenAI/Internal/OpenAIClientFactory.cs | 1 + .../Internal/OpenAIClientFactoryTests.cs | 21 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index cea8dbaf9664..bfde63641d18 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Azure; using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI; using OpenAI.Assistants; diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs index 0c89ea1b3c2b..0b32ae8e0411 100644 --- a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs @@ -33,6 +33,7 @@ public static OpenAIClient CreateClient(OpenAIServiceConfiguration config) { return new AzureOpenAIClient(config.Endpoint, config.Credential, clientOptions); } + if (!string.IsNullOrEmpty(config.ApiKey)) { return new AzureOpenAIClient(config.Endpoint, config.ApiKey!, clientOptions); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs index 7fa2ebca23c8..df2387c917e3 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs @@ -2,6 +2,7 @@ using System; using System.Net.Http; using Azure.Core; +using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using Moq; @@ -38,6 +39,26 @@ public void VerifyOpenAIClientFactoryTargetAzureByCredential() Assert.NotNull(client); } + /// + /// Verify that the factory throws exception for null credential. + /// + [Fact] + public void VerifyOpenAIClientFactoryTargetAzureNullCredential() + { + OpenAIServiceConfiguration config = new() { Type = OpenAIServiceConfiguration.OpenAIServiceType.AzureOpenAI }; + Assert.Throws(() => OpenAIClientFactory.CreateClient(config)); + } + + /// + /// Verify that the factory throws exception for null credential. + /// + [Fact] + public void VerifyOpenAIClientFactoryTargetUnknownTypes() + { + OpenAIServiceConfiguration config = new() { Type = (OpenAIServiceConfiguration.OpenAIServiceType)99 }; + Assert.Throws(() => OpenAIClientFactory.CreateClient(config)); + } + /// /// Verify that the factory can create a client for various OpenAI service configurations. /// From 117066c2a8e665e148fa3675d494795f89370ff3 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 22 Jul 2024 17:31:37 -0700 Subject: [PATCH 056/121] Clean-up --- .../GettingStartedWithAgents/Step6_DependencyInjection.cs | 6 +++--- .../OpenAI/Internal/AssistantMessageAdapterTests.cs | 1 - dotnet/src/Experimental/Agents/Internal/Agent.cs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs b/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs index 06c4ca3060e0..21af5db70dce 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs @@ -38,9 +38,9 @@ public async Task UseDependencyInjectionToCreateAgentAsync() if (this.UseOpenAIConfig) { - //serviceContainer.AddOpenAIChatCompletion( // %%% CONNECTOR IMPL - // TestConfiguration.OpenAI.ChatModelId, - // TestConfiguration.OpenAI.ApiKey); + serviceContainer.AddOpenAIChatCompletion( + TestConfiguration.OpenAI.ChatModelId, + TestConfiguration.OpenAI.ApiKey); } else { diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs index 1a60f1014bf0..241bd5d8ada5 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using Microsoft.SemanticKernel.ChatCompletion; diff --git a/dotnet/src/Experimental/Agents/Internal/Agent.cs b/dotnet/src/Experimental/Agents/Internal/Agent.cs index c078703cda02..ae64af04d39a 100644 --- a/dotnet/src/Experimental/Agents/Internal/Agent.cs +++ b/dotnet/src/Experimental/Agents/Internal/Agent.cs @@ -121,7 +121,7 @@ internal Agent( this.Kernel = this._restContext.HasVersion ? builder.AddAzureOpenAIChatCompletion(this._model.Model, this.GetAzureRootEndpoint(), this._restContext.ApiKey).Build() : - new(); // %%% HACK builder.AddOpenAIChatCompletion(this._model.Model, this._restContext.ApiKey).Build(); + builder.AddOpenAIChatCompletion(this._model.Model, this._restContext.ApiKey).Build(); if (plugins is not null) { From 10c4afb668a7cf80a3ffcfd4b52a6c2d7f060707 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 23 Jul 2024 13:27:48 -0700 Subject: [PATCH 057/121] Sync with bug-fix --- .../Internal/AssistantMessageAdapter.cs | 4 - .../OpenAI/Internal/AssistantThreadActions.cs | 86 +++++++++---------- .../Internal/AssistantMessageAdapterTests.cs | 20 +---- .../Contents/MessageAttachmentContent.cs | 42 --------- 4 files changed, 43 insertions(+), 109 deletions(-) delete mode 100644 dotnet/src/SemanticKernel.Abstractions/Contents/MessageAttachmentContent.cs diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs index 99aeb53f1ebe..50b9b9e9547f 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs @@ -43,10 +43,6 @@ public static IEnumerable GetMessageContents(ChatMessageContent // yield return MessageContent.FromImageUrl(new Uri(imageContent.DataUri!)); //} } - else if (content is MessageAttachmentContent attachmentContent) - { - options.Attachments.Add(new MessageCreationAttachment(attachmentContent.FileId, [ToolDefinition.CreateCodeInterpreter()])); // %%% TODO: Tool Type - } else if (content is FileReferenceContent fileContent) { yield return MessageContent.FromImageFileId(fileContent.FileId); diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index bfde63641d18..68e1f4e0ce0f 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -43,8 +43,7 @@ internal static class AssistantThreadActions /// if a system message is present, without taking any other action public static async Task CreateMessageAsync(AssistantClient client, string threadId, ChatMessageContent message, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(message.Content) || - message.Items.Any(i => i is FunctionCallContent)) + if (message.Items.Any(i => i is FunctionCallContent)) { return; } @@ -86,14 +85,11 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist assistantName ??= message.AssistantId; - foreach (MessageContent itemContent in message.Content) - { - ChatMessageContent content = GenerateMessageContent(role, assistantName, itemContent); + ChatMessageContent content = GenerateMessageContent(assistantName, message); - if (content.Items.Count > 0) - { - yield return content; - } + if (content.Items.Count > 0) + { + yield return content; } } } @@ -221,18 +217,13 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist if (message is not null) { - AuthorRole role = new(message.Role.ToString()); + ChatMessageContent content = GenerateMessageContent(agent.GetName(), message); - foreach (MessageContent itemContent in message.Content) + if (content.Items.Count > 0) { - ChatMessageContent content = GenerateMessageContent(role, agent.Name, itemContent); - - if (content.Items.Count > 0) - { - ++messageCount; + ++messageCount; - yield return (IsVisible: true, Message: content); - } + yield return (IsVisible: true, Message: content); } } } @@ -336,6 +327,38 @@ IEnumerable ParseFunctionStep(OpenAIAssistantAgent agent, R } } + private static ChatMessageContent GenerateMessageContent(string? assistantName, ThreadMessage message) + { + AuthorRole role = new(message.Role.ToString()); + + ChatMessageContent content = + new(role, content: null) + { + AuthorName = assistantName, + }; + + foreach (MessageContent itemContent in message.Content) + { + // Process text content + if (!string.IsNullOrEmpty(itemContent.Text)) + { + content.Items.Add(new TextContent(itemContent.Text.Trim())); + + foreach (TextAnnotation annotation in itemContent.TextAnnotations) + { + content.Items.Add(GenerateAnnotationContent(annotation)); + } + } + // Process image content + else if (itemContent.ImageFileId != null) + { + content.Items.Add(new FileReferenceContent(itemContent.ImageFileId)); + } + } + + return content; + } + private static AnnotationContent GenerateAnnotationContent(TextAnnotation annotation) { string? fileId = null; @@ -363,7 +386,7 @@ private static ChatMessageContent GenerateCodeInterpreterContent(string agentNam { return new ChatMessageContent( - AuthorRole.Tool, + AuthorRole.Assistant, [ new TextContent(code) ]) @@ -401,31 +424,6 @@ private static ChatMessageContent GenerateFunctionResultContent(string agentName return functionCallContent; } - private static ChatMessageContent GenerateMessageContent(AuthorRole role, string? assistantName, MessageContent itemContent) - { - ChatMessageContent content = - new(role, content: null) - { - AuthorName = assistantName, - }; - - if (!string.IsNullOrEmpty(itemContent.Text)) - { - content.Items.Add(new TextContent(itemContent.Text.Trim())); - foreach (TextAnnotation annotation in itemContent.TextAnnotations) - { - content.Items.Add(GenerateAnnotationContent(annotation)); - } - } - // Process image content - else if (itemContent.ImageFileId != null) - { - content.Items.Add(new FileReferenceContent(itemContent.ImageFileId)); - } - - return content; - } - private static Task[] ExecuteFunctionSteps(OpenAIAssistantAgent agent, FunctionCallContent[] functionSteps, CancellationToken cancellationToken) { Task[] functionTasks = new Task[functionSteps.Length]; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs index 241bd5d8ada5..eaa00eb529eb 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs @@ -167,20 +167,6 @@ public void VerifyAssistantMessageAdapterGetMessageWithImageFile() Assert.NotNull(contents.Single().ImageFileId); } - /// - /// Verify options creation. - /// - [Fact] - public void VerifyAssistantMessageAdapterGetMessageWithAttachment() - { - ChatMessageContent message = new(AuthorRole.User, items: [new MessageAttachmentContent("file-id")]); - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); - MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); - Assert.NotNull(options.Attachments); - Assert.Single(options.Attachments); - Assert.NotNull(options.Attachments.Single().FileId); - } - /// /// Verify options creation. /// @@ -194,15 +180,11 @@ public void VerifyAssistantMessageAdapterGetMessageWithAll() [ new TextContent("test"), new ImageContent(new Uri("https://localhost/myimage.png")), - new FileReferenceContent("file-id"), - new MessageAttachmentContent("file-id"), + new FileReferenceContent("file-id") ]); MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); Assert.NotNull(contents); Assert.Equal(3, contents.Length); - Assert.NotNull(options.Attachments); - Assert.Single(options.Attachments); - Assert.NotNull(options.Attachments.Single().FileId); } } diff --git a/dotnet/src/SemanticKernel.Abstractions/Contents/MessageAttachmentContent.cs b/dotnet/src/SemanticKernel.Abstractions/Contents/MessageAttachmentContent.cs deleted file mode 100644 index 6f4f55f9af4a..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/Contents/MessageAttachmentContent.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Text.Json.Serialization; - -namespace Microsoft.SemanticKernel; - -/// -/// Content type to support message attachment. -/// -[Experimental("SKEXP0110")] -public class MessageAttachmentContent : FileReferenceContent -{ - /// - /// The associated tool. - /// - public string Tool { get; init; } = string.Empty; - - /// - /// Initializes a new instance of the class. - /// - [JsonConstructor] - public MessageAttachmentContent() - { } - - /// - /// Initializes a new instance of the class. - /// - /// The identifier of the referenced file. - /// The model ID used to generate the content. - /// Inner content - /// Additional metadata - public MessageAttachmentContent( - string fileId, - string? modelId = null, - object? innerContent = null, - IReadOnlyDictionary? metadata = null) - : base(fileId, modelId, innerContent, metadata) - { - // %%% TOOL TYPE - } -} From 3396076c06b0cdb3d066b57ed332d3509122406a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 24 Jul 2024 09:40:38 -0700 Subject: [PATCH 058/121] Checkpoint --- dotnet/Directory.Packages.props | 2 +- dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj | 2 +- ...eAdapter.cs => AssistantMessageFactory.cs} | 21 ++++++- .../Internal/AssistantRunOptionsFactory.cs | 8 ++- .../OpenAI/Internal/AssistantThreadActions.cs | 43 ++++++++++++- .../Internal/AssistantToolResourcesFactory.cs | 51 +++++++++++++++ .../OpenAI/Internal/OpenAIClientFactory.cs | 3 + .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 62 ++----------------- ...sts.cs => AssistantMessageFactoryTests.cs} | 27 ++++---- .../Connectors.OpenAI.csproj | 2 +- 10 files changed, 138 insertions(+), 83 deletions(-) rename dotnet/src/Agents/OpenAI/Internal/{AssistantMessageAdapter.cs => AssistantMessageFactory.cs} (67%) create mode 100644 dotnet/src/Agents/OpenAI/Internal/AssistantToolResourcesFactory.cs rename dotnet/src/Agents/UnitTests/OpenAI/Internal/{AssistantMessageAdapterTests.cs => AssistantMessageFactoryTests.cs} (79%) diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 2da11cbc8882..1c1884adb7e7 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -8,7 +8,7 @@ - + diff --git a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj index a4cd9b8b9f57..fa73edc77cde 100644 --- a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj +++ b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj @@ -33,7 +33,7 @@ - + diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs similarity index 67% rename from dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs rename to dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs index 50b9b9e9547f..0814d9720ecf 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageAdapter.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs @@ -4,8 +4,19 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; -internal static class AssistantMessageAdapter +/// +/// Factory for creating based on . +/// Also able to produce . +/// +/// +/// Improves testability. +/// +internal static class AssistantMessageFactory { + /// + /// Produces based on . + /// + /// The message content. public static MessageCreationOptions CreateOptions(ChatMessageContent message) { MessageCreationOptions options = new(); @@ -21,7 +32,11 @@ public static MessageCreationOptions CreateOptions(ChatMessageContent message) return options; } - public static IEnumerable GetMessageContents(ChatMessageContent message, MessageCreationOptions options) + /// + /// Translates into enumeration of . + /// + /// The message content. + public static IEnumerable GetMessageContents(ChatMessageContent message) { foreach (KernelContent content in message.Items) { @@ -37,7 +52,7 @@ public static IEnumerable GetMessageContents(ChatMessageContent } //else if (string.IsNullOrWhiteSpace(imageContent.DataUri)) //{ - // %%% BUG: https://github.com/openai/openai-dotnet/issues/135 + // SDK BUG - BAD SIGNATURE (https://github.com/openai/openai-dotnet/issues/135) // URI does not accept the format used for `DataUri` // Approach is inefficient anyway... // yield return MessageContent.FromImageUrl(new Uri(imageContent.DataUri!)); diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs index 97132677e8c7..03f0b5ca067a 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs @@ -4,6 +4,12 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; +/// +/// Factory for creating definition. +/// +/// +/// Improves testability. +/// internal static class AssistantRunOptionsFactory { /// @@ -25,7 +31,7 @@ public static RunCreationOptions GenerateOptions(OpenAIAssistantDefinition defin ParallelToolCallsEnabled = ResolveExecutionSetting(invocationOptions?.ParallelToolCallsEnabled, definition.ExecutionOptions?.ParallelToolCallsEnabled), ResponseFormat = ResolveExecutionSetting(invocationOptions?.EnableJsonResponse, definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, Temperature = ResolveExecutionSetting(invocationOptions?.Temperature, definition.Temperature), - //ToolConstraint - Not Supported: https://github.com/microsoft/semantic-kernel/issues/6795 + //ToolConstraint - Not Currently Supported (https://github.com/microsoft/semantic-kernel/issues/6795) TruncationStrategy = truncationMessageCount.HasValue ? RunTruncationStrategy.CreateLastMessagesStrategy(truncationMessageCount.Value) : null, }; diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 68e1f4e0ce0f..e8830e9106f7 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -33,6 +33,43 @@ internal static class AssistantThreadActions RunStatus.Cancelled, ]; + /// + /// Create a new assistant thread. + /// + /// %%% + /// %%% + /// The to monitor for cancellation requests. The default is . + /// The thread identifier + public static async Task CreateThreadAsync(AssistantClient client, OpenAIThreadCreationOptions? options, CancellationToken cancellationToken = default) + { + ThreadCreationOptions createOptions = + new() + { + ToolResources = AssistantToolResourcesFactory.GenerateToolResources(options?.VectorStoreId, options?.CodeInterpterFileIds), + }; + + if (options?.Messages != null) + { + foreach (ChatMessageContent message in options.Messages) + { + ThreadInitializationMessage threadMessage = new(AssistantMessageFactory.GetMessageContents(message)); + createOptions.InitialMessages.Add(threadMessage); + } + } + + if (options?.Metadata != null) + { + foreach (KeyValuePair item in options.Metadata) + { + createOptions.Metadata[item.Key] = item.Value; + } + } + + AssistantThread thread = await client.CreateThreadAsync(createOptions, cancellationToken).ConfigureAwait(false); + + return thread.Id; + } + /// /// Create a message in the specified thread. /// @@ -48,11 +85,11 @@ public static async Task CreateMessageAsync(AssistantClient client, string threa return; } - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); await client.CreateMessageAsync( threadId, - AssistantMessageAdapter.GetMessageContents(message, options), + AssistantMessageFactory.GetMessageContents(message), options, cancellationToken).ConfigureAwait(false); } @@ -76,7 +113,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist if (!string.IsNullOrWhiteSpace(message.AssistantId) && !agentNames.TryGetValue(message.AssistantId, out assistantName)) { - Assistant assistant = await client.GetAssistantAsync(message.AssistantId).ConfigureAwait(false); // %%% BUG CANCEL TOKEN + Assistant assistant = await client.GetAssistantAsync(message.AssistantId).ConfigureAwait(false); // SDK BUG - CANCEL TOKEN (https://github.com/microsoft/semantic-kernel/issues/7431) if (!string.IsNullOrWhiteSpace(assistant.Name)) { agentNames.Add(assistant.Id, assistant.Name); diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantToolResourcesFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantToolResourcesFactory.cs new file mode 100644 index 000000000000..e7566a5db4f8 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantToolResourcesFactory.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; +using OpenAI.Assistants; + +namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; + +/// +/// Factory for creating definition. +/// +/// +/// Improves testability. +/// +internal static class AssistantToolResourcesFactory +{ + /// + /// Produces a definition based on the provided parameters. + /// + /// An optional vector-store-id for the 'file_search' tool + /// An optionallist of file-identifiers for the 'code_interpreter' tool. + public static ToolResources? GenerateToolResources(string? vectorStoreId, IReadOnlyList? codeInterpreterFileIds) + { + bool hasFileSearch = !string.IsNullOrWhiteSpace(vectorStoreId); + bool hasCodeInterpreterFiles = (codeInterpreterFileIds?.Count ?? 0) > 0; + + ToolResources? toolResources = null; + + if (hasFileSearch || hasCodeInterpreterFiles) + { + toolResources = + new ToolResources() + { + FileSearch = + hasFileSearch ? + new FileSearchToolResources() + { + VectorStoreIds = [vectorStoreId!], + } : + null, + CodeInterpreter = + hasCodeInterpreterFiles ? + new CodeInterpreterToolResources() + { + FileIds = (IList)codeInterpreterFileIds!, + } : + null, + }; + } + + return toolResources; + } +} diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs index 0b32ae8e0411..4474da62115f 100644 --- a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs @@ -8,6 +8,9 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; +/// +/// Factory for creating . +/// internal static class OpenAIClientFactory { /// diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 336e7ed001d1..b94c99bf1b87 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -116,7 +116,7 @@ public static async Task RetrieveAsync( AssistantClient client = CreateClient(config); // Retrieve the assistant - Assistant model = await client.GetAssistantAsync(id).ConfigureAwait(false); // %%% BUG CANCEL TOKEN + Assistant model = await client.GetAssistantAsync(id).ConfigureAwait(false); // SDK BUG - CANCEL TOKEN (https://github.com/microsoft/semantic-kernel/issues/7431) // Instantiate the agent return @@ -132,7 +132,7 @@ public static async Task RetrieveAsync( /// The to monitor for cancellation requests. The default is . /// The thread identifier public Task CreateThreadAsync(CancellationToken cancellationToken = default) - => this.CreateThreadAsync(options: null, cancellationToken); + => AssistantThreadActions.CreateThreadAsync(this._client, options: null, cancellationToken); /// /// Create a new assistant thread. @@ -140,28 +140,8 @@ public Task CreateThreadAsync(CancellationToken cancellationToken = defa /// %%% /// The to monitor for cancellation requests. The default is . /// The thread identifier - public async Task CreateThreadAsync(OpenAIThreadCreationOptions? options, CancellationToken cancellationToken = default) - { - ThreadCreationOptions createOptions = - new() - { - ToolResources = GenerateToolResources(options?.VectorStoreId, options?.CodeInterpterFileIds), - }; - - //options.InitialMessages, // %%% TODO - - if (options?.Metadata != null) - { - foreach (KeyValuePair item in options.Metadata) - { - createOptions.Metadata[item.Key] = item.Value; - } - } - - AssistantThread thread = await this._client.CreateThreadAsync(createOptions, cancellationToken).ConfigureAwait(false); - - return thread.Id; - } + public Task CreateThreadAsync(OpenAIThreadCreationOptions? options, CancellationToken cancellationToken = default) + => AssistantThreadActions.CreateThreadAsync(this._client, options, cancellationToken); /// /// Create a new assistant thread. @@ -348,7 +328,7 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss Description = definition.Description, Instructions = definition.Instructions, Name = definition.Name, - ToolResources = GenerateToolResources(definition.VectorStoreId, definition.EnableCodeInterpreter ? definition.CodeInterpterFileIds : null), + ToolResources = AssistantToolResourcesFactory.GenerateToolResources(definition.VectorStoreId, definition.EnableCodeInterpreter ? definition.CodeInterpterFileIds : null), ResponseFormat = definition.EnableJsonResponse ? AssistantResponseFormat.JsonObject : AssistantResponseFormat.Auto, Temperature = definition.Temperature, NucleusSamplingFactor = definition.TopP, @@ -381,38 +361,6 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss return assistantCreationOptions; } - private static ToolResources? GenerateToolResources(string? vectorStoreId, IReadOnlyList? codeInterpreterFileIds) - { - bool hasFileSearch = !string.IsNullOrWhiteSpace(vectorStoreId); - bool hasCodeInterpreterFiles = (codeInterpreterFileIds?.Count ?? 0) > 0; - - ToolResources? toolResources = null; - - if (hasFileSearch || hasCodeInterpreterFiles) - { - toolResources = - new ToolResources() - { - FileSearch = - hasFileSearch ? - new FileSearchToolResources() - { - VectorStoreIds = [vectorStoreId!], - } : - null, - CodeInterpreter = - hasCodeInterpreterFiles ? - new CodeInterpreterToolResources() - { - FileIds = (IList)codeInterpreterFileIds!, - } : - null, - }; - } - - return toolResources; - } - private static AssistantClient CreateClient(OpenAIServiceConfiguration config) { OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs similarity index 79% rename from dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs rename to dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs index eaa00eb529eb..5b13a584deb0 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageAdapterTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs @@ -11,9 +11,9 @@ namespace SemanticKernel.Agents.UnitTests.OpenAI.Internal; /// -/// Unit testing of . +/// Unit testing of . /// -public class AssistantMessageAdapterTests +public class AssistantMessageFactoryTests { /// /// Verify options creation. @@ -25,7 +25,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsDefault() ChatMessageContent message = new(AuthorRole.User, "test"); // Create options - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); // Validate Assert.NotNull(options); @@ -46,7 +46,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataEmpty() }; // Create options - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); // Validate Assert.NotNull(options); @@ -72,7 +72,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsWithMetadata() }; // Create options - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); // Validate Assert.NotNull(options); @@ -101,7 +101,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataNull() }; // Create options - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); + MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); // Validate Assert.NotNull(options); @@ -118,8 +118,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataNull() public void VerifyAssistantMessageAdapterGetMessageContentsWithText() { ChatMessageContent message = new(AuthorRole.User, items: [new TextContent("test")]); - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); - MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); Assert.NotNull(contents); Assert.Single(contents); Assert.NotNull(contents.Single().Text); @@ -132,8 +131,7 @@ public void VerifyAssistantMessageAdapterGetMessageContentsWithText() public void VerifyAssistantMessageAdapterGetMessageWithImageUrl() { ChatMessageContent message = new(AuthorRole.User, items: [new ImageContent(new Uri("https://localhost/myimage.png"))]); - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); - MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); Assert.NotNull(contents); Assert.Single(contents); Assert.NotNull(contents.Single().ImageUrl); @@ -146,8 +144,7 @@ public void VerifyAssistantMessageAdapterGetMessageWithImageUrl() public void VerifyAssistantMessageAdapterGetMessageWithImageData() { ChatMessageContent message = new(AuthorRole.User, items: [new ImageContent(new byte[] { 1, 2, 3 }, "image/png")]); - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); - MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); Assert.NotNull(contents); Assert.Single(contents); Assert.NotNull(contents.Single().ImageUrl); @@ -160,8 +157,7 @@ public void VerifyAssistantMessageAdapterGetMessageWithImageData() public void VerifyAssistantMessageAdapterGetMessageWithImageFile() { ChatMessageContent message = new(AuthorRole.User, items: [new FileReferenceContent("file-id")]); - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); - MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); Assert.NotNull(contents); Assert.Single(contents); Assert.NotNull(contents.Single().ImageFileId); @@ -182,8 +178,7 @@ public void VerifyAssistantMessageAdapterGetMessageWithAll() new ImageContent(new Uri("https://localhost/myimage.png")), new FileReferenceContent("file-id") ]); - MessageCreationOptions options = AssistantMessageAdapter.CreateOptions(message); - MessageContent[] contents = AssistantMessageAdapter.GetMessageContents(message, options).ToArray(); + MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); Assert.NotNull(contents); Assert.Equal(3, contents.Length); } diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Connectors.OpenAI.csproj b/dotnet/src/Connectors/Connectors.OpenAI/Connectors.OpenAI.csproj index f873d8d9cd29..06237a66d39f 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Connectors.OpenAI.csproj +++ b/dotnet/src/Connectors/Connectors.OpenAI/Connectors.OpenAI.csproj @@ -29,6 +29,6 @@ - + From 511e6ca712953e756be33d319d3cc043f42e96c2 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 24 Jul 2024 16:56:37 -0700 Subject: [PATCH 059/121] Test Complete --- .../Agents/OpenAIAssistant_ChartMaker.cs | 4 +- .../Agents/OpenAIAssistant_CodeInterpreter.cs | 2 +- dotnet/samples/Concepts/Concepts.csproj | 1 - .../GettingStartedWithAgents.csproj | 1 - .../OpenAI/Internal/AssistantThreadActions.cs | 4 +- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 4 +- .../OpenAI/OpenAIAssistantExecutionOptions.cs | 6 + .../OpenAI/OpenAIAssistantAgentTests.cs | 472 +++++++++++++----- 8 files changed, 348 insertions(+), 146 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index 63ed511742f8..ce1f05a8b08b 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -37,7 +37,7 @@ await OpenAIAssistantAgent.CreateAsync( }); // Create a chat for agent interaction. - var chat = new AgentGroupChat(); + AgentGroupChat chat = new(); // Respond to user input try @@ -54,7 +54,7 @@ Others 23 373 156 552 Sum 426 1622 856 2904 """); - await InvokeAgentAsync("Can you regenerate this same chart using the category names as the bar colors?"); // %%% WHY NOT ??? + await InvokeAgentAsync("Can you regenerate this same chart using the category names as the bar colors?"); } finally { diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs index 646c1f244967..b3090007e0e4 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs @@ -28,7 +28,7 @@ await OpenAIAssistantAgent.CreateAsync( }); // Create a chat for agent interaction. - var chat = new AgentGroupChat(); + AgentGroupChat chat = new(); // Respond to user input try diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index 5001100fae7d..4816c0b6257e 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -63,7 +63,6 @@ - diff --git a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj index 99d086787951..b95bbd546d34 100644 --- a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj +++ b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj @@ -39,7 +39,6 @@ - diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index e8830e9106f7..1e393027e43d 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -36,8 +36,8 @@ internal static class AssistantThreadActions /// /// Create a new assistant thread. /// - /// %%% - /// %%% + /// The assistant client + /// The options for creating the thread /// The to monitor for cancellation requests. The default is . /// The thread identifier public static async Task CreateThreadAsync(AssistantClient client, OpenAIThreadCreationOptions? options, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index b94c99bf1b87..164eceb5aff5 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -17,7 +17,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// public sealed class OpenAIAssistantAgent : KernelAgent { - private const string OptionsMetadataKey = "__run_options"; + internal const string OptionsMetadataKey = "__run_options"; private readonly Assistant _assistant; private readonly AssistantClient _client; @@ -137,7 +137,7 @@ public Task CreateThreadAsync(CancellationToken cancellationToken = defa /// /// Create a new assistant thread. /// - /// %%% + /// The options for creating the thread /// The to monitor for cancellation requests. The default is . /// The thread identifier public Task CreateThreadAsync(OpenAIThreadCreationOptions? options, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs index 2f87d326eb75..28b625d3eee8 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs @@ -1,4 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Text.Json.Serialization; + namespace Microsoft.SemanticKernel.Agents.OpenAI; /// @@ -12,21 +14,25 @@ public sealed class OpenAIAssistantExecutionOptions /// /// The maximum number of completion tokens that may be used over the course of the run. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? MaxCompletionTokens { get; init; } /// /// The maximum number of prompt tokens that may be used over the course of the run. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? MaxPromptTokens { get; init; } /// /// Enables parallel function calling during tool use. Enabled by default. /// Use this property to disable. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? ParallelToolCallsEnabled { get; init; } /// /// When set, the thread will be truncated to the N most recent messages in the thread. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? TruncationMessageCount { get; init; } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index 8eab72d37805..9c1c789fa8e2 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; @@ -36,25 +38,12 @@ public async Task VerifyOpenAIAssistantAgentCreationEmptyAsync() ModelName = "testmodel", }; - this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentSimple); - - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - this._emptyKernel, - this.CreateTestConfiguration(targetAzure: true), - definition); - - Assert.NotNull(agent); - Assert.NotNull(agent.Id); - Assert.Null(agent.Instructions); - Assert.Null(agent.Name); - Assert.Null(agent.Description); - Assert.False(agent.IsDeleted); + await this.VerifyAgentCreationAsync(definition); } /// /// Verify the invocation and response of - /// for an agent with optional properties defined. + /// for an agent with name, instructions, and description. /// [Fact] public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() @@ -68,130 +57,207 @@ public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() Instructions = "testinstructions", }; - this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentFull); + await this.VerifyAgentCreationAsync(definition); + } - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - this._emptyKernel, - this.CreateTestConfiguration(), - definition); + /// + /// Verify the invocation and response of + /// for an agent with code-interpreter enabled. + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterAsync() + { + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + EnableCodeInterpreter = true, + }; - Assert.NotNull(agent); - Assert.NotNull(agent.Id); - Assert.NotNull(agent.Instructions); - Assert.NotNull(agent.Name); - Assert.NotNull(agent.Description); - Assert.False(agent.IsDeleted); + await this.VerifyAgentCreationAsync(definition); } /// - /// %%% + /// Verify the invocation and response of + /// for an agent with code-interpreter files. /// [Fact] - public async Task VerifyOpenAIAssistantAgentCreationEverythingAsync() // %%% NAME + public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterFilesAsync() { OpenAIAssistantDefinition definition = new() { ModelName = "testmodel", EnableCodeInterpreter = true, - VectorStoreId = "#vs", - Metadata = new Dictionary() { { "a", "1" } }, + CodeInterpterFileIds = ["file1", "file2"], }; - this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentWithEverything); + await this.VerifyAgentCreationAsync(definition); + } - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - this._emptyKernel, - this.CreateTestConfiguration(), - definition); + /// + /// Verify the invocation and response of + /// for an agent with a vector-store-id (for file-search). + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationWithVectorStoreAsync() + { + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + VectorStoreId = "#vs1", + }; - Assert.NotNull(agent); - Assert.Equal(2, agent.Tools.Count); - Assert.True(agent.Tools.OfType().Any()); - Assert.True(agent.Tools.OfType().Any()); - Assert.Equal("#vs", agent.Definition.VectorStoreId); - Assert.Null(agent.Definition.CodeInterpterFileIds); - Assert.NotNull(agent.Definition.Metadata); - Assert.NotEmpty(agent.Definition.Metadata); + await this.VerifyAgentCreationAsync(definition); } /// - /// %%% + /// Verify the invocation and response of + /// for an agent with metadata. /// [Fact] - public async Task VerifyOpenAIAssistantAgentCreationEverything2Async() // %%% NAME + public async Task VerifyOpenAIAssistantAgentCreationWithMetadataAsync() { OpenAIAssistantDefinition definition = new() { ModelName = "testmodel", - EnableCodeInterpreter = true, - CodeInterpterFileIds = ["file1", "file2"], - Metadata = new Dictionary() { { "a", "1" } }, + Metadata = new Dictionary() + { + { "a", "1" }, + { "b", "2" }, + }, }; - this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentWithEverything); + await this.VerifyAgentCreationAsync(definition); + } - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - this._emptyKernel, - this.CreateTestConfiguration(), - definition); + /// + /// Verify the invocation and response of + /// for an agent with json-response mode enabled. + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationWithJsonResponseAsync() + { + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + EnableJsonResponse = true, + }; - Assert.NotNull(agent); - Assert.Equal(2, agent.Tools.Count); - Assert.True(agent.Tools.OfType().Any()); - Assert.True(agent.Tools.OfType().Any()); - //Assert.Null(agent.Definition.VectorStoreId); // %%% SETUP - //Assert.Null(agent.Definition.CodeInterpterFileIds); // %%% SETUP - Assert.NotNull(agent.Definition.Metadata); - Assert.NotEmpty(agent.Definition.Metadata); + await this.VerifyAgentCreationAsync(definition); } /// - /// %%% + /// Verify the invocation and response of + /// for an agent with temperature defined. /// [Fact] - public async Task VerifyOpenAIAssistantAgentCreationEverything3Async() // %%% NAME + public async Task VerifyOpenAIAssistantAgentCreationWithTemperatureAsync() + { + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + Temperature = 2.0F, + }; + + await this.VerifyAgentCreationAsync(definition); + } + + /// + /// Verify the invocation and response of + /// for an agent with topP defined. + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationWithTopPAsync() + { + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + TopP = 2.0F, + }; + + await this.VerifyAgentCreationAsync(definition); + } + + /// + /// Verify the invocation and response of + /// for an agent with empty execution settings. + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAsync() { OpenAIAssistantDefinition definition = new() { ModelName = "testmodel", - EnableCodeInterpreter = false, - EnableJsonResponse = true, - CodeInterpterFileIds = ["file1", "file2"], - Metadata = new Dictionary() { { "a", "1" } }, ExecutionOptions = new(), }; - this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentWithEverything); + await this.VerifyAgentCreationAsync(definition); + } - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - this._emptyKernel, - this.CreateTestConfiguration(), - definition); + /// + /// Verify the invocation and response of + /// for an agent with populated execution settings. + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationWithExecutionOptionsAsync() + { + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + ExecutionOptions = + new() + { + MaxCompletionTokens = 100, + ParallelToolCallsEnabled = false, + } + }; - Assert.NotNull(agent); - Assert.Equal(2, agent.Tools.Count); - Assert.True(agent.Tools.OfType().Any()); - Assert.True(agent.Tools.OfType().Any()); - //Assert.Null(agent.Definition.VectorStoreId); // %%% SETUP - //Assert.Null(agent.Definition.CodeInterpterFileIds); // %%% SETUP - Assert.NotNull(agent.Definition.Metadata); - Assert.NotEmpty(agent.Definition.Metadata); + await this.VerifyAgentCreationAsync(definition); + } + + /// + /// Verify the invocation and response of + /// for an agent with execution settings and meta-data. + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAndMetadataAsync() + { + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + ExecutionOptions = new(), + Metadata = new Dictionary() + { + { "a", "1" }, + { "b", "2" }, + }, + }; + + await this.VerifyAgentCreationAsync(definition); } /// /// Verify the invocation and response of . /// [Fact] - public async Task VerifyOpenAIAssistantAgentRetrieveAsync() + public async Task VerifyOpenAIAssistantAgentRetrievalAsync() { - this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentSimple); + OpenAIAssistantDefinition definition = + new() + { + ModelName = "testmodel", + }; + + this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentPayload(definition)); OpenAIAssistantAgent agent = await OpenAIAssistantAgent.RetrieveAsync( @@ -199,12 +265,7 @@ await OpenAIAssistantAgent.RetrieveAsync( this.CreateTestConfiguration(), "#id"); - Assert.NotNull(agent); - Assert.NotNull(agent.Id); - Assert.Null(agent.Instructions); - Assert.Null(agent.Name); - Assert.Null(agent.Description); - Assert.False(agent.IsDeleted); + ValidateAgentDefinition(agent, definition); } /// @@ -432,6 +493,91 @@ public OpenAIAssistantAgentTests() this._emptyKernel = new Kernel(); } + private async Task VerifyAgentCreationAsync(OpenAIAssistantDefinition definition) + { + this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentPayload(definition)); + + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + this._emptyKernel, + this.CreateTestConfiguration(), + definition); + + ValidateAgentDefinition(agent, definition); + } + + private static void ValidateAgentDefinition(OpenAIAssistantAgent agent, OpenAIAssistantDefinition sourceDefinition) + { + // Verify fundamental state + Assert.NotNull(agent); + Assert.NotNull(agent.Id); + Assert.False(agent.IsDeleted); + Assert.NotNull(agent.Definition); + Assert.Equal(sourceDefinition.ModelName, agent.Definition.ModelName); + + // Verify core properties + Assert.Equal(sourceDefinition.Instructions ?? string.Empty, agent.Instructions); + Assert.Equal(sourceDefinition.Name ?? string.Empty, agent.Name); + Assert.Equal(sourceDefinition.Description ?? string.Empty, agent.Description); + + // Verify options + Assert.Equal(sourceDefinition.Temperature, agent.Definition.Temperature); + Assert.Equal(sourceDefinition.TopP, agent.Definition.TopP); + Assert.Equal(sourceDefinition.ExecutionOptions?.MaxCompletionTokens, agent.Definition.ExecutionOptions?.MaxCompletionTokens); + Assert.Equal(sourceDefinition.ExecutionOptions?.MaxPromptTokens, agent.Definition.ExecutionOptions?.MaxPromptTokens); + Assert.Equal(sourceDefinition.ExecutionOptions?.ParallelToolCallsEnabled, agent.Definition.ExecutionOptions?.ParallelToolCallsEnabled); + Assert.Equal(sourceDefinition.ExecutionOptions?.TruncationMessageCount, agent.Definition.ExecutionOptions?.TruncationMessageCount); + + // Verify tool definitions + int expectedToolCount = 0; + + bool hasCodeIterpreter = false; + if (sourceDefinition.EnableCodeInterpreter) + { + hasCodeIterpreter = true; + ++expectedToolCount; + } + + Assert.Equal(hasCodeIterpreter, agent.Tools.OfType().Any()); + + bool hasFileSearch = false; + if (!string.IsNullOrWhiteSpace(sourceDefinition.VectorStoreId)) + { + hasFileSearch = true; + ++expectedToolCount; + } + + Assert.Equal(hasFileSearch, agent.Tools.OfType().Any()); + + Assert.Equal(expectedToolCount, agent.Tools.Count); + + // Verify metadata + Assert.NotNull(agent.Definition.Metadata); + if (sourceDefinition.ExecutionOptions == null) + { + Assert.Equal(sourceDefinition.Metadata ?? new Dictionary(), agent.Definition.Metadata); + } + else // Additional metadata present when execution options are defined + { + Assert.Equal((sourceDefinition.Metadata?.Count ?? 0) + 1, agent.Definition.Metadata.Count); + + if (sourceDefinition.Metadata != null) + { + foreach (var (key, value) in sourceDefinition.Metadata) + { + string? targetValue = agent.Definition.Metadata[key]; + Assert.NotNull(targetValue); + Assert.Equal(value, targetValue); + } + } + } + + // Verify detail definition + Assert.Equal(sourceDefinition.VectorStoreId, agent.Definition.VectorStoreId); + Assert.Equal(sourceDefinition.CodeInterpterFileIds, agent.Definition.CodeInterpterFileIds); + + } + private Task CreateAgentAsync() { OpenAIAssistantDefinition definition = @@ -440,7 +586,7 @@ private Task CreateAgentAsync() ModelName = "testmodel", }; - this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentSimple); + this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentPayload(definition)); return OpenAIAssistantAgent.CreateAsync( @@ -486,62 +632,114 @@ public void MyFunction(int index) private static class ResponseContent { - public const string CreateAgentSimple = - """ + public static string CreateAgentPayload(OpenAIAssistantDefinition definition) + { + StringBuilder builder = new(); + builder.AppendLine("{"); + builder.AppendLine(@" ""id"": ""asst_abc123"","); + builder.AppendLine(@" ""object"": ""assistant"","); + builder.AppendLine(@" ""created_at"": 1698984975,"); + builder.AppendLine(@$" ""name"": ""{definition.Name}"","); + builder.AppendLine(@$" ""description"": ""{definition.Description}"","); + builder.AppendLine(@$" ""instructions"": ""{definition.Instructions}"","); + builder.AppendLine(@$" ""model"": ""{definition.ModelName}"","); + + bool hasCodeInterpeter = definition.EnableCodeInterpreter; + bool hasCodeInterpeterFiles = (definition.CodeInterpterFileIds?.Count ?? 0) > 0; + bool hasFileSearch = !string.IsNullOrWhiteSpace(definition.VectorStoreId); + if (!hasCodeInterpeter && !hasFileSearch) { - "id": "asst_abc123", - "object": "assistant", - "created_at": 1698984975, - "name": null, - "description": null, - "model": "gpt-4-turbo", - "instructions": null, - "tools": [], - "file_ids": [], - "metadata": {} + builder.AppendLine(@" ""tools"": [],"); } - """; + else + { + builder.AppendLine(@" ""tools"": ["); - public const string CreateAgentFull = - """ + if (hasCodeInterpeter) + { + builder.Append(@$" {{ ""type"": ""code_interpreter"" }}{(hasFileSearch ? "," : string.Empty)}"); + } + + if (hasFileSearch) + { + builder.AppendLine(@" { ""type"": ""file_search"" }"); + } + + builder.AppendLine(" ],"); + } + + if (!hasCodeInterpeterFiles && !hasFileSearch) { - "id": "asst_abc123", - "object": "assistant", - "created_at": 1698984975, - "name": "testname", - "description": "testdescription", - "model": "gpt-4-turbo", - "instructions": "testinstructions", - "tools": [], - "file_ids": [], - "metadata": {} + builder.AppendLine(@" ""tool_resources"": {},"); } - """; + else + { + builder.AppendLine(@" ""tool_resources"": {"); - public const string CreateAgentWithEverything = - """ + if (hasCodeInterpeterFiles) + { + string fileIds = string.Join(",", definition.CodeInterpterFileIds!.Select(fileId => "\"" + fileId + "\"")); + builder.AppendLine(@$" ""code_interpreter"": {{ ""file_ids"": [{fileIds}] }}{(hasFileSearch ? "," : string.Empty)}"); + } + + if (hasFileSearch) + { + builder.AppendLine(@$" ""file_search"": {{ ""vector_store_ids"": [""{definition.VectorStoreId}""] }}"); + } + + builder.AppendLine(" },"); + } + + if (definition.Temperature.HasValue) { - "id": "asst_abc123", - "object": "assistant", - "created_at": 1698984975, - "name": null, - "description": null, - "model": "gpt-4-turbo", - "instructions": null, - "tools": [ + builder.AppendLine(@$" ""temperature"": {definition.Temperature},"); + } + + if (definition.TopP.HasValue) + { + builder.AppendLine(@$" ""top_p"": {definition.TopP},"); + } + + bool hasExecutionOptions = definition.ExecutionOptions != null; + int metadataCount = (definition.Metadata?.Count ?? 0); + if (metadataCount == 0 && !hasExecutionOptions) + { + builder.AppendLine(@" ""metadata"": {}"); + } + else + { + int index = 0; + builder.AppendLine(@" ""metadata"": {"); + + if (hasExecutionOptions) { - "type": "code_interpreter" - }, + string serializedExecutionOptions = JsonSerializer.Serialize(definition.ExecutionOptions); + builder.AppendLine(@$" ""{OpenAIAssistantAgent.OptionsMetadataKey}"": ""{JsonEncodedText.Encode(serializedExecutionOptions)}""{(metadataCount > 0 ? "," : string.Empty)}"); + } + + if (metadataCount > 0) { - "type": "file_search" + foreach (var (key, value) in definition.Metadata!) + { + builder.AppendLine(@$" ""{key}"": ""{value}""{(index < metadataCount - 1 ? "," : string.Empty)}"); + ++index; + } } - ], + + builder.AppendLine(" }"); + } + + builder.AppendLine("}"); + + return builder.ToString(); + } + + public const string CreateAgentWithEverything = + """ + { "tool_resources": { - "file_search": { - "vector_store_ids": ["#vs"] - } + "file_search": { "vector_store_ids": ["#vs"] } }, - "metadata": {"a": "1"} } """; From 8ca0a9dbc391131a14c1109f14a6fdb3338ccd61 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 24 Jul 2024 17:11:44 -0700 Subject: [PATCH 060/121] More tests + typo fix --- .../OpenAI/OpenAIAssistantAgentTests.cs | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index 9c1c789fa8e2..c83d69b560d0 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -284,6 +284,28 @@ public async Task VerifyOpenAIAssistantAgentDeleteAsync() await agent.DeleteAsync(); // Doesn't throw Assert.True(agent.IsDeleted); + + await Assert.ThrowsAsync(() => agent.AddChatMessageAsync("threadid", new(AuthorRole.User, "test"))); + await Assert.ThrowsAsync(() => agent.InvokeAsync("threadid").ToArrayAsync().AsTask()); + } + + /// + /// Verify the deletion of agent via . + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreateThreadAsync() + { + OpenAIAssistantAgent agent = await this.CreateAgentAsync(); + + this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateThread); + + string threadId = await agent.CreateThreadAsync(); + Assert.NotNull(threadId); + + this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateThread); + + threadId = await agent.CreateThreadAsync(new()); + Assert.NotNull(threadId); } /// @@ -644,10 +666,10 @@ public static string CreateAgentPayload(OpenAIAssistantDefinition definition) builder.AppendLine(@$" ""instructions"": ""{definition.Instructions}"","); builder.AppendLine(@$" ""model"": ""{definition.ModelName}"","); - bool hasCodeInterpeter = definition.EnableCodeInterpreter; - bool hasCodeInterpeterFiles = (definition.CodeInterpterFileIds?.Count ?? 0) > 0; + bool hasCodeInterpreter = definition.EnableCodeInterpreter; + bool hasCodeInterpreterFiles = (definition.CodeInterpterFileIds?.Count ?? 0) > 0; bool hasFileSearch = !string.IsNullOrWhiteSpace(definition.VectorStoreId); - if (!hasCodeInterpeter && !hasFileSearch) + if (!hasCodeInterpreter && !hasFileSearch) { builder.AppendLine(@" ""tools"": [],"); } @@ -655,7 +677,7 @@ public static string CreateAgentPayload(OpenAIAssistantDefinition definition) { builder.AppendLine(@" ""tools"": ["); - if (hasCodeInterpeter) + if (hasCodeInterpreter) { builder.Append(@$" {{ ""type"": ""code_interpreter"" }}{(hasFileSearch ? "," : string.Empty)}"); } @@ -668,7 +690,7 @@ public static string CreateAgentPayload(OpenAIAssistantDefinition definition) builder.AppendLine(" ],"); } - if (!hasCodeInterpeterFiles && !hasFileSearch) + if (!hasCodeInterpreterFiles && !hasFileSearch) { builder.AppendLine(@" ""tool_resources"": {},"); } @@ -676,7 +698,7 @@ public static string CreateAgentPayload(OpenAIAssistantDefinition definition) { builder.AppendLine(@" ""tool_resources"": {"); - if (hasCodeInterpeterFiles) + if (hasCodeInterpreterFiles) { string fileIds = string.Join(",", definition.CodeInterpterFileIds!.Select(fileId => "\"" + fileId + "\"")); builder.AppendLine(@$" ""code_interpreter"": {{ ""file_ids"": [{fileIds}] }}{(hasFileSearch ? "," : string.Empty)}"); From bd56cd9c62e0d6448bf512e24a1b780a59e43209 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 24 Jul 2024 17:12:22 -0700 Subject: [PATCH 061/121] Blank line --- dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index c83d69b560d0..ddbace38d4d1 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -597,7 +597,6 @@ private static void ValidateAgentDefinition(OpenAIAssistantAgent agent, OpenAIAs // Verify detail definition Assert.Equal(sourceDefinition.VectorStoreId, agent.Definition.VectorStoreId); Assert.Equal(sourceDefinition.CodeInterpterFileIds, agent.Definition.CodeInterpterFileIds); - } private Task CreateAgentAsync() From 51ffe3aef0c2af53a55852a2ac41254344a5c21a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 24 Jul 2024 17:13:36 -0700 Subject: [PATCH 062/121] Typo --- .../Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index ddbace38d4d1..c61cc99093e4 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -553,14 +553,14 @@ private static void ValidateAgentDefinition(OpenAIAssistantAgent agent, OpenAIAs // Verify tool definitions int expectedToolCount = 0; - bool hasCodeIterpreter = false; + bool hasCodeInterpreter = false; if (sourceDefinition.EnableCodeInterpreter) { - hasCodeIterpreter = true; + hasCodeInterpreter = true; ++expectedToolCount; } - Assert.Equal(hasCodeIterpreter, agent.Tools.OfType().Any()); + Assert.Equal(hasCodeInterpreter, agent.Tools.OfType().Any()); bool hasFileSearch = false; if (!string.IsNullOrWhiteSpace(sourceDefinition.VectorStoreId)) From ef92c71dcbf5b5e72144847ad9626270f0319f9a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 25 Jul 2024 09:13:27 -0700 Subject: [PATCH 063/121] Update based on merge and PR input --- .../ChatCompletion_FunctionTermination.cs | 5 ++- .../Concepts/Agents/MixedChat_Agents.cs | 2 +- .../Agents/OpenAIAssistant_ChartMaker.cs | 2 +- .../Agents/OpenAIAssistant_CodeInterpreter.cs | 2 +- .../OpenAIAssistant_FileManipulation.cs | 2 +- .../Agents/OpenAIAssistant_FileSearch.cs | 2 +- .../GettingStartedWithAgents/Step2_Plugins.cs | 3 +- .../Step8_OpenAIAssistant.cs | 2 +- dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj | 1 - .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 4 +-- .../OpenAI/OpenAIAssistantDefinition.cs | 2 +- .../Agents/UnitTests/Agents.UnitTests.csproj | 1 + .../OpenAI/OpenAIAssistantAgentTests.cs | 32 +++++++++---------- .../OpenAI/OpenAIAssistantDefinitionTests.cs | 6 ++-- .../Agents/ChatCompletionAgentTests.cs | 2 +- .../Agents/OpenAIAssistantAgentTests.cs | 4 +-- .../IntegrationTests/IntegrationTests.csproj | 4 +-- 17 files changed, 37 insertions(+), 39 deletions(-) rename dotnet/src/{IntegrationTestsV2 => IntegrationTests}/Agents/ChatCompletionAgentTests.cs (98%) rename dotnet/src/{IntegrationTestsV2 => IntegrationTests}/Agents/OpenAIAssistantAgentTests.cs (97%) diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs index f90f38587131..f344dae432b9 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs @@ -3,7 +3,6 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.AzureOpenAI; using Microsoft.SemanticKernel.Connectors.OpenAI; namespace Agents; @@ -23,7 +22,7 @@ public async Task UseAutoFunctionInvocationFilterWithAgentInvocationAsync() { Instructions = "Answer questions about the menu.", Kernel = CreateKernelWithChatCompletion(), - ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, + ExecutionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, }; KernelPlugin plugin = KernelPluginFactory.CreateFromType(); @@ -76,7 +75,7 @@ public async Task UseAutoFunctionInvocationFilterWithAgentChatAsync() { Instructions = "Answer questions about the menu.", Kernel = CreateKernelWithChatCompletion(), - ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, + ExecutionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, }; KernelPlugin plugin = KernelPluginFactory.CreateFromType(); diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs index 20769fa030b7..91add34e8693 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs @@ -52,7 +52,7 @@ await OpenAIAssistantAgent.CreateAsync( { Instructions = CopyWriterInstructions, Name = CopyWriterName, - ModelName = this.Model, + ModelId = this.Model, }); // Create a chat for agent interaction. diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index ce1f05a8b08b..531e47b8ec0b 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -33,7 +33,7 @@ await OpenAIAssistantAgent.CreateAsync( Instructions = AgentInstructions, Name = AgentName, EnableCodeInterpreter = true, - ModelName = this.Model, + ModelId = this.Model, }); // Create a chat for agent interaction. diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs index b3090007e0e4..eb5169f40b3f 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs @@ -24,7 +24,7 @@ await OpenAIAssistantAgent.CreateAsync( new() { EnableCodeInterpreter = true, // Enable code-interpreter - ModelName = this.Model, + ModelId = this.Model, }); // Create a chat for agent interaction. diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index ff4f26a92ba6..3de5b2d4f3ff 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -41,7 +41,7 @@ await OpenAIAssistantAgent.CreateAsync( { CodeInterpterFileIds = [uploadFile.Id], EnableCodeInterpreter = true, // Enable code-interpreter - ModelName = this.Model, + ModelId = this.Model, }); // Create a chat for agent interaction. diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs index 9fe775e0b50e..550615c6bf3e 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs @@ -47,7 +47,7 @@ await OpenAIAssistantAgent.CreateAsync( config, new() { - ModelName = this.Model, + ModelId = this.Model, VectorStoreId = vectorStore.Id, }); diff --git a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs b/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs index eb5ed86a77e3..38741bbb2e7c 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs @@ -3,7 +3,6 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.AzureOpenAI; using Microsoft.SemanticKernel.Connectors.OpenAI; namespace GettingStarted; @@ -27,7 +26,7 @@ public async Task UseChatCompletionWithPluginAgentAsync() Instructions = HostInstructions, Name = HostName, Kernel = this.CreateKernelWithChatCompletion(), - ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, + ExecutionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, }; // Initialize plugin and add to the agent's Kernel (same as direct Kernel usage). diff --git a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs index 964e9671eda2..5e5aa604a77a 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs @@ -31,7 +31,7 @@ await OpenAIAssistantAgent.CreateAsync( { Instructions = HostInstructions, Name = HostName, - ModelName = this.Model, + ModelId = this.Model, }); // Initialize plugin and add to the agent's Kernel (same as direct Kernel usage). diff --git a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj index 94c914c88eba..fa73edc77cde 100644 --- a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj +++ b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj @@ -29,7 +29,6 @@ - diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 164eceb5aff5..17ce8d4e1685 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -68,7 +68,7 @@ public static async Task CreateAsync( // Create the assistant AssistantCreationOptions assistantCreationOptions = CreateAssistantCreationOptions(definition); - Assistant model = await client.CreateAssistantAsync(definition.ModelName, assistantCreationOptions, cancellationToken).ConfigureAwait(false); + Assistant model = await client.CreateAssistantAsync(definition.ModelId, assistantCreationOptions, cancellationToken).ConfigureAwait(false); // Instantiate the agent return @@ -311,7 +311,7 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod CodeInterpterFileIds = fileIds, EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), Metadata = model.Metadata, - ModelName = model.Model, + ModelId = model.Model, EnableJsonResponse = enableJsonResponse, TopP = model.NucleusSamplingFactor, Temperature = model.Temperature, diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index ae66aab1502b..d16ff4dbb091 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -11,7 +11,7 @@ public sealed class OpenAIAssistantDefinition /// /// Identifies the AI model targeted by the agent. /// - public string ModelName { get; init; } = string.Empty; + public string ModelId { get; init; } = string.Empty; /// /// The description of the assistant. diff --git a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj index 96938cff3129..6b9fea49fde2 100644 --- a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj +++ b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj @@ -32,6 +32,7 @@ + diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index c61cc99093e4..2bf7a8e2dd1c 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -35,7 +35,7 @@ public async Task VerifyOpenAIAssistantAgentCreationEmptyAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", }; await this.VerifyAgentCreationAsync(definition); @@ -51,7 +51,7 @@ public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", Name = "testname", Description = "testdescription", Instructions = "testinstructions", @@ -70,7 +70,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", EnableCodeInterpreter = true, }; @@ -87,7 +87,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterFilesAsyn OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", EnableCodeInterpreter = true, CodeInterpterFileIds = ["file1", "file2"], }; @@ -105,7 +105,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithVectorStoreAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", VectorStoreId = "#vs1", }; @@ -122,7 +122,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithMetadataAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", Metadata = new Dictionary() { { "a", "1" }, @@ -143,7 +143,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithJsonResponseAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", EnableJsonResponse = true, }; @@ -160,7 +160,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithTemperatureAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", Temperature = 2.0F, }; @@ -177,7 +177,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithTopPAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", TopP = 2.0F, }; @@ -194,7 +194,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAsy OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", ExecutionOptions = new(), }; @@ -211,7 +211,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithExecutionOptionsAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", ExecutionOptions = new() { @@ -233,7 +233,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAnd OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", ExecutionOptions = new(), Metadata = new Dictionary() { @@ -254,7 +254,7 @@ public async Task VerifyOpenAIAssistantAgentRetrievalAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", }; this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentPayload(definition)); @@ -535,7 +535,7 @@ private static void ValidateAgentDefinition(OpenAIAssistantAgent agent, OpenAIAs Assert.NotNull(agent.Id); Assert.False(agent.IsDeleted); Assert.NotNull(agent.Definition); - Assert.Equal(sourceDefinition.ModelName, agent.Definition.ModelName); + Assert.Equal(sourceDefinition.ModelId, agent.Definition.ModelId); // Verify core properties Assert.Equal(sourceDefinition.Instructions ?? string.Empty, agent.Instructions); @@ -604,7 +604,7 @@ private Task CreateAgentAsync() OpenAIAssistantDefinition definition = new() { - ModelName = "testmodel", + ModelId = "testmodel", }; this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentPayload(definition)); @@ -663,7 +663,7 @@ public static string CreateAgentPayload(OpenAIAssistantDefinition definition) builder.AppendLine(@$" ""name"": ""{definition.Name}"","); builder.AppendLine(@$" ""description"": ""{definition.Description}"","); builder.AppendLine(@$" ""instructions"": ""{definition.Instructions}"","); - builder.AppendLine(@$" ""model"": ""{definition.ModelName}"","); + builder.AppendLine(@$" ""model"": ""{definition.ModelId}"","); bool hasCodeInterpreter = definition.EnableCodeInterpreter; bool hasCodeInterpreterFiles = (definition.CodeInterpterFileIds?.Count ?? 0) > 0; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index bf38207c3026..32bab0ac0609 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -19,7 +19,7 @@ public void VerifyOpenAIAssistantDefinitionInitialState() OpenAIAssistantDefinition definition = new(); Assert.Equal(string.Empty, definition.Id); - Assert.Equal(string.Empty, definition.ModelName); + Assert.Equal(string.Empty, definition.ModelId); Assert.Null(definition.Name); Assert.Null(definition.Instructions); Assert.Null(definition.Description); @@ -44,7 +44,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() { Id = "testid", Name = "testname", - ModelName = "testmodel", + ModelId = "testmodel", Instructions = "testinstructions", Description = "testdescription", VectorStoreId = "#vs", @@ -66,7 +66,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Assert.Equal("testid", definition.Id); Assert.Equal("testname", definition.Name); - Assert.Equal("testmodel", definition.ModelName); + Assert.Equal("testmodel", definition.ModelId); Assert.Equal("testinstructions", definition.Instructions); Assert.Equal("testdescription", definition.Description); Assert.Equal("#vs", definition.VectorStoreId); diff --git a/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs b/dotnet/src/IntegrationTests/Agents/ChatCompletionAgentTests.cs similarity index 98% rename from dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs rename to dotnet/src/IntegrationTests/Agents/ChatCompletionAgentTests.cs index e6f06b766053..4d5302f5ae03 100644 --- a/dotnet/src/IntegrationTestsV2/Agents/ChatCompletionAgentTests.cs +++ b/dotnet/src/IntegrationTests/Agents/ChatCompletionAgentTests.cs @@ -13,7 +13,7 @@ using SemanticKernel.IntegrationTests.TestSettings; using Xunit; -namespace SemanticKernel.IntegrationTests.Agents.OpenAI; +namespace SemanticKernel.IntegrationTests.Agents; #pragma warning disable xUnit1004 // Contains test methods used in manual verification. Disable warning for this file only. diff --git a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs similarity index 97% rename from dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs rename to dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs index 7e34c4ebe65e..f7dee91db903 100644 --- a/dotnet/src/IntegrationTestsV2/Agents/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs @@ -11,7 +11,7 @@ using SemanticKernel.IntegrationTests.TestSettings; using Xunit; -namespace SemanticKernel.IntegrationTests.Agents.OpenAI; +namespace SemanticKernel.IntegrationTests.Agents; #pragma warning disable xUnit1004 // Contains test methods used in manual verification. Disable warning for this file only. @@ -79,7 +79,7 @@ await OpenAIAssistantAgent.CreateAsync( new() { Instructions = "Answer questions about the menu.", - ModelName = modelName, + ModelId = modelName, }); AgentGroupChat chat = new(); diff --git a/dotnet/src/IntegrationTests/IntegrationTests.csproj b/dotnet/src/IntegrationTests/IntegrationTests.csproj index 3adeaaffd2c1..6abbb8eb3020 100644 --- a/dotnet/src/IntegrationTests/IntegrationTests.csproj +++ b/dotnet/src/IntegrationTests/IntegrationTests.csproj @@ -62,8 +62,8 @@ - + + From afda06119bdc8aa9adf9f8d46b892e0e4f2c8f85 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 25 Jul 2024 09:30:41 -0700 Subject: [PATCH 064/121] Migrate new samples --- .../Concepts/Agents/MixedChat_Files.cs | 34 +++++++++++-------- .../Concepts/Agents/MixedChat_Images.cs | 21 +++++++----- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs index b95c6efca36d..52b8b1920afa 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs @@ -4,7 +4,7 @@ using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI.Files; using Resources; namespace Agents; @@ -25,13 +25,15 @@ public class MixedChat_Files(ITestOutputHelper output) : BaseTest(output) [Fact] public async Task AnalyzeFileAndGenerateReportAsync() { -#pragma warning disable CS0618 // Type or member is obsolete - OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); + OpenAIServiceConfiguration config = GetOpenAIConfiguration(); - OpenAIFileReference uploadFile = - await fileService.UploadContentAsync( - new BinaryContent(await EmbeddedResource.ReadAllAsync("30-user-context.txt"), mimeType: "text/plain"), - new OpenAIFileUploadExecutionSettings("30-user-context.txt", OpenAIFilePurpose.Assistants)); + FileClient fileClient = config.CreateFileClient(); + + OpenAIFileInfo uploadFile = + await fileClient.UploadFileAsync( + new BinaryData(await EmbeddedResource.ReadAllAsync("30-user-context.txt")), + "30-user-context.txt", + FileUploadPurpose.Assistants); Console.WriteLine(this.ApiKey); @@ -39,12 +41,12 @@ await fileService.UploadContentAsync( OpenAIAssistantAgent analystAgent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: new(this.ApiKey, this.Endpoint), + config, new() { EnableCodeInterpreter = true, // Enable code-interpreter ModelId = this.Model, - FileIds = [uploadFile.Id] // Associate uploaded file with assistant + CodeInterpterFileIds = [uploadFile.Id] // Associate uploaded file with assistant }); ChatCompletionAgent summaryAgent = @@ -71,7 +73,7 @@ Create a tab delimited file report of the ordered (descending) frequency distrib finally { await analystAgent.DeleteAsync(); - await fileService.DeleteFileAsync(uploadFile.Id); + await fileClient.DeleteFileAsync(uploadFile.Id); } // Local function to invoke agent and display the conversation messages. @@ -90,12 +92,16 @@ async Task InvokeAgentAsync(Agent agent, string? input = null) foreach (AnnotationContent annotation in content.Items.OfType()) { Console.WriteLine($"\t* '{annotation.Quote}' => {annotation.FileId}"); - BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!); - byte[] byteContent = fileContent.Data?.ToArray() ?? []; - Console.WriteLine($"\n{Encoding.Default.GetString(byteContent)}"); + BinaryData fileContent = await fileClient.DownloadFileAsync(annotation.FileId!); + Console.WriteLine($"\n{Encoding.Default.GetString(fileContent.ToArray())}"); } } } -#pragma warning restore CS0618 // Type or member is obsolete } + + private OpenAIServiceConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs index 36b96fc4be54..cfbcd97c8260 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs @@ -3,7 +3,7 @@ using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI.Files; namespace Agents; @@ -27,14 +27,15 @@ public class MixedChat_Images(ITestOutputHelper output) : BaseTest(output) [Fact] public async Task AnalyzeDataAndGenerateChartAsync() { -#pragma warning disable CS0618 // Type or member is obsolete - OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey); + OpenAIServiceConfiguration config = GetOpenAIConfiguration(); + + FileClient fileClient = config.CreateFileClient(); // Define the agents OpenAIAssistantAgent analystAgent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: new(this.ApiKey, this.Endpoint), + config, new() { Instructions = AnalystInstructions, @@ -101,14 +102,18 @@ async Task InvokeAgentAsync(Agent agent, string? input = null) foreach (FileReferenceContent fileReference in message.Items.OfType()) { Console.WriteLine($"\t* Generated image - @{fileReference.FileId}"); - BinaryContent fileContent = await fileService.GetFileContentAsync(fileReference.FileId!); - byte[] byteContent = fileContent.Data?.ToArray() ?? []; + BinaryData fileContent = await fileClient.DownloadFileAsync(fileReference.FileId!); string filePath = Path.ChangeExtension(Path.GetTempFileName(), ".png"); - await File.WriteAllBytesAsync($"{filePath}.png", byteContent); + await File.WriteAllBytesAsync($"{filePath}.png", fileContent.ToArray()); Console.WriteLine($"\t* Local path - {filePath}"); } } } -#pragma warning restore CS0618 // Type or member is obsolete } + + private OpenAIServiceConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } From ea1fa9270311597904926364daf7ab85c116f229 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 25 Jul 2024 10:02:32 -0700 Subject: [PATCH 065/121] Legacy fix --- .../Experimental/Agents/Internal/ChatRun.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Experimental/Agents/Internal/ChatRun.cs b/dotnet/src/Experimental/Agents/Internal/ChatRun.cs index 1928f219c903..218ef3e3ddfc 100644 --- a/dotnet/src/Experimental/Agents/Internal/ChatRun.cs +++ b/dotnet/src/Experimental/Agents/Internal/ChatRun.cs @@ -163,13 +163,12 @@ private IEnumerable> ExecuteStep(ThreadRunStepModel step, private async Task ProcessFunctionStepAsync(string callId, ThreadRunStepModel.FunctionDetailsModel functionDetails, CancellationToken cancellationToken) { var result = await InvokeFunctionCallAsync().ConfigureAwait(false); - var toolResult = result as string ?? JsonSerializer.Serialize(result); return new ToolResultModel { CallId = callId, - Output = toolResult!, + Output = ParseFunctionResult(result), }; async Task InvokeFunctionCallAsync() @@ -191,4 +190,19 @@ async Task InvokeFunctionCallAsync() return result.GetValue() ?? string.Empty; } } + + private static string ParseFunctionResult(object result) + { + if (result is string stringResult) + { + return stringResult; + } + + if (result is ChatMessageContent messageResult) + { + return messageResult.Content ?? JsonSerializer.Serialize(messageResult); + } + + return JsonSerializer.Serialize(result); + } } From 259b769d92a5c789099aa518459695a87a3b13ee Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 25 Jul 2024 10:23:03 -0700 Subject: [PATCH 066/121] Exception message --- dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs index 4474da62115f..97c3cc978f68 100644 --- a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs @@ -42,7 +42,7 @@ public static OpenAIClient CreateClient(OpenAIServiceConfiguration config) return new AzureOpenAIClient(config.Endpoint, config.ApiKey!, clientOptions); } - throw new KernelException($"Unsupported configuration type: {config.Type}"); + throw new KernelException($"Unsupported configuration state: {config.Type}. No api-key or credential present."); } case OpenAIServiceConfiguration.OpenAIServiceType.OpenAI: { @@ -50,7 +50,7 @@ public static OpenAIClient CreateClient(OpenAIServiceConfiguration config) return new OpenAIClient(config.ApiKey ?? SingleSpaceKey, clientOptions); } default: - throw new KernelException($"Unsupported configuration state: {config.Type}"); + throw new KernelException($"Unsupported configuration type: {config.Type}"); } } From 20c70571380a97855ff75156c07ed8bbbce1bc5e Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 25 Jul 2024 13:06:27 -0700 Subject: [PATCH 067/121] Clean-up --- .../src/IntegrationTestsV2/testsettings.json | 97 ------------------- 1 file changed, 97 deletions(-) delete mode 100644 dotnet/src/IntegrationTestsV2/testsettings.json diff --git a/dotnet/src/IntegrationTestsV2/testsettings.json b/dotnet/src/IntegrationTestsV2/testsettings.json deleted file mode 100644 index 66df73f8b7a5..000000000000 --- a/dotnet/src/IntegrationTestsV2/testsettings.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "OpenAI": { - "ServiceId": "gpt-3.5-turbo-instruct", - "ModelId": "gpt-3.5-turbo-instruct", - "ApiKey": "" - }, - "AzureOpenAI": { - "ServiceId": "azure-gpt-35-turbo-instruct", - "DeploymentName": "gpt-35-turbo-instruct", - "ChatDeploymentName": "gpt-4", - "Endpoint": "", - "ApiKey": "" - }, - "OpenAIEmbeddings": { - "ServiceId": "text-embedding-ada-002", - "ModelId": "text-embedding-ada-002", - "ApiKey": "" - }, - "AzureOpenAIEmbeddings": { - "ServiceId": "azure-text-embedding-ada-002", - "DeploymentName": "ada-002", - "Endpoint": "", - "ApiKey": "" - }, - "OpenAITextToAudio": { - "ServiceId": "tts-1", - "ModelId": "tts-1", - "ApiKey": "" - }, - "AzureOpenAITextToAudio": { - "ServiceId": "azure-tts", - "DeploymentName": "tts", - "Endpoint": "", - "ApiKey": "" - }, - "OpenAIAudioToText": { - "ServiceId": "whisper-1", - "ModelId": "whisper-1", - "ApiKey": "" - }, - "AzureOpenAIAudioToText": { - "ServiceId": "azure-whisper", - "DeploymentName": "whisper", - "Endpoint": "", - "ApiKey": "" - }, - "HuggingFace": { - "ApiKey": "" - }, - "GoogleAI": { - "EmbeddingModelId": "embedding-001", - "ApiKey": "", - "Gemini": { - "ModelId": "gemini-1.5-flash", - "VisionModelId": "gemini-1.5-flash" - } - }, - "VertexAI": { - "EmbeddingModelId": "textembedding-gecko@003", - "BearerKey": "", - "Location": "us-central1", - "ProjectId": "", - "Gemini": { - "ModelId": "gemini-1.5-flash", - "VisionModelId": "gemini-1.5-flash" - } - }, - "Bing": { - "ApiKey": "" - }, - "Postgres": { - "ConnectionString": "" - }, - "MongoDB": { - "ConnectionString": "", - "VectorSearchCollection": "dotnetMSKNearestTest.nearestSearch" - }, - "AzureCosmosDB": { - "ConnectionString": "" - }, - "SqlServer": { - "ConnectionString": "" - }, - "Planners": { - "AzureOpenAI": { - "ServiceId": "azure-gpt-35-turbo", - "DeploymentName": "gpt-35-turbo", - "Endpoint": "", - "ApiKey": "" - }, - "OpenAI": { - "ServiceId": "openai-gpt-4", - "ModelId": "gpt-4", - "ApiKey": "" - } - } -} \ No newline at end of file From f419ac213c96ba11ab7bdd10e29eec2415ec82aa Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 31 Jul 2024 09:46:19 -0700 Subject: [PATCH 068/121] Clean-up --- .../Concepts/Agents/Legacy_AgentDelegation.cs | 2 +- .../Concepts/Resources/Plugins/MenuPlugin.cs | 34 ------------------- .../Step8_OpenAIAssistant.cs | 6 ---- 3 files changed, 1 insertion(+), 41 deletions(-) delete mode 100644 dotnet/samples/Concepts/Resources/Plugins/MenuPlugin.cs diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs index 08f7e99096f0..b4b0ed93199f 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs @@ -31,7 +31,7 @@ public async Task RunAsync() try { - var plugin = KernelPluginFactory.CreateFromType(); + var plugin = KernelPluginFactory.CreateFromType(); var menuAgent = Track( await new AgentBuilder() diff --git a/dotnet/samples/Concepts/Resources/Plugins/MenuPlugin.cs b/dotnet/samples/Concepts/Resources/Plugins/MenuPlugin.cs deleted file mode 100644 index be82177eda5d..000000000000 --- a/dotnet/samples/Concepts/Resources/Plugins/MenuPlugin.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.ComponentModel; -using Microsoft.SemanticKernel; - -namespace Plugins; - -public sealed class MenuPlugin -{ - public const string CorrelationIdArgument = "correlationId"; - - private readonly List _correlationIds = []; - - public IReadOnlyList CorrelationIds => this._correlationIds; - - [KernelFunction, Description("Provides a list of specials from the menu.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")] - public string GetSpecials() - { - return @" -Special Soup: Clam Chowder -Special Salad: Cobb Salad -Special Drink: Chai Tea -"; - } - - [KernelFunction, Description("Provides the price of the requested menu item.")] - public string GetItemPrice( - [Description("The name of the menu item.")] - string menuItem) - { - return "$9.99"; - } -} diff --git a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs index 5e5aa604a77a..dda6ea31df81 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs @@ -80,12 +80,6 @@ private OpenAIServiceConfiguration GetOpenAIConfiguration() private sealed class MenuPlugin { - public const string CorrelationIdArgument = "correlationId"; - - private readonly List _correlationIds = []; - - public IReadOnlyList CorrelationIds => this._correlationIds; - [KernelFunction, Description("Provides a list of specials from the menu.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")] public string GetSpecials() From cd24a45a1e441e90fd907debf4b943b7498ec8df Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 31 Jul 2024 09:58:08 -0700 Subject: [PATCH 069/121] Legacy clean-up --- .../samples/Concepts/Agents/Legacy_Agents.cs | 8 +----- .../Resources/Plugins/LegacyMenuPlugin.cs | 25 ------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/Legacy_Agents.cs b/dotnet/samples/Concepts/Agents/Legacy_Agents.cs index 0a03d4c10809..31cc4926392b 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_Agents.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_Agents.cs @@ -48,18 +48,12 @@ public async Task RunWithMethodFunctionsAsync() await ChatAsync( "Agents.ToolAgent.yaml", // Defined under ./Resources/Agents plugin, - arguments: new() { { LegacyMenuPlugin.CorrelationIdArgument, 3.141592653 } }, + arguments: null, "Hello", "What is the special soup?", "What is the special drink?", "Do you have enough soup for 5 orders?", "Thank you!"); - - Console.WriteLine("\nCorrelation Ids:"); - foreach (string correlationId in menuApi.CorrelationIds) - { - Console.WriteLine($"- {correlationId}"); - } } /// diff --git a/dotnet/samples/Concepts/Resources/Plugins/LegacyMenuPlugin.cs b/dotnet/samples/Concepts/Resources/Plugins/LegacyMenuPlugin.cs index 7111e873cf4c..c383ea9025f1 100644 --- a/dotnet/samples/Concepts/Resources/Plugins/LegacyMenuPlugin.cs +++ b/dotnet/samples/Concepts/Resources/Plugins/LegacyMenuPlugin.cs @@ -7,12 +7,6 @@ namespace Plugins; public sealed class LegacyMenuPlugin { - public const string CorrelationIdArgument = "correlationId"; - - private readonly List _correlationIds = []; - - public IReadOnlyList CorrelationIds => this._correlationIds; - /// /// Returns a mock item menu. /// @@ -20,8 +14,6 @@ public sealed class LegacyMenuPlugin [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")] public string[] GetSpecials(KernelArguments? arguments) { - CaptureCorrelationId(arguments, nameof(GetSpecials)); - return [ "Special Soup: Clam Chowder", @@ -39,8 +31,6 @@ public string GetItemPrice( string menuItem, KernelArguments? arguments) { - CaptureCorrelationId(arguments, nameof(GetItemPrice)); - return "$9.99"; } @@ -55,21 +45,6 @@ public bool IsItem86d( int count, KernelArguments? arguments) { - CaptureCorrelationId(arguments, nameof(IsItem86d)); - return count < 3; } - - private void CaptureCorrelationId(KernelArguments? arguments, string scope) - { - if (arguments?.TryGetValue(CorrelationIdArgument, out object? correlationId) ?? false) - { - string? correlationText = correlationId?.ToString(); - - if (!string.IsNullOrWhiteSpace(correlationText)) - { - this._correlationIds.Add($"{scope}:{correlationText}"); - } - } - } } From b4dfd7aaa12742bec9f97c7c313ef62c30781713 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 31 Jul 2024 10:59:11 -0700 Subject: [PATCH 070/121] Type fix --- dotnet/samples/Concepts/Agents/MixedChat_Files.cs | 2 +- .../Concepts/Agents/OpenAIAssistant_FileManipulation.cs | 2 +- .../src/Agents/OpenAI/Internal/AssistantThreadActions.cs | 2 +- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 4 ++-- dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs | 2 +- dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs | 2 +- .../Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs | 8 ++++---- .../UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs | 6 +++--- .../UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs | 6 +++--- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs index 52b8b1920afa..24742278f6e5 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs @@ -46,7 +46,7 @@ await OpenAIAssistantAgent.CreateAsync( { EnableCodeInterpreter = true, // Enable code-interpreter ModelId = this.Model, - CodeInterpterFileIds = [uploadFile.Id] // Associate uploaded file with assistant + CodeInterpreterFileIds = [uploadFile.Id] // Associate uploaded file with assistant }); ChatCompletionAgent summaryAgent = diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index 3de5b2d4f3ff..8a6c98bd3a38 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -39,7 +39,7 @@ await OpenAIAssistantAgent.CreateAsync( config, new() { - CodeInterpterFileIds = [uploadFile.Id], + CodeInterpreterFileIds = [uploadFile.Id], EnableCodeInterpreter = true, // Enable code-interpreter ModelId = this.Model, }); diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 1e393027e43d..acc7283fde4f 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -45,7 +45,7 @@ public static async Task CreateThreadAsync(AssistantClient client, OpenA ThreadCreationOptions createOptions = new() { - ToolResources = AssistantToolResourcesFactory.GenerateToolResources(options?.VectorStoreId, options?.CodeInterpterFileIds), + ToolResources = AssistantToolResourcesFactory.GenerateToolResources(options?.VectorStoreId, options?.CodeInterpreterFileIds), }; if (options?.Messages != null) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 17ce8d4e1685..f22167e9033f 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -308,7 +308,7 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod Name = model.Name, Description = model.Description, Instructions = model.Instructions, - CodeInterpterFileIds = fileIds, + CodeInterpreterFileIds = fileIds, EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), Metadata = model.Metadata, ModelId = model.Model, @@ -328,7 +328,7 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss Description = definition.Description, Instructions = definition.Instructions, Name = definition.Name, - ToolResources = AssistantToolResourcesFactory.GenerateToolResources(definition.VectorStoreId, definition.EnableCodeInterpreter ? definition.CodeInterpterFileIds : null), + ToolResources = AssistantToolResourcesFactory.GenerateToolResources(definition.VectorStoreId, definition.EnableCodeInterpreter ? definition.CodeInterpreterFileIds : null), ResponseFormat = definition.EnableJsonResponse ? AssistantResponseFormat.JsonObject : AssistantResponseFormat.Auto, Temperature = definition.Temperature, NucleusSamplingFactor = definition.TopP, diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index d16ff4dbb091..cb8cb6c84734 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -36,7 +36,7 @@ public sealed class OpenAIAssistantDefinition /// /// Optional file-ids made available to the code_interpreter tool, if enabled. /// - public IReadOnlyList? CodeInterpterFileIds { get; init; } + public IReadOnlyList? CodeInterpreterFileIds { get; init; } /// /// Set if code-interpreter is enabled. diff --git a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs index 4f596e000153..d2e8eb012e17 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs @@ -11,7 +11,7 @@ public sealed class OpenAIThreadCreationOptions /// /// Optional file-ids made available to the code_interpreter tool, if enabled. /// - public IReadOnlyList? CodeInterpterFileIds { get; init; } + public IReadOnlyList? CodeInterpreterFileIds { get; init; } /// /// Optional messages to initialize thread with.. diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index 2bf7a8e2dd1c..de99478eea51 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -89,7 +89,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterFilesAsyn { ModelId = "testmodel", EnableCodeInterpreter = true, - CodeInterpterFileIds = ["file1", "file2"], + CodeInterpreterFileIds = ["file1", "file2"], }; await this.VerifyAgentCreationAsync(definition); @@ -596,7 +596,7 @@ private static void ValidateAgentDefinition(OpenAIAssistantAgent agent, OpenAIAs // Verify detail definition Assert.Equal(sourceDefinition.VectorStoreId, agent.Definition.VectorStoreId); - Assert.Equal(sourceDefinition.CodeInterpterFileIds, agent.Definition.CodeInterpterFileIds); + Assert.Equal(sourceDefinition.CodeInterpreterFileIds, agent.Definition.CodeInterpreterFileIds); } private Task CreateAgentAsync() @@ -666,7 +666,7 @@ public static string CreateAgentPayload(OpenAIAssistantDefinition definition) builder.AppendLine(@$" ""model"": ""{definition.ModelId}"","); bool hasCodeInterpreter = definition.EnableCodeInterpreter; - bool hasCodeInterpreterFiles = (definition.CodeInterpterFileIds?.Count ?? 0) > 0; + bool hasCodeInterpreterFiles = (definition.CodeInterpreterFileIds?.Count ?? 0) > 0; bool hasFileSearch = !string.IsNullOrWhiteSpace(definition.VectorStoreId); if (!hasCodeInterpreter && !hasFileSearch) { @@ -699,7 +699,7 @@ public static string CreateAgentPayload(OpenAIAssistantDefinition definition) if (hasCodeInterpreterFiles) { - string fileIds = string.Join(",", definition.CodeInterpterFileIds!.Select(fileId => "\"" + fileId + "\"")); + string fileIds = string.Join(",", definition.CodeInterpreterFileIds!.Select(fileId => "\"" + fileId + "\"")); builder.AppendLine(@$" ""code_interpreter"": {{ ""file_ids"": [{fileIds}] }}{(hasFileSearch ? "," : string.Empty)}"); } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index 32bab0ac0609..a91a043febfb 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -28,7 +28,7 @@ public void VerifyOpenAIAssistantDefinitionInitialState() Assert.Null(definition.Temperature); Assert.Null(definition.TopP); Assert.Null(definition.VectorStoreId); - Assert.Null(definition.CodeInterpterFileIds); + Assert.Null(definition.CodeInterpreterFileIds); Assert.False(definition.EnableCodeInterpreter); Assert.False(definition.EnableJsonResponse); } @@ -59,7 +59,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() ParallelToolCallsEnabled = false, TruncationMessageCount = 12, }, - CodeInterpterFileIds = ["file1"], + CodeInterpreterFileIds = ["file1"], EnableCodeInterpreter = true, EnableJsonResponse = true, }; @@ -78,7 +78,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Assert.Equal(12, definition.ExecutionOptions.TruncationMessageCount); Assert.False(definition.ExecutionOptions.ParallelToolCallsEnabled); Assert.Single(definition.Metadata); - Assert.Single(definition.CodeInterpterFileIds); + Assert.Single(definition.CodeInterpreterFileIds); Assert.True(definition.EnableCodeInterpreter); Assert.True(definition.EnableJsonResponse); } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs index ba4992304227..d4e680efee09 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs @@ -23,7 +23,7 @@ public void OpenAIThreadCreationOptionsInitialState() Assert.Null(options.Messages); Assert.Null(options.Metadata); Assert.Null(options.VectorStoreId); - Assert.Null(options.CodeInterpterFileIds); + Assert.Null(options.CodeInterpreterFileIds); } /// @@ -38,12 +38,12 @@ public void OpenAIThreadCreationOptionsAssignment() Messages = [new ChatMessageContent(AuthorRole.User, "test")], VectorStoreId = "#vs", Metadata = new Dictionary() { { "a", "1" } }, - CodeInterpterFileIds = ["file1"], + CodeInterpreterFileIds = ["file1"], }; Assert.Single(definition.Messages); Assert.Single(definition.Metadata); Assert.Equal("#vs", definition.VectorStoreId); - Assert.Single(definition.CodeInterpterFileIds); + Assert.Single(definition.CodeInterpreterFileIds); } } From e2d34081f5b6269626970d2d61b002eea2229b6a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 31 Jul 2024 11:03:38 -0700 Subject: [PATCH 071/121] Remove friend relationship to concepts --- dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj index fa73edc77cde..a5a4cde76d6f 100644 --- a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj +++ b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj @@ -38,7 +38,6 @@ - From d68b567cd68130054f243340f08948885242b2bc Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 31 Jul 2024 11:33:55 -0700 Subject: [PATCH 072/121] Update CharMaker sample to download and view images --- .../Agents/OpenAIAssistant_ChartMaker.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index 531e47b8ec0b..bef11e52bd6d 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -1,8 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Diagnostics; +using System.Threading; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI.Files; namespace Agents; @@ -23,11 +26,15 @@ public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseTest(out [Fact] public async Task GenerateChartWithOpenAIAssistantAgentAsync() { + OpenAIServiceConfiguration config = GetOpenAIConfiguration(); + + FileClient fileClient = config.CreateFileClient(); + // Define the agent OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: GetOpenAIConfiguration(), + config, new() { Instructions = AgentInstructions, @@ -55,6 +62,7 @@ Sum 426 1622 856 2904 """); await InvokeAgentAsync("Can you regenerate this same chart using the category names as the bar colors?"); + await InvokeAgentAsync("Perfect, can you regenerate this as a line chart?"); } finally { @@ -78,9 +86,30 @@ async Task InvokeAgentAsync(string input) foreach (var fileReference in message.Items.OfType()) { Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: @{fileReference.FileId}"); + + string downloadPath = await DownloadFileContentAsync(fileReference.FileId); + + Console.WriteLine($"# {message.Role}: @{fileReference.FileId} downloaded to {downloadPath}"); } } } + + async Task DownloadFileContentAsync(string fileId) + { + string filePath = Path.Combine(Environment.CurrentDirectory, $"{fileId}.jpg"); + BinaryData content = await fileClient.DownloadFileAsync(fileId); + await using var outputStream = File.OpenWrite(filePath); + await outputStream.WriteAsync(content.ToArray()); + + Process.Start( + new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = $"/C start {filePath}" + }); + + return filePath; + } } private OpenAIServiceConfiguration GetOpenAIConfiguration() From 475a9f6fb4e8d9f823af90e964a15b34dcf314d1 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 31 Jul 2024 11:36:43 -0700 Subject: [PATCH 073/121] Namespace --- dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index bef11e52bd6d..275cd8bcf36b 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. using System.Diagnostics; -using System.Threading; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; From 844cef9cda4a873d52bf4a8de69052d53190299d Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 31 Jul 2024 11:48:55 -0700 Subject: [PATCH 074/121] Param comment --- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index f22167e9033f..f54129096618 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -188,7 +188,7 @@ public IAsyncEnumerable GetThreadMessagesAsync(string thread /// /// Delete the assistant definition. /// - /// + /// The to monitor for cancellation requests. The default is . /// True if assistant definition has been deleted /// /// Assistant based agent will not be useable after deletion. From dac78fd465abe04715e5d0fa2b0b9d67c837d739 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 1 Aug 2024 14:36:19 -0700 Subject: [PATCH 075/121] PR comments --- dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs | 3 +-- dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs index 62c477dc57d3..d40755101309 100644 --- a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs +++ b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs @@ -62,8 +62,7 @@ async Task InvokeAgentAsync(IAgentThread thread, string imageName, string questi Console.WriteLine($"# {message.Role}: {fileId}"); Console.WriteLine($"# {message.Role}: {path}"); BinaryData content = await fileClient.DownloadFileAsync(fileId); - await using var outputStream = File.OpenWrite(filename); - await outputStream.WriteAsync(content.ToArray()); + File.WriteAllBytes(filename, content.ToArray()); Process.Start( new ProcessStartInfo { diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index 275cd8bcf36b..4c2ea0c59534 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -97,8 +97,7 @@ async Task DownloadFileContentAsync(string fileId) { string filePath = Path.Combine(Environment.CurrentDirectory, $"{fileId}.jpg"); BinaryData content = await fileClient.DownloadFileAsync(fileId); - await using var outputStream = File.OpenWrite(filePath); - await outputStream.WriteAsync(content.ToArray()); + File.WriteAllBytes(filePath, content.ToArray()); Process.Start( new ProcessStartInfo From 03f245689d1eec81c519e11eaacb49c43b034e41 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 1 Aug 2024 14:47:44 -0700 Subject: [PATCH 076/121] Simplify --- dotnet/src/IntegrationTests/Agents/ChatCompletionAgentTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/IntegrationTests/Agents/ChatCompletionAgentTests.cs b/dotnet/src/IntegrationTests/Agents/ChatCompletionAgentTests.cs index 4d5302f5ae03..32985c457fc3 100644 --- a/dotnet/src/IntegrationTests/Agents/ChatCompletionAgentTests.cs +++ b/dotnet/src/IntegrationTests/Agents/ChatCompletionAgentTests.cs @@ -60,7 +60,7 @@ public async Task AzureChatCompletionAgentAsync(string input, string expectedAns { Kernel = kernel, Instructions = "Answer questions about the menu.", - ExecutionSettings = new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, + ExecutionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, }; AgentGroupChat chat = new(); From 197a7e306bcb6d7034e8d55713b9669cd0d0618e Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 2 Aug 2024 06:38:07 -0700 Subject: [PATCH 077/121] Update dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- .../UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs index 5b13a584deb0..f624dd62152b 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs @@ -27,7 +27,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsDefault() // Create options MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); - // Validate + // Assert Assert.NotNull(options); Assert.Empty(options.Metadata); } From a1848181bb5199fd1f7cbf1c0dadb294a7aa3ad9 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 2 Aug 2024 06:43:58 -0700 Subject: [PATCH 078/121] Update dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- .../Extensions/OpenAIServiceConfigurationExtensions.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs index 29e5e36c6f4c..4f2baa1acde5 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs @@ -16,11 +16,7 @@ public static class OpenAIServiceConfigurationExtensions /// /// The configuration public static FileClient CreateFileClient(this OpenAIServiceConfiguration configuration) - { - OpenAIClient client = OpenAIClientFactory.CreateClient(configuration); - - return client.GetFileClient(); - } + => OpenAIClientFactory.CreateClient(configuration).GetFileClient(); /// /// Provide a newly created based on the specified configuration. From 042ab642f380a445a057a9571d525dd3e942dd33 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 2 Aug 2024 06:46:11 -0700 Subject: [PATCH 079/121] Cosmetic: Maximize single line code complexity and eliminate potential break-point. --- .../Extensions/OpenAIServiceConfigurationExtensions.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs index 4f2baa1acde5..cf31d0c1c876 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs @@ -23,9 +23,5 @@ public static FileClient CreateFileClient(this OpenAIServiceConfiguration config /// /// The configuration public static VectorStoreClient CreateVectorStoreClient(this OpenAIServiceConfiguration configuration) - { - OpenAIClient client = OpenAIClientFactory.CreateClient(configuration); - - return client.GetVectorStoreClient(); - } + => OpenAIClientFactory.CreateClient(configuration).GetVectorStoreClient(); } From 2a4baec55839a0a8a08b184c7e374dae55371b4f Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 2 Aug 2024 06:46:24 -0700 Subject: [PATCH 080/121] Namespace --- .../OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs index cf31d0c1c876..9b3a55b9e6fe 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. using Microsoft.SemanticKernel.Agents.OpenAI.Internal; -using OpenAI; using OpenAI.Files; using OpenAI.VectorStores; From ae47cbf6ea09a53def0034bd1cda976ef0da047c Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 2 Aug 2024 06:49:05 -0700 Subject: [PATCH 081/121] Remove silent failure --- .../OpenAI/Internal/AssistantMessageFactory.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs index 0814d9720ecf..eca1cab2c590 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs @@ -50,15 +50,15 @@ public static IEnumerable GetMessageContents(ChatMessageContent { yield return MessageContent.FromImageUrl(imageContent.Uri); } - //else if (string.IsNullOrWhiteSpace(imageContent.DataUri)) - //{ - // SDK BUG - BAD SIGNATURE (https://github.com/openai/openai-dotnet/issues/135) - // URI does not accept the format used for `DataUri` - // Approach is inefficient anyway... - // yield return MessageContent.FromImageUrl(new Uri(imageContent.DataUri!)); - //} - } - else if (content is FileReferenceContent fileContent) + else if (string.IsNullOrWhiteSpace(imageContent.DataUri)) + { + //SDK BUG - BAD SIGNATURE (https://github.com/openai/openai-dotnet/issues/135) + // URI does not accept the format used for `DataUri` + // Approach is inefficient anyway... + //yield return MessageContent.FromImageUrl(new Uri(imageContent.DataUri!)); + throw new KernelException($"{nameof(ImageContent.DataUri)} not supported for assistant input."); + } + else if (content is FileReferenceContent fileContent) { yield return MessageContent.FromImageFileId(fileContent.FileId); } From bd55f03f6d4ca5646e2d4ff918a10319cf5e8ded Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 2 Aug 2024 06:54:02 -0700 Subject: [PATCH 082/121] Braces --- dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs index eca1cab2c590..3d627a2e3e01 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs @@ -59,8 +59,9 @@ public static IEnumerable GetMessageContents(ChatMessageContent throw new KernelException($"{nameof(ImageContent.DataUri)} not supported for assistant input."); } else if (content is FileReferenceContent fileContent) - { - yield return MessageContent.FromImageFileId(fileContent.FileId); + { + yield return MessageContent.FromImageFileId(fileContent.FileId); + } } } } From f3c8af1f4eceec080f75f7f4f1afe341d47eb341 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 2 Aug 2024 10:42:45 -0700 Subject: [PATCH 083/121] Fix logic level --- .../src/Agents/OpenAI/Internal/AssistantMessageFactory.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs index 3d627a2e3e01..8b65961e2677 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs @@ -58,10 +58,10 @@ public static IEnumerable GetMessageContents(ChatMessageContent //yield return MessageContent.FromImageUrl(new Uri(imageContent.DataUri!)); throw new KernelException($"{nameof(ImageContent.DataUri)} not supported for assistant input."); } - else if (content is FileReferenceContent fileContent) - { - yield return MessageContent.FromImageFileId(fileContent.FileId); - } + } + else if (content is FileReferenceContent fileContent) + { + yield return MessageContent.FromImageFileId(fileContent.FileId); } } } From 0c11eb3a36dbff322e8060d528832bca86fa559d Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 5 Aug 2024 10:39:18 -0700 Subject: [PATCH 084/121] Port update --- dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs | 1 + dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index acc7283fde4f..756ddf0ee582 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -429,6 +429,7 @@ private static ChatMessageContent GenerateCodeInterpreterContent(string agentNam ]) { AuthorName = agentName, + Metadata = new Dictionary { { OpenAIAssistantAgent.CodeInterpreterMetadataKey, true } }, }; } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index f54129096618..7d542a30ab80 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -17,6 +17,11 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// public sealed class OpenAIAssistantAgent : KernelAgent { + /// + /// Metadata key that identifies code-interpreter content. + /// + public const string CodeInterpreterMetadataKey = "code"; + internal const string OptionsMetadataKey = "__run_options"; private readonly Assistant _assistant; From 406c3d9c25feee6df8adf547da8acb6c66b36497 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 6 Aug 2024 15:54:36 -0700 Subject: [PATCH 085/121] Checkpoint --- dotnet/SK-dotnet.sln | 32 +++-- .../ChatCompletion_FunctionTermination.cs | 35 +++--- .../Agents/ChatCompletion_Streaming.cs | 23 ++-- .../Agents/ComplexChat_NestedShopper.cs | 17 +-- .../Concepts/Agents/MixedChat_Agents.cs | 21 ++-- .../Concepts/Agents/MixedChat_Files.cs | 28 ++--- .../Concepts/Agents/MixedChat_Images.cs | 30 ++--- .../Agents/OpenAIAssistant_ChartMaker.cs | 50 ++------ .../Agents/OpenAIAssistant_CodeInterpreter.cs | 62 --------- .../OpenAIAssistant_FileManipulation.cs | 27 ++-- .../Agents/OpenAIAssistant_FileSearch.cs | 25 ++-- .../Agents/OpenAIAssistant_FileService.cs | 4 +- dotnet/samples/Concepts/Concepts.csproj | 5 +- .../GettingStartedWithAgents.csproj | 17 ++- .../Resources/cat.jpg | Bin 0 -> 37831 bytes .../Resources/employees.pdf | Bin 0 -> 43422 bytes .../{Step1_Agent.cs => Step01_Agent.cs} | 14 +-- .../{Step2_Plugins.cs => Step02_Plugins.cs} | 35 +++--- .../{Step3_Chat.cs => Step03_Chat.cs} | 14 +-- ....cs => Step04_KernelFunctionStrategies.cs} | 21 ++-- ...ep5_JsonResult.cs => Step05_JsonResult.cs} | 21 ++-- ...ction.cs => Step06_DependencyInjection.cs} | 49 ++------ .../{Step7_Logging.cs => Step07_Logging.cs} | 16 +-- ...OpenAIAssistant.cs => Step08_Assistant.cs} | 57 ++++----- .../Step09_Assistant_Vision.cs | 75 +++++++++++ .../Step10_AssistantTool_CodeInterpreter.cs | 55 ++++++++ .../Step11_AssistantTool_FileSearch.cs | 84 +++++++++++++ .../Logging/AgentChatLogMessages.cs | 2 +- .../Internal/AssistantToolResourcesFactory.cs | 6 +- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 38 +++++- .../OpenAI/OpenAIAssistantDefinition.cs | 7 +- .../samples/AgentUtilities/BaseAgentsTest.cs | 118 ++++++++++++++++++ .../samples/SamplesInternalUtilities.props | 5 +- 33 files changed, 598 insertions(+), 395 deletions(-) delete mode 100644 dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs create mode 100644 dotnet/samples/GettingStartedWithAgents/Resources/cat.jpg create mode 100644 dotnet/samples/GettingStartedWithAgents/Resources/employees.pdf rename dotnet/samples/GettingStartedWithAgents/{Step1_Agent.cs => Step01_Agent.cs} (76%) rename dotnet/samples/GettingStartedWithAgents/{Step2_Plugins.cs => Step02_Plugins.cs} (76%) rename dotnet/samples/GettingStartedWithAgents/{Step3_Chat.cs => Step03_Chat.cs} (86%) rename dotnet/samples/GettingStartedWithAgents/{Step4_KernelFunctionStrategies.cs => Step04_KernelFunctionStrategies.cs} (85%) rename dotnet/samples/GettingStartedWithAgents/{Step5_JsonResult.cs => Step05_JsonResult.cs} (79%) rename dotnet/samples/GettingStartedWithAgents/{Step6_DependencyInjection.cs => Step06_DependencyInjection.cs} (65%) rename dotnet/samples/GettingStartedWithAgents/{Step7_Logging.cs => Step07_Logging.cs} (86%) rename dotnet/samples/GettingStartedWithAgents/{Step8_OpenAIAssistant.cs => Step08_Assistant.cs} (57%) create mode 100644 dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs create mode 100644 dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs create mode 100644 dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs create mode 100644 dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index fa7d9fbd3007..2a9596c5d08b 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -277,16 +277,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStartedWithAgents", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{77E141BA-AF5E-4C01-A970-6C07AC3CD55A}" ProjectSection(SolutionItems) = preProject - src\InternalUtilities\samples\ConfigurationNotFoundException.cs = src\InternalUtilities\samples\ConfigurationNotFoundException.cs - src\InternalUtilities\samples\EnumerableExtensions.cs = src\InternalUtilities\samples\EnumerableExtensions.cs - src\InternalUtilities\samples\Env.cs = src\InternalUtilities\samples\Env.cs - src\InternalUtilities\samples\ObjectExtensions.cs = src\InternalUtilities\samples\ObjectExtensions.cs - src\InternalUtilities\samples\PlanExtensions.cs = src\InternalUtilities\samples\PlanExtensions.cs - src\InternalUtilities\samples\RepoFiles.cs = src\InternalUtilities\samples\RepoFiles.cs src\InternalUtilities\samples\SamplesInternalUtilities.props = src\InternalUtilities\samples\SamplesInternalUtilities.props - src\InternalUtilities\samples\TextOutputHelperExtensions.cs = src\InternalUtilities\samples\TextOutputHelperExtensions.cs - src\InternalUtilities\samples\XunitLogger.cs = src\InternalUtilities\samples\XunitLogger.cs - src\InternalUtilities\samples\YourAppException.cs = src\InternalUtilities\samples\YourAppException.cs EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Functions.Prompty", "src\Functions\Functions.Prompty\Functions.Prompty.csproj", "{12B06019-740B-466D-A9E0-F05BC123A47D}" @@ -350,6 +341,27 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MathPlugin", "MathPlugin", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kernel-functions-generator", "samples\Demos\CreateChatGptPlugin\MathPlugin\kernel-functions-generator\kernel-functions-generator.csproj", "{4326A974-F027-4ABD-A220-382CC6BB0801}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{EE454832-085F-4D37-B19B-F94F7FC6984A}" + ProjectSection(SolutionItems) = preProject + src\InternalUtilities\samples\InternalUtilities\BaseTest.cs = src\InternalUtilities\samples\InternalUtilities\BaseTest.cs + src\InternalUtilities\samples\InternalUtilities\ConfigurationNotFoundException.cs = src\InternalUtilities\samples\InternalUtilities\ConfigurationNotFoundException.cs + src\InternalUtilities\samples\InternalUtilities\EmbeddedResource.cs = src\InternalUtilities\samples\InternalUtilities\EmbeddedResource.cs + src\InternalUtilities\samples\InternalUtilities\EnumerableExtensions.cs = src\InternalUtilities\samples\InternalUtilities\EnumerableExtensions.cs + src\InternalUtilities\samples\InternalUtilities\Env.cs = src\InternalUtilities\samples\InternalUtilities\Env.cs + src\InternalUtilities\samples\InternalUtilities\JsonResultTranslator.cs = src\InternalUtilities\samples\InternalUtilities\JsonResultTranslator.cs + src\InternalUtilities\samples\InternalUtilities\ObjectExtensions.cs = src\InternalUtilities\samples\InternalUtilities\ObjectExtensions.cs + src\InternalUtilities\samples\InternalUtilities\RepoFiles.cs = src\InternalUtilities\samples\InternalUtilities\RepoFiles.cs + src\InternalUtilities\samples\InternalUtilities\TestConfiguration.cs = src\InternalUtilities\samples\InternalUtilities\TestConfiguration.cs + src\InternalUtilities\samples\InternalUtilities\TextOutputHelperExtensions.cs = src\InternalUtilities\samples\InternalUtilities\TextOutputHelperExtensions.cs + src\InternalUtilities\samples\InternalUtilities\XunitLogger.cs = src\InternalUtilities\samples\InternalUtilities\XunitLogger.cs + src\InternalUtilities\samples\InternalUtilities\YourAppException.cs = src\InternalUtilities\samples\InternalUtilities\YourAppException.cs + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Agents", "Agents", "{5C6C30E0-7AC1-47F4-8244-57B066B43FD8}" + ProjectSection(SolutionItems) = preProject + src\InternalUtilities\samples\AgentUtilities\BaseAgentsTest.cs = src\InternalUtilities\samples\AgentUtilities\BaseAgentsTest.cs + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -971,6 +983,8 @@ Global {6B268108-2AB5-4607-B246-06AD8410E60E} = {4BB70FB3-1EC0-4F4A-863B-273D6C6D4D9A} {4BB70FB3-1EC0-4F4A-863B-273D6C6D4D9A} = {F8B82F6B-B16A-4F8C-9C41-E9CD8D79A098} {4326A974-F027-4ABD-A220-382CC6BB0801} = {4BB70FB3-1EC0-4F4A-863B-273D6C6D4D9A} + {EE454832-085F-4D37-B19B-F94F7FC6984A} = {77E141BA-AF5E-4C01-A970-6C07AC3CD55A} + {5C6C30E0-7AC1-47F4-8244-57B066B43FD8} = {77E141BA-AF5E-4C01-A970-6C07AC3CD55A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs index 12438c0fc328..c8d0531d9bda 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs @@ -12,7 +12,7 @@ namespace Agents; /// Demonstrate usage of for both direction invocation /// of and via . /// -public class ChatCompletion_FunctionTermination(ITestOutputHelper output) : BaseTest(output) +public class ChatCompletion_FunctionTermination(ITestOutputHelper output) : BaseAgentsTest(output) { [Fact] public async Task UseAutoFunctionInvocationFilterWithAgentInvocationAsync() @@ -44,25 +44,25 @@ public async Task UseAutoFunctionInvocationFilterWithAgentInvocationAsync() Console.WriteLine("================================"); foreach (ChatMessageContent message in chat) { - this.WriteContent(message); + this.WriteAgentChatMessage(message); } // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - ChatMessageContent userContent = new(AuthorRole.User, input); - chat.Add(userContent); - this.WriteContent(userContent); + ChatMessageContent message = new(AuthorRole.User, input); + chat.Add(message); + this.WriteAgentChatMessage(message); - await foreach (ChatMessageContent content in agent.InvokeAsync(chat)) + await foreach (ChatMessageContent response in agent.InvokeAsync(chat)) { // Do not add a message implicitly added to the history. - if (!content.Items.Any(i => i is FunctionCallContent || i is FunctionResultContent)) + if (!response.Items.Any(i => i is FunctionCallContent || i is FunctionResultContent)) { - chat.Add(content); + chat.Add(response); } - this.WriteContent(content); + this.WriteAgentChatMessage(response); } } } @@ -98,28 +98,23 @@ public async Task UseAutoFunctionInvocationFilterWithAgentChatAsync() ChatMessageContent[] history = await chat.GetChatMessagesAsync().ToArrayAsync(); for (int index = history.Length; index > 0; --index) { - this.WriteContent(history[index - 1]); + this.WriteAgentChatMessage(history[index - 1]); } // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - ChatMessageContent userContent = new(AuthorRole.User, input); - chat.AddChatMessage(userContent); - this.WriteContent(userContent); + ChatMessageContent message = new(AuthorRole.User, input); + chat.AddChatMessage(message); + this.WriteAgentChatMessage(message); - await foreach (ChatMessageContent content in chat.InvokeAsync(agent)) + await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { - this.WriteContent(content); + this.WriteAgentChatMessage(response); } } } - private void WriteContent(ChatMessageContent content) - { - Console.WriteLine($"[{content.Items.LastOrDefault()?.GetType().Name ?? "(empty)"}] {content.Role} : '{content.Content}'"); - } - private Kernel CreateKernelWithFilter() { IKernelBuilder builder = Kernel.CreateBuilder(); diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs index 258e12166a6b..7e74e425536c 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs @@ -12,7 +12,7 @@ namespace Agents; /// Demonstrate creation of and /// eliciting its response to three explicit user messages. /// -public class ChatCompletion_Streaming(ITestOutputHelper output) : BaseTest(output) +public class ChatCompletion_Streaming(ITestOutputHelper output) : BaseAgentsTest(output) { private const string ParrotName = "Parrot"; private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound."; @@ -66,32 +66,33 @@ public async Task UseStreamingChatCompletionAgentWithPluginAsync() // Local function to invoke agent and display the conversation messages. private async Task InvokeAgentAsync(ChatCompletionAgent agent, ChatHistory chat, string input) { - chat.Add(new ChatMessageContent(AuthorRole.User, input)); - - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + ChatMessageContent message = new(AuthorRole.User, input); + chat.Add(message); + this.WriteAgentChatMessage(message); StringBuilder builder = new(); - await foreach (StreamingChatMessageContent message in agent.InvokeStreamingAsync(chat)) + await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(chat)) { - if (string.IsNullOrEmpty(message.Content)) + if (string.IsNullOrEmpty(response.Content)) { continue; } if (builder.Length == 0) { - Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}:"); + Console.WriteLine($"# {response.Role} - {response.AuthorName ?? "*"}:"); } - Console.WriteLine($"\t > streamed: '{message.Content}'"); - builder.Append(message.Content); + Console.WriteLine($"\t > streamed: '{response.Content}'"); + builder.Append(response.Content); } if (builder.Length > 0) { // Display full response and capture in chat history - Console.WriteLine($"\t > complete: '{builder}'"); - chat.Add(new ChatMessageContent(AuthorRole.Assistant, builder.ToString()) { AuthorName = agent.Name }); + ChatMessageContent response = new(AuthorRole.Assistant, builder.ToString()) { AuthorName = agent.Name }; + chat.Add(message); + this.WriteAgentChatMessage(message); } } diff --git a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs index 81b2914ade3b..e12dee448370 100644 --- a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs +++ b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs @@ -1,4 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. +using Azure; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; @@ -13,7 +14,7 @@ namespace Agents; /// Demonstrate usage of and /// to manage execution. /// -public class ComplexChat_NestedShopper(ITestOutputHelper output) : BaseTest(output) +public class ComplexChat_NestedShopper(ITestOutputHelper output) : BaseAgentsTest(output) { protected override bool ForceOpenAI => true; @@ -154,20 +155,20 @@ public async Task NestedChatWithAggregatorAgentAsync() Console.WriteLine(">>>> AGGREGATED CHAT"); Console.WriteLine(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); - await foreach (ChatMessageContent content in chat.GetChatMessagesAsync(personalShopperAgent).Reverse()) + await foreach (ChatMessageContent message in chat.GetChatMessagesAsync(personalShopperAgent).Reverse()) { - Console.WriteLine($">>>> {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + WriteAgentChatMessage(message); } async Task InvokeChatAsync(string input) { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + ChatMessageContent message = new(AuthorRole.User, input); + chat.AddChatMessage(message); + this.WriteAgentChatMessage(message); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (ChatMessageContent content in chat.InvokeAsync(personalShopperAgent)) + await foreach (ChatMessageContent response in chat.InvokeAsync(personalShopperAgent)) { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + WriteAgentChatMessage(response); } Console.WriteLine($"\n# IS COMPLETE: {chat.IsComplete}"); diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs index 91add34e8693..18ab8a673ca1 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs @@ -10,7 +10,7 @@ namespace Agents; /// Demonstrate that two different agent types are able to participate in the same conversation. /// In this case a and participate. /// -public class MixedChat_Agents(ITestOutputHelper output) : BaseTest(output) +public class MixedChat_Agents(ITestOutputHelper output) : BaseAgentsTest(output) { private const string ReviewerName = "ArtDirector"; private const string ReviewerInstructions = @@ -53,6 +53,7 @@ await OpenAIAssistantAgent.CreateAsync( Instructions = CopyWriterInstructions, Name = CopyWriterName, ModelId = this.Model, + Metadata = AssistantSampleMetadata, }); // Create a chat for agent interaction. @@ -76,16 +77,16 @@ await OpenAIAssistantAgent.CreateAsync( }; // Invoke chat and display messages. - string input = "concept: maps made out of egg cartons."; - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + ChatMessageContent input = new(AuthorRole.User, "concept: maps made out of egg cartons."); + chat.AddChatMessage(input); + this.WriteAgentChatMessage(input); - await foreach (ChatMessageContent content in chat.InvokeAsync()) + await foreach (ChatMessageContent response in chat.InvokeAsync()) { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + this.WriteAgentChatMessage(response); } - Console.WriteLine($"# IS COMPLETE: {chat.IsComplete}"); + Console.WriteLine($"\n[IS COMPLETED: {chat.IsComplete}]"); } private sealed class ApprovalTerminationStrategy : TerminationStrategy @@ -94,10 +95,4 @@ private sealed class ApprovalTerminationStrategy : TerminationStrategy protected override Task ShouldAgentTerminateAsync(Agent agent, IReadOnlyList history, CancellationToken cancellationToken) => Task.FromResult(history[history.Count - 1].Content?.Contains("approve", StringComparison.OrdinalIgnoreCase) ?? false); } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs index 24742278f6e5..f14ad8d1222d 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Text; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; @@ -13,7 +12,7 @@ namespace Agents; /// Demonstrate agent interacts with /// when it produces file output. /// -public class MixedChat_Files(ITestOutputHelper output) : BaseTest(output) +public class MixedChat_Files(ITestOutputHelper output) : BaseAgentsTest(output) { /// /// Target OpenAI services. @@ -25,7 +24,7 @@ public class MixedChat_Files(ITestOutputHelper output) : BaseTest(output) [Fact] public async Task AnalyzeFileAndGenerateReportAsync() { - OpenAIServiceConfiguration config = GetOpenAIConfiguration(); + OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); FileClient fileClient = config.CreateFileClient(); @@ -45,8 +44,9 @@ await OpenAIAssistantAgent.CreateAsync( new() { EnableCodeInterpreter = true, // Enable code-interpreter + CodeInterpreterFileIds = [uploadFile.Id], // Associate uploaded file with assistant ModelId = this.Model, - CodeInterpreterFileIds = [uploadFile.Id] // Associate uploaded file with assistant + Metadata = AssistantSampleMetadata, }); ChatCompletionAgent summaryAgent = @@ -81,27 +81,15 @@ async Task InvokeAgentAsync(Agent agent, string? input = null) { if (!string.IsNullOrWhiteSpace(input)) { + ChatMessageContent message = new(AuthorRole.User, input); chat.AddChatMessage(new(AuthorRole.User, input)); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + this.WriteAgentChatMessage(message); } - await foreach (ChatMessageContent content in chat.InvokeAsync(agent)) + await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { - Console.WriteLine($"\n# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); - - foreach (AnnotationContent annotation in content.Items.OfType()) - { - Console.WriteLine($"\t* '{annotation.Quote}' => {annotation.FileId}"); - BinaryData fileContent = await fileClient.DownloadFileAsync(annotation.FileId!); - Console.WriteLine($"\n{Encoding.Default.GetString(fileContent.ToArray())}"); - } + this.WriteAgentChatMessage(response); } } } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs index cfbcd97c8260..e1bf8b2b7068 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs @@ -11,7 +11,7 @@ namespace Agents; /// Demonstrate agent interacts with /// when it produces image output. /// -public class MixedChat_Images(ITestOutputHelper output) : BaseTest(output) +public class MixedChat_Images(ITestOutputHelper output) : BaseAgentsTest(output) { /// /// Target OpenAI services. @@ -27,7 +27,7 @@ public class MixedChat_Images(ITestOutputHelper output) : BaseTest(output) [Fact] public async Task AnalyzeDataAndGenerateChartAsync() { - OpenAIServiceConfiguration config = GetOpenAIConfiguration(); + OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); FileClient fileClient = config.CreateFileClient(); @@ -42,6 +42,7 @@ await OpenAIAssistantAgent.CreateAsync( Name = AnalystName, EnableCodeInterpreter = true, ModelId = this.Model, + Metadata = AssistantSampleMetadata, }); ChatCompletionAgent summaryAgent = @@ -88,32 +89,15 @@ async Task InvokeAgentAsync(Agent agent, string? input = null) { if (!string.IsNullOrWhiteSpace(input)) { + ChatMessageContent message = new(AuthorRole.User, input); chat.AddChatMessage(new(AuthorRole.User, input)); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + this.WriteAgentChatMessage(message); } - await foreach (ChatMessageContent message in chat.InvokeAsync(agent)) + await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { - if (!string.IsNullOrWhiteSpace(message.Content)) - { - Console.WriteLine($"\n# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); - } - - foreach (FileReferenceContent fileReference in message.Items.OfType()) - { - Console.WriteLine($"\t* Generated image - @{fileReference.FileId}"); - BinaryData fileContent = await fileClient.DownloadFileAsync(fileReference.FileId!); - string filePath = Path.ChangeExtension(Path.GetTempFileName(), ".png"); - await File.WriteAllBytesAsync($"{filePath}.png", fileContent.ToArray()); - Console.WriteLine($"\t* Local path - {filePath}"); - } + this.WriteAgentChatMessage(response); } } } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index 4c2ea0c59534..af8990096a65 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Diagnostics; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; @@ -12,7 +11,7 @@ namespace Agents; /// Demonstrate using code-interpreter with to /// produce image content displays the requested charts. /// -public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseTest(output) +public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseAgentsTest(output) { /// /// Target Open AI services. @@ -25,7 +24,7 @@ public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseTest(out [Fact] public async Task GenerateChartWithOpenAIAssistantAgentAsync() { - OpenAIServiceConfiguration config = GetOpenAIConfiguration(); + OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); FileClient fileClient = config.CreateFileClient(); @@ -40,6 +39,7 @@ await OpenAIAssistantAgent.CreateAsync( Name = AgentName, EnableCodeInterpreter = true, ModelId = this.Model, + Metadata = AssistantSampleMetadata, }); // Create a chat for agent interaction. @@ -71,48 +71,14 @@ Sum 426 1622 856 2904 // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + ChatMessageContent message = new(AuthorRole.User, input); + chat.AddChatMessage(new(AuthorRole.User, input)); + this.WriteAgentChatMessage(message); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (var message in chat.InvokeAsync(agent)) + await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { - if (!string.IsNullOrWhiteSpace(message.Content)) - { - Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); - } - - foreach (var fileReference in message.Items.OfType()) - { - Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: @{fileReference.FileId}"); - - string downloadPath = await DownloadFileContentAsync(fileReference.FileId); - - Console.WriteLine($"# {message.Role}: @{fileReference.FileId} downloaded to {downloadPath}"); - } + this.WriteAgentChatMessage(response); } } - - async Task DownloadFileContentAsync(string fileId) - { - string filePath = Path.Combine(Environment.CurrentDirectory, $"{fileId}.jpg"); - BinaryData content = await fileClient.DownloadFileAsync(fileId); - File.WriteAllBytes(filePath, content.ToArray()); - - Process.Start( - new ProcessStartInfo - { - FileName = "cmd.exe", - Arguments = $"/C start {filePath}" - }); - - return filePath; - } } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs deleted file mode 100644 index eb5169f40b3f..000000000000 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.ChatCompletion; - -namespace Agents; - -/// -/// Demonstrate using code-interpreter on . -/// -public class OpenAIAssistant_CodeInterpreter(ITestOutputHelper output) : BaseTest(output) -{ - protected override bool ForceOpenAI => false; - - [Fact] - public async Task UseCodeInterpreterToolWithOpenAIAssistantAgentAsync() - { - // Define the agent - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - kernel: new(), - config: GetOpenAIConfiguration(), - new() - { - EnableCodeInterpreter = true, // Enable code-interpreter - ModelId = this.Model, - }); - - // Create a chat for agent interaction. - AgentGroupChat chat = new(); - - // Respond to user input - try - { - await InvokeAgentAsync("Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?"); - } - finally - { - await agent.DeleteAsync(); - } - - // Local function to invoke agent and display the conversation messages. - async Task InvokeAgentAsync(string input) - { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); - - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (var content in chat.InvokeAsync(agent)) - { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); - } - } - } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); -} diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index 8a6c98bd3a38..0f92b31ffb04 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Text; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; @@ -12,7 +11,7 @@ namespace Agents; /// /// Demonstrate using code-interpreter to manipulate and generate csv files with . /// -public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTest(output) +public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseAgentsTest(output) { /// /// Target OpenAI services. @@ -22,7 +21,7 @@ public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTe [Fact] public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() { - OpenAIServiceConfiguration config = GetOpenAIConfiguration(); + OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); FileClient fileClient = config.CreateFileClient(); @@ -42,6 +41,7 @@ await OpenAIAssistantAgent.CreateAsync( CodeInterpreterFileIds = [uploadFile.Id], EnableCodeInterpreter = true, // Enable code-interpreter ModelId = this.Model, + Metadata = AssistantSampleMetadata, }); // Create a chat for agent interaction. @@ -63,27 +63,14 @@ await OpenAIAssistantAgent.CreateAsync( // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { + ChatMessageContent message = new(AuthorRole.User, input); chat.AddChatMessage(new(AuthorRole.User, input)); + this.WriteAgentChatMessage(message); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (ChatMessageContent message in chat.InvokeAsync(agent)) + await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { - Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'"); - - foreach (AnnotationContent annotation in message.Items.OfType()) - { - Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}"); - BinaryData content = await fileClient.DownloadFileAsync(annotation.FileId!); - Console.WriteLine(Encoding.Default.GetString(content.ToArray())); - } + this.WriteAgentChatMessage(response); } } } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs index 550615c6bf3e..c73934421a7c 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs @@ -12,7 +12,7 @@ namespace Agents; /// /// Demonstrate using retrieval on . /// -public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseTest(output) +public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseAgentsTest(output) { /// /// Retrieval tool not supported on Azure OpenAI. @@ -22,7 +22,7 @@ public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseTest(out [Fact] public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync() { - OpenAIServiceConfiguration config = GetOpenAIConfiguration(); + OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); FileClient fileClient = config.CreateFileClient(); @@ -47,12 +47,13 @@ await OpenAIAssistantAgent.CreateAsync( config, new() { - ModelId = this.Model, VectorStoreId = vectorStore.Id, + ModelId = this.Model, + Metadata = AssistantSampleMetadata, }); // Create a chat for agent interaction. - var chat = new AgentGroupChat(); + AgentGroupChat chat = new(); // Respond to user input try @@ -71,20 +72,14 @@ await OpenAIAssistantAgent.CreateAsync( // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + ChatMessageContent message = new(AuthorRole.User, input); + chat.AddChatMessage(new(AuthorRole.User, input)); + this.WriteAgentChatMessage(message); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (var content in chat.InvokeAsync(agent)) + await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + this.WriteAgentChatMessage(response); } } } - - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileService.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileService.cs index 38bac46f648a..a8f31622c753 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileService.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileService.cs @@ -28,7 +28,7 @@ public async Task UploadAndRetrieveFilesAsync() new BinaryContent(data: await EmbeddedResource.ReadAllAsync("travelinfo.txt"), mimeType: "text/plain") { InnerContent = "travelinfo.txt" } ]; - var fileContents = new Dictionary(); + Dictionary fileContents = new(); foreach (BinaryContent file in files) { OpenAIFileReference result = await fileService.UploadContentAsync(file, new(file.InnerContent!.ToString()!, OpenAIFilePurpose.FineTune)); @@ -49,7 +49,7 @@ public async Task UploadAndRetrieveFilesAsync() string? fileName = fileContents[fileReference.Id].InnerContent!.ToString(); ReadOnlyMemory data = content.Data ?? new(); - var typedContent = mimeType switch + BinaryContent typedContent = mimeType switch { "image/jpeg" => new ImageContent(data, mimeType) { Uri = content.Uri, InnerContent = fileName, Metadata = content.Metadata }, "audio/wav" => new AudioContent(data, mimeType) { Uri = content.Uri, InnerContent = fileName, Metadata = content.Metadata }, diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index e19ed5e0d8c5..32fe001cbf09 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -40,7 +40,10 @@ - + + + true + diff --git a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj index b95bbd546d34..df9e025b678f 100644 --- a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj +++ b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj @@ -9,7 +9,7 @@ true - $(NoWarn);CS8618,IDE0009,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110 + $(NoWarn);CS8618,IDE0009,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110,OPENAI001 Library 5ee045b0-aea3-4f08-8d31-32d1a6f8fed0 @@ -32,7 +32,10 @@ - + + + true + @@ -48,4 +51,14 @@ + + + Always + + + + + + + diff --git a/dotnet/samples/GettingStartedWithAgents/Resources/cat.jpg b/dotnet/samples/GettingStartedWithAgents/Resources/cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1e9f26de48fc542676a7461020206fab297c0314 GIT binary patch literal 37831 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 X-`J9C)u%;8=#JWSR9(_<@Uj2d>mT>!jGU~c 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 tt_Qna&aFsUb~*nK+dRE#YYMVVf=Qm-$*ez@Z! literal 0 HcmV?d00001 diff --git a/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs b/dotnet/samples/GettingStartedWithAgents/Step01_Agent.cs similarity index 76% rename from dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs rename to dotnet/samples/GettingStartedWithAgents/Step01_Agent.cs index d7d4a0471b01..bc5bee5249e5 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step01_Agent.cs @@ -9,7 +9,7 @@ namespace GettingStarted; /// Demonstrate creation of and /// eliciting its response to three explicit user messages. /// -public class Step1_Agent(ITestOutputHelper output) : BaseTest(output) +public class Step01_Agent(ITestOutputHelper output) : BaseAgentsTest(output) { private const string ParrotName = "Parrot"; private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound."; @@ -37,15 +37,15 @@ public async Task UseSingleChatCompletionAgentAsync() // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - chat.Add(new ChatMessageContent(AuthorRole.User, input)); + ChatMessageContent message = new(AuthorRole.User, input); + chat.Add(message); + this.WriteAgentChatMessage(message); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (ChatMessageContent content in agent.InvokeAsync(chat)) + await foreach (ChatMessageContent response in agent.InvokeAsync(chat)) { - chat.Add(content); + chat.Add(response); - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + this.WriteAgentChatMessage(response); } } } diff --git a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs b/dotnet/samples/GettingStartedWithAgents/Step02_Plugins.cs similarity index 76% rename from dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs rename to dotnet/samples/GettingStartedWithAgents/Step02_Plugins.cs index 38741bbb2e7c..7cbd1b1b0706 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step02_Plugins.cs @@ -11,7 +11,7 @@ namespace GettingStarted; /// Demonstrate creation of with a , /// and then eliciting its response to explicit user messages. /// -public class Step2_Plugins(ITestOutputHelper output) : BaseTest(output) +public class Step02_Plugins(ITestOutputHelper output) : BaseAgentsTest(output) { private const string HostName = "Host"; private const string HostInstructions = "Answer questions about the menu."; @@ -45,37 +45,34 @@ public async Task UseChatCompletionWithPluginAgentAsync() // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - chat.Add(new ChatMessageContent(AuthorRole.User, input)); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + ChatMessageContent message = new(AuthorRole.User, input); + chat.Add(message); + this.WriteAgentChatMessage(message); - await foreach (ChatMessageContent content in agent.InvokeAsync(chat)) + await foreach (ChatMessageContent response in agent.InvokeAsync(chat)) { - chat.Add(content); + chat.Add(response); - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + this.WriteAgentChatMessage(response); } } } - public sealed class MenuPlugin + private sealed class MenuPlugin { [KernelFunction, Description("Provides a list of specials from the menu.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")] - public string GetSpecials() - { - return @" -Special Soup: Clam Chowder -Special Salad: Cobb Salad -Special Drink: Chai Tea -"; - } + public string GetSpecials() => + """ + Special Soup: Clam Chowder + Special Salad: Cobb Salad + Special Drink: Chai Tea + """; [KernelFunction, Description("Provides the price of the requested menu item.")] public string GetItemPrice( [Description("The name of the menu item.")] - string menuItem) - { - return "$9.99"; - } + string menuItem) => + "$9.99"; } } diff --git a/dotnet/samples/GettingStartedWithAgents/Step3_Chat.cs b/dotnet/samples/GettingStartedWithAgents/Step03_Chat.cs similarity index 86% rename from dotnet/samples/GettingStartedWithAgents/Step3_Chat.cs rename to dotnet/samples/GettingStartedWithAgents/Step03_Chat.cs index 5d0c185f95f5..1ada85d512f3 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step3_Chat.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step03_Chat.cs @@ -11,7 +11,7 @@ namespace GettingStarted; /// that inform how chat proceeds with regards to: Agent selection, chat continuation, and maximum /// number of agent interactions. /// -public class Step3_Chat(ITestOutputHelper output) : BaseTest(output) +public class Step03_Chat(ITestOutputHelper output) : BaseAgentsTest(output) { private const string ReviewerName = "ArtDirector"; private const string ReviewerInstructions = @@ -74,16 +74,16 @@ public async Task UseAgentGroupChatWithTwoAgentsAsync() }; // Invoke chat and display messages. - string input = "concept: maps made out of egg cartons."; - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + ChatMessageContent input = new(AuthorRole.User, "concept: maps made out of egg cartons."); + chat.AddChatMessage(input); + this.WriteAgentChatMessage(input); - await foreach (ChatMessageContent content in chat.InvokeAsync()) + await foreach (ChatMessageContent response in chat.InvokeAsync()) { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + this.WriteAgentChatMessage(response); } - Console.WriteLine($"# IS COMPLETE: {chat.IsComplete}"); + Console.WriteLine($"\n[IS COMPLETED: {chat.IsComplete}]"); } private sealed class ApprovalTerminationStrategy : TerminationStrategy diff --git a/dotnet/samples/GettingStartedWithAgents/Step4_KernelFunctionStrategies.cs b/dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs similarity index 85% rename from dotnet/samples/GettingStartedWithAgents/Step4_KernelFunctionStrategies.cs rename to dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs index 9cabe0193d3e..24a4a1dc70b5 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step4_KernelFunctionStrategies.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs @@ -10,7 +10,7 @@ namespace GettingStarted; /// Demonstrate usage of and /// to manage execution. /// -public class Step4_KernelFunctionStrategies(ITestOutputHelper output) : BaseTest(output) +public class Step04_KernelFunctionStrategies(ITestOutputHelper output) : BaseAgentsTest(output) { private const string ReviewerName = "ArtDirector"; private const string ReviewerInstructions = @@ -64,8 +64,9 @@ public async Task UseKernelFunctionStrategiesWithAgentGroupChatAsync() KernelFunction selectionFunction = KernelFunctionFactory.CreateFromPrompt( $$$""" - Your job is to determine which participant takes the next turn in a conversation according to the action of the most recent participant. + Determine which participant takes the next turn in a conversation based on the the most recent participant. State only the name of the participant to take the next turn. + No participant should take more than one turn in a row. Choose only from these participants: - {{{ReviewerName}}} @@ -73,8 +74,8 @@ State only the name of the participant to take the next turn. Always follow these rules when selecting the next participant: - After user input, it is {{{CopyWriterName}}}'a turn. - - After {{{CopyWriterName}}} replies, it is {{{ReviewerName}}}'s turn. - - After {{{ReviewerName}}} provides feedback, it is {{{CopyWriterName}}}'s turn. + - After {{{CopyWriterName}}}, it is {{{ReviewerName}}}'s turn. + - After {{{ReviewerName}}}, it is {{{CopyWriterName}}}'s turn. History: {{$history}} @@ -116,15 +117,15 @@ State only the name of the participant to take the next turn. }; // Invoke chat and display messages. - string input = "concept: maps made out of egg cartons."; - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + ChatMessageContent message = new(AuthorRole.User, "concept: maps made out of egg cartons."); + chat.AddChatMessage(message); + this.WriteAgentChatMessage(message); - await foreach (ChatMessageContent content in chat.InvokeAsync()) + await foreach (ChatMessageContent responese in chat.InvokeAsync()) { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + this.WriteAgentChatMessage(responese); } - Console.WriteLine($"# IS COMPLETE: {chat.IsComplete}"); + Console.WriteLine($"\n[IS COMPLETED: {chat.IsComplete}]"); } } diff --git a/dotnet/samples/GettingStartedWithAgents/Step5_JsonResult.cs b/dotnet/samples/GettingStartedWithAgents/Step05_JsonResult.cs similarity index 79% rename from dotnet/samples/GettingStartedWithAgents/Step5_JsonResult.cs rename to dotnet/samples/GettingStartedWithAgents/Step05_JsonResult.cs index 20ad4c2096d4..8806c7d3b62d 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step5_JsonResult.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step05_JsonResult.cs @@ -10,14 +10,14 @@ namespace GettingStarted; /// /// Demonstrate parsing JSON response. /// -public class Step5_JsonResult(ITestOutputHelper output) : BaseTest(output) +public class Step05_JsonResult(ITestOutputHelper output) : BaseAgentsTest(output) { private const int ScoreCompletionThreshold = 70; private const string TutorName = "Tutor"; private const string TutorInstructions = """ - Think step-by-step and rate the user input on creativity and expressivness from 1-100. + Think step-by-step and rate the user input on creativity and expressiveness from 1-100. Respond in JSON format with the following JSON schema: @@ -60,19 +60,20 @@ public async Task UseKernelFunctionStrategiesWithJsonResultAsync() // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + ChatMessageContent message = new(AuthorRole.User, input); + chat.AddChatMessage(message); + this.WriteAgentChatMessage(message); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - - await foreach (ChatMessageContent content in chat.InvokeAsync(agent)) + await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); - Console.WriteLine($"# IS COMPLETE: {chat.IsComplete}"); + this.WriteAgentChatMessage(response); + + Console.WriteLine($"[IS COMPLETED: {chat.IsComplete}]"); } } } - private record struct InputScore(int score, string notes); + private record struct WritingScore(int score, string notes); private sealed class ThresholdTerminationStrategy : TerminationStrategy { @@ -80,7 +81,7 @@ protected override Task ShouldAgentTerminateAsync(Agent agent, IReadOnlyLi { string lastMessageContent = history[history.Count - 1].Content ?? string.Empty; - InputScore? result = JsonResultTranslator.Translate(lastMessageContent); + WritingScore? result = JsonResultTranslator.Translate(lastMessageContent); return Task.FromResult((result?.score ?? 0) >= ScoreCompletionThreshold); } diff --git a/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs b/dotnet/samples/GettingStartedWithAgents/Step06_DependencyInjection.cs similarity index 65% rename from dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs rename to dotnet/samples/GettingStartedWithAgents/Step06_DependencyInjection.cs index 21af5db70dce..a0d32f8cefba 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step06_DependencyInjection.cs @@ -3,23 +3,19 @@ using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.Chat; using Microsoft.SemanticKernel.ChatCompletion; -using Resources; namespace GettingStarted; /// /// Demonstrate creation of an agent via dependency injection. /// -public class Step6_DependencyInjection(ITestOutputHelper output) : BaseTest(output) +public class Step06_DependencyInjection(ITestOutputHelper output) : BaseAgentsTest(output) { - private const int ScoreCompletionThreshold = 70; - private const string TutorName = "Tutor"; private const string TutorInstructions = """ - Think step-by-step and rate the user input on creativity and expressivness from 1-100. + Think step-by-step and rate the user input on creativity and expressiveness from 1-100. Respond in JSON format with the following JSON schema: @@ -80,50 +76,27 @@ public async Task UseDependencyInjectionToCreateAgentAsync() // Local function to invoke agent and display the conversation messages. async Task WriteAgentResponse(string input) { - Console.WriteLine($"# {AuthorRole.User}: {input}"); + ChatMessageContent message = new(AuthorRole.User, input); + this.WriteAgentChatMessage(message); - await foreach (ChatMessageContent content in agentClient.RunDemoAsync(input)) + await foreach (ChatMessageContent response in agentClient.RunDemoAsync(message)) { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + this.WriteAgentChatMessage(response); } } } private sealed class AgentClient([FromKeyedServices(TutorName)] ChatCompletionAgent agent) { - private readonly AgentGroupChat _chat = - new() - { - ExecutionSettings = - new() - { - // Here a TerminationStrategy subclass is used that will terminate when - // the response includes a score that is greater than or equal to 70. - TerminationStrategy = new ThresholdTerminationStrategy() - } - }; - - public IAsyncEnumerable RunDemoAsync(string input) - { - // Create a chat for agent interaction. + private readonly AgentGroupChat _chat = new(); - this._chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + public IAsyncEnumerable RunDemoAsync(ChatMessageContent input) + { + this._chat.AddChatMessage(input); return this._chat.InvokeAsync(agent); } } - private record struct InputScore(int score, string notes); - - private sealed class ThresholdTerminationStrategy : TerminationStrategy - { - protected override Task ShouldAgentTerminateAsync(Agent agent, IReadOnlyList history, CancellationToken cancellationToken) - { - string lastMessageContent = history[history.Count - 1].Content ?? string.Empty; - - InputScore? result = JsonResultTranslator.Translate(lastMessageContent); - - return Task.FromResult((result?.score ?? 0) >= ScoreCompletionThreshold); - } - } + private record struct WritingScore(int score, string notes); } diff --git a/dotnet/samples/GettingStartedWithAgents/Step7_Logging.cs b/dotnet/samples/GettingStartedWithAgents/Step07_Logging.cs similarity index 86% rename from dotnet/samples/GettingStartedWithAgents/Step7_Logging.cs rename to dotnet/samples/GettingStartedWithAgents/Step07_Logging.cs index 1ab559e668fb..3a48d407dea9 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step7_Logging.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step07_Logging.cs @@ -8,13 +8,13 @@ namespace GettingStarted; /// -/// A repeat of with logging enabled via assignment +/// A repeat of with logging enabled via assignment /// of a to . /// /// /// Samples become super noisy with logging always enabled. /// -public class Step7_Logging(ITestOutputHelper output) : BaseTest(output) +public class Step07_Logging(ITestOutputHelper output) : BaseAgentsTest(output) { private const string ReviewerName = "ArtDirector"; private const string ReviewerInstructions = @@ -81,16 +81,16 @@ public async Task UseLoggerFactoryWithAgentGroupChatAsync() }; // Invoke chat and display messages. - string input = "concept: maps made out of egg cartons."; - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + ChatMessageContent input = new(AuthorRole.User, "concept: maps made out of egg cartons."); + chat.AddChatMessage(input); + this.WriteAgentChatMessage(input); - await foreach (ChatMessageContent content in chat.InvokeAsync()) + await foreach (ChatMessageContent response in chat.InvokeAsync()) { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); + this.WriteAgentChatMessage(response); } - Console.WriteLine($"# IS COMPLETE: {chat.IsComplete}"); + Console.WriteLine($"\n[IS COMPLETED: {chat.IsComplete}]"); } private sealed class ApprovalTerminationStrategy : TerminationStrategy diff --git a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs b/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs similarity index 57% rename from dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs rename to dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs index dda6ea31df81..bf3ddbac47f8 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step8_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs @@ -8,38 +8,36 @@ namespace GettingStarted; /// -/// This example demonstrates that outside of initialization (and cleanup), using -/// is no different from -/// even with with a . +/// This example demonstrates similarity between using +/// and (see: Step 2). /// -public class Step8_OpenAIAssistant(ITestOutputHelper output) : BaseTest(output) +public class Step08_Assistant(ITestOutputHelper output) : BaseAgentsTest(output) { - protected override bool ForceOpenAI => false; - private const string HostName = "Host"; private const string HostInstructions = "Answer questions about the menu."; [Fact] - public async Task UseSingleOpenAIAssistantAgentAsync() + public async Task UseSingleAssistantAgentAsync() { // Define the agent OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: GetOpenAIConfiguration(), + config: this.GetOpenAIConfiguration(), new() { Instructions = HostInstructions, Name = HostName, ModelId = this.Model, + Metadata = AssistantSampleMetadata, }); // Initialize plugin and add to the agent's Kernel (same as direct Kernel usage). KernelPlugin plugin = KernelPluginFactory.CreateFromType(); agent.Kernel.Plugins.Add(plugin); - // Create a thread for the agent interaction. - string threadId = await agent.CreateThreadAsync(); + // Create a thread for the agent conversation. + string threadId = await agent.CreateThreadAsync(new OpenAIThreadCreationOptions { Metadata = AssistantSampleMetadata }); // Respond to user input try @@ -58,45 +56,32 @@ await OpenAIAssistantAgent.CreateAsync( // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - await agent.AddChatMessageAsync(threadId, new ChatMessageContent(AuthorRole.User, input)); - - Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + ChatMessageContent message = new(AuthorRole.User, input); + await agent.AddChatMessageAsync(threadId, message); + this.WriteAgentChatMessage(message); - await foreach (ChatMessageContent content in agent.InvokeAsync(threadId)) + await foreach (ChatMessageContent response in agent.InvokeAsync(threadId)) { - if (content.Role != AuthorRole.Tool) - { - Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); - } + this.WriteAgentChatMessage(response); } } } - private OpenAIServiceConfiguration GetOpenAIConfiguration() - => - this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); - private sealed class MenuPlugin { [KernelFunction, Description("Provides a list of specials from the menu.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")] - public string GetSpecials() - { - return @" -Special Soup: Clam Chowder -Special Salad: Cobb Salad -Special Drink: Chai Tea -"; - } + public string GetSpecials() => + """ + Special Soup: Clam Chowder + Special Salad: Cobb Salad + Special Drink: Chai Tea + """; [KernelFunction, Description("Provides the price of the requested menu item.")] public string GetItemPrice( [Description("The name of the menu item.")] - string menuItem) - { - return "$9.99"; - } + string menuItem) => + "$9.99"; } } diff --git a/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs b/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs new file mode 100644 index 000000000000..c0d2d2151a3f --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All rights reserved. +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; +using Resources; + +namespace GettingStarted; + +/// +/// Demonstrate providing image input to . +/// +public class Step09_Assistant_Vision(ITestOutputHelper output) : BaseAgentsTest(output) +{ + /// + /// Azure currently only supports message of type=text. + /// + protected override bool ForceOpenAI => true; + + [Fact] + public async Task UseSingleAssistantAgentAsync() + { +// Define the agent + OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + kernel: new(), + config, + new() + { + ModelId = this.Model, + Metadata = AssistantSampleMetadata, + }); + + // Upload an image + await using Stream imageStream = EmbeddedResource.ReadStream("cat.jpg")!; + string fileId = await OpenAIAssistantAgent.UploadFileAsync(config, imageStream, "cat.jpg"); + + // Create a thread for the agent conversation. + string threadId = await agent.CreateThreadAsync(new OpenAIThreadCreationOptions { Metadata = AssistantSampleMetadata }); + + // Respond to user input + try + { + // Refer to public image by url + await InvokeAgentAsync(CreateMessageWithImageUrl("Describe this image.", "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/New_york_times_square-terabass.jpg/1200px-New_york_times_square-terabass.jpg")); + await InvokeAgentAsync(CreateMessageWithImageUrl("What are is the main color in this image?", "https://upload.wikimedia.org/wikipedia/commons/5/56/White_shark.jpg")); + // Refer to uploaded image by file-id. + await InvokeAgentAsync(CreateMessageWithImageReference("Is there an animal in this image?", fileId)); + } + finally + { + await agent.DeleteThreadAsync(threadId); + await agent.DeleteAsync(); + await config.CreateFileClient().DeleteFileAsync(fileId); + } + + // Local function to invoke agent and display the conversation messages. + async Task InvokeAgentAsync(ChatMessageContent message) + { + await agent.AddChatMessageAsync(threadId, message); + this.WriteAgentChatMessage(message); + + await foreach (ChatMessageContent response in agent.InvokeAsync(threadId)) + { + this.WriteAgentChatMessage(response); + } + } + } + + private ChatMessageContent CreateMessageWithImageUrl(string input, string url) + => new(AuthorRole.User, [new TextContent(input), new ImageContent(new Uri(url))]); + + private ChatMessageContent CreateMessageWithImageReference(string input, string fileId) + => new(AuthorRole.User, [new TextContent(input), new FileReferenceContent(fileId)]); +} diff --git a/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs new file mode 100644 index 000000000000..596bd690fcc1 --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All rights reserved. +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace GettingStarted; + +/// +/// Demonstrate using code-interpreter on . +/// +public class Step10_AssistantTool_CodeInterpreter(ITestOutputHelper output) : BaseAgentsTest(output) +{ + [Fact] + public async Task UseCodeInterpreterToolWithAssistantAgentAsync() + { + // Define the agent + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + kernel: new(), + config: this.GetOpenAIConfiguration(), + new() + { + EnableCodeInterpreter = true, + ModelId = this.Model, + Metadata = AssistantSampleMetadata, + }); + + // Create a thread for the agent conversation. + string threadId = await agent.CreateThreadAsync(new OpenAIThreadCreationOptions { Metadata = AssistantSampleMetadata }); + + // Respond to user input + try + { + await InvokeAgentAsync("Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?"); + } + finally + { + await agent.DeleteThreadAsync(threadId); + await agent.DeleteAsync(); + } + + // Local function to invoke agent and display the conversation messages. + async Task InvokeAgentAsync(string input) + { + ChatMessageContent message = new(AuthorRole.User, input); + await agent.AddChatMessageAsync(threadId, message); + this.WriteAgentChatMessage(message); + + await foreach (ChatMessageContent response in agent.InvokeAsync(threadId)) + { + this.WriteAgentChatMessage(response); + } + } + } +} diff --git a/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs b/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs new file mode 100644 index 000000000000..5769db1178ea --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI.Files; +using OpenAI.VectorStores; +using Resources; + +namespace GettingStarted; + +/// +/// Demonstrate using code-interpreter on . +/// +public class Step11_AssistantTool_FileSearch(ITestOutputHelper output) : BaseAgentsTest(output) +{ + [Fact] + public async Task UseFileSearchToolWithAssistantAgentAsync() + { + // Define the agent + OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); + OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + kernel: new(), + config: this.GetOpenAIConfiguration(), + new() + { + EnableFileSearch = true, + ModelId = this.Model, + Metadata = AssistantSampleMetadata, + }); + + // Upload file - Using a table of fictional employees. + FileClient fileClient = config.CreateFileClient(); + await using Stream stream = EmbeddedResource.ReadStream("employees.pdf")!; + OpenAIFileInfo fileInfo = await fileClient.UploadFileAsync(stream, "employees.pdf", FileUploadPurpose.Assistants); + + // Create a vector-store + VectorStoreClient vectorStoreClient = config.CreateVectorStoreClient(); + VectorStore vectorStore = + await vectorStoreClient.CreateVectorStoreAsync( + new VectorStoreCreationOptions() + { + FileIds = [fileInfo.Id], + Metadata = { { AssistantSampleMetadataKey, bool.TrueString } } + }); + + // Create a thread associated with a vector-store for the agent conversation. + string threadId = + await agent.CreateThreadAsync( + new OpenAIThreadCreationOptions + { + VectorStoreId = vectorStore.Id, + Metadata = AssistantSampleMetadata, + }); + + // Respond to user input + try + { + await InvokeAgentAsync("Who is the youngest employee?"); + await InvokeAgentAsync("Who works in sales?"); + await InvokeAgentAsync("I have a customer request, who can help me?"); + } + finally + { + await agent.DeleteThreadAsync(threadId); + await agent.DeleteAsync(); + await vectorStoreClient.DeleteVectorStoreAsync(vectorStore); + await fileClient.DeleteFileAsync(fileInfo); + } + + // Local function to invoke agent and display the conversation messages. + async Task InvokeAgentAsync(string input) + { + ChatMessageContent message = new(AuthorRole.User, input); + await agent.AddChatMessageAsync(threadId, message); + this.WriteAgentChatMessage(message); + + await foreach (ChatMessageContent response in agent.InvokeAsync(threadId)) + { + this.WriteAgentChatMessage(response); + } + } + } +} diff --git a/dotnet/src/Agents/Abstractions/Logging/AgentChatLogMessages.cs b/dotnet/src/Agents/Abstractions/Logging/AgentChatLogMessages.cs index 314d68ce8cd8..b971fe2ce8d4 100644 --- a/dotnet/src/Agents/Abstractions/Logging/AgentChatLogMessages.cs +++ b/dotnet/src/Agents/Abstractions/Logging/AgentChatLogMessages.cs @@ -61,7 +61,7 @@ public static partial void LogAgentChatAddingMessages( [LoggerMessage( EventId = 0, Level = LogLevel.Information, - Message = "[{MethodName}] Adding Messages: {MessageCount}.")] + Message = "[{MethodName}] Added Messages: {MessageCount}.")] public static partial void LogAgentChatAddedMessages( this ILogger logger, string methodName, diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantToolResourcesFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantToolResourcesFactory.cs index e7566a5db4f8..6874e1d21755 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantToolResourcesFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantToolResourcesFactory.cs @@ -19,18 +19,18 @@ internal static class AssistantToolResourcesFactory /// An optionallist of file-identifiers for the 'code_interpreter' tool. public static ToolResources? GenerateToolResources(string? vectorStoreId, IReadOnlyList? codeInterpreterFileIds) { - bool hasFileSearch = !string.IsNullOrWhiteSpace(vectorStoreId); + bool hasVectorStore = !string.IsNullOrWhiteSpace(vectorStoreId); bool hasCodeInterpreterFiles = (codeInterpreterFileIds?.Count ?? 0) > 0; ToolResources? toolResources = null; - if (hasFileSearch || hasCodeInterpreterFiles) + if (hasVectorStore || hasCodeInterpreterFiles) { toolResources = new ToolResources() { FileSearch = - hasFileSearch ? + hasVectorStore ? new FileSearchToolResources() { VectorStoreIds = [vectorStoreId!], diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 7d542a30ab80..fc775a4c4130 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; @@ -9,6 +10,7 @@ using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using OpenAI; using OpenAI.Assistants; +using OpenAI.Files; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -53,7 +55,7 @@ public sealed class OpenAIAssistantAgent : KernelAgent /// Define a new . /// /// The containing services, plugins, and other state for use throughout the operation. - /// Configuration for accessing the Assistants API service. + /// Configuration for accessing the API service. /// The assistant definition. /// The to monitor for cancellation requests. The default is . /// An instance @@ -86,7 +88,7 @@ public static async Task CreateAsync( /// /// Retrieve a list of assistant definitions: . /// - /// Configuration for accessing the Assistants API service. + /// Configuration for accessing the API service. /// The to monitor for cancellation requests. The default is . /// An list of objects. public static async IAsyncEnumerable ListDefinitionsAsync( @@ -107,7 +109,7 @@ public static async IAsyncEnumerable ListDefinitionsA /// Retrieve a by identifier. /// /// The containing services, plugins, and other state for use throughout the operation. - /// Configuration for accessing the Assistants API service. + /// Configuration for accessing the API service. /// The agent identifier /// The to monitor for cancellation requests. The default is . /// An instance @@ -164,6 +166,26 @@ public async Task DeleteThreadAsync( return await this._client.DeleteThreadAsync(threadId, cancellationToken).ConfigureAwait(false); } + /// + /// Uploads an file for the purpose of using with assistant. + /// + /// Configuration for accessing the API service. + /// The content to upload + /// The name of the file + /// The to monitor for cancellation requests. The default is . + /// The file identifier + /// + /// Use the directly for more advanced file operations. + /// + public static async Task UploadFileAsync(OpenAIServiceConfiguration config, Stream stream, string name, CancellationToken cancellationToken = default) + { + FileClient client = config.CreateFileClient(); + + OpenAIFileInfo fileInfo = await client.UploadFileAsync(stream, name, FileUploadPurpose.Assistants, cancellationToken).ConfigureAwait(false); + + return fileInfo.Id; + } + /// /// Adds a message to the specified thread. /// @@ -303,7 +325,7 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod } IReadOnlyList? fileIds = (IReadOnlyList?)model.ToolResources?.CodeInterpreter?.FileIds; - string? vectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.Single(); + string? vectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.SingleOrDefault(); bool enableJsonResponse = model.ResponseFormat is not null && model.ResponseFormat == AssistantResponseFormat.JsonObject; return @@ -315,6 +337,7 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod Instructions = model.Instructions, CodeInterpreterFileIds = fileIds, EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), + EnableFileSearch = model.Tools.Any(t => t is FileSearchToolDefinition), Metadata = model.Metadata, ModelId = model.Model, EnableJsonResponse = enableJsonResponse, @@ -333,7 +356,10 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss Description = definition.Description, Instructions = definition.Instructions, Name = definition.Name, - ToolResources = AssistantToolResourcesFactory.GenerateToolResources(definition.VectorStoreId, definition.EnableCodeInterpreter ? definition.CodeInterpreterFileIds : null), + ToolResources = + AssistantToolResourcesFactory.GenerateToolResources( + definition.EnableFileSearch ? definition.VectorStoreId : null, + definition.EnableCodeInterpreter ? definition.CodeInterpreterFileIds : null), ResponseFormat = definition.EnableJsonResponse ? AssistantResponseFormat.JsonObject : AssistantResponseFormat.Auto, Temperature = definition.Temperature, NucleusSamplingFactor = definition.TopP, @@ -358,7 +384,7 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss assistantCreationOptions.Tools.Add(ToolDefinition.CreateCodeInterpreter()); } - if (!string.IsNullOrWhiteSpace(definition.VectorStoreId)) + if (definition.EnableFileSearch) { assistantCreationOptions.Tools.Add(ToolDefinition.CreateFileSearch()); } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index cb8cb6c84734..b79101e98434 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -43,6 +43,11 @@ public sealed class OpenAIAssistantDefinition /// public bool EnableCodeInterpreter { get; init; } + /// + /// Set if file-search is enabled. + /// + public bool EnableFileSearch { get; init; } + /// /// Set if json response-format is enabled. /// @@ -71,7 +76,7 @@ public sealed class OpenAIAssistantDefinition public float? TopP { get; init; } /// - /// Enables file-search if specified. + /// Requires file-search if specified. /// public string? VectorStoreId { get; init; } diff --git a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs new file mode 100644 index 000000000000..34211fb97661 --- /dev/null +++ b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.ObjectModel; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Microsoft.SemanticKernel.ChatCompletion; + +/// +/// Base class for samples that demonstrate the usage of agents. +/// +public abstract class BaseAgentsTest(ITestOutputHelper output) : BaseTest(output) +{ + /// + /// Metadata key to indicate the assistant as created for a sample. + /// + protected const string AssistantSampleMetadataKey = "sksample"; + + /// + /// Metadata to indicate the assistant as created for a sample. + /// + /// + /// While the samples do attempt delete the assistants it creates, it is possible + /// that some assistants may remain. This metadata can be used to identify and sample + /// agents for clean-up. + /// + protected static readonly ReadOnlyDictionary AssistantSampleMetadata = + new(new Dictionary + { + { AssistantSampleMetadataKey, bool.TrueString } + }); + + /// + /// Provide a according to the configuration settings. + /// + protected OpenAIServiceConfiguration GetOpenAIConfiguration() + => + this.UseOpenAIConfig ? + OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : + OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + + /// + /// %%% REMOVE ??? + /// + protected async Task WriteAgentResponseAsync(IAsyncEnumerable messages, ChatHistory? history = null) + { + await foreach (ChatMessageContent message in messages) + { + if (history != null && + !message.Items.Any(i => i is FunctionCallContent || i is FunctionResultContent)) + { + history.Add(message); + } + + this.WriteAgentChatMessage(message); + } + } + + /// + /// Common method to write formatted agent chat content to the console. + /// + protected void WriteAgentChatMessage(ChatMessageContent message) + { + // Include ChatMessageContent.AuthorName in output, if present. + string authorExpression = message.Role == AuthorRole.User ? string.Empty : $" - {message.AuthorName ?? "*"}"; + // Include TextContent (via ChatMessageContent.Content), if present. + string contentExpression = string.IsNullOrWhiteSpace(message.Content) ? string.Empty : message.Content; + bool isCode = message.Metadata?.ContainsKey(OpenAIAssistantAgent.CodeInterpreterMetadataKey) ?? false; + string codeMarker = isCode ? "\n [CODE]\n" : " "; + Console.WriteLine($"\n# {message.Role}{authorExpression}:{codeMarker}{contentExpression}"); + + // Provide visibility for inner content (that isn't TextContent). + foreach (KernelContent item in message.Items) + { + if (item is AnnotationContent annotation) + { + Console.WriteLine($" [{item.GetType().Name}] {annotation.Quote}: File #{annotation.FileId}"); + //BinaryData fileContent = await fileClient.DownloadFileAsync(annotation.FileId!); // %%% COMMON + //Console.WriteLine($"\n{Encoding.Default.GetString(fileContent.ToArray())}"); + //Console.WriteLine($"\t[{item.GetType().Name}] {functionCall.Id}"); + } + if (item is FileReferenceContent fileReference) + { + Console.WriteLine($" [{item.GetType().Name}] File #{fileReference.FileId}"); + //BinaryData fileContent = await fileClient.DownloadFileAsync(fileReference.FileId!); // %%% COMMON + //string filePath = Path.ChangeExtension(Path.GetTempFileName(), ".png"); + //await File.WriteAllBytesAsync($"{filePath}.png", fileContent.ToArray()); + //Console.WriteLine($"\t* Local path - {filePath}"); + } + if (item is ImageContent image) + { + Console.WriteLine($" [{item.GetType().Name}] {image.Uri?.ToString() ?? image.DataUri ?? $"{image.Data?.Length} bytes"}"); + } + else if (item is FunctionCallContent functionCall) + { + Console.WriteLine($" [{item.GetType().Name}] {functionCall.Id}"); + } + else if (item is FunctionResultContent functionResult) + { + Console.WriteLine($" [{item.GetType().Name}] {functionResult.CallId}"); + } + } + } + + //private async Task DownloadFileContentAsync(string fileId) + //{ + // string filePath = Path.Combine(Environment.CurrentDirectory, $"{fileId}.jpg"); + // BinaryData content = await fileClient.DownloadFileAsync(fileId); + // File.WriteAllBytes(filePath, content.ToArray()); + + // Process.Start( + // new ProcessStartInfo + // { + // FileName = "cmd.exe", + // Arguments = $"/C start {filePath}" + // }); + + // return filePath; + //} +} diff --git a/dotnet/src/InternalUtilities/samples/SamplesInternalUtilities.props b/dotnet/src/InternalUtilities/samples/SamplesInternalUtilities.props index 0c47e16d8d93..df5205c40a82 100644 --- a/dotnet/src/InternalUtilities/samples/SamplesInternalUtilities.props +++ b/dotnet/src/InternalUtilities/samples/SamplesInternalUtilities.props @@ -1,5 +1,8 @@ - + + \ No newline at end of file From 9fd6b92165950c3b0ec16f4886ab90aed5626045 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 6 Aug 2024 16:08:27 -0700 Subject: [PATCH 086/121] Unit-tests --- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 3 ++- .../OpenAI/OpenAIAssistantAgentTests.cs | 22 +++++++++++++++++-- .../OpenAI/OpenAIAssistantDefinitionTests.cs | 3 +++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index fc775a4c4130..c55b0dd42b91 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -343,7 +344,7 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod EnableJsonResponse = enableJsonResponse, TopP = model.NucleusSamplingFactor, Temperature = model.Temperature, - VectorStoreId = vectorStoreId, + VectorStoreId = string.IsNullOrWhiteSpace(vectorStoreId) ? null : vectorStoreId, ExecutionOptions = options, }; } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index de99478eea51..59073cdb1802 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -95,6 +95,23 @@ public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterFilesAsyn await this.VerifyAgentCreationAsync(definition); } + /// + /// Verify the invocation and response of + /// for an agent with a file-search and no vector-store + /// + [Fact] + public async Task VerifyOpenAIAssistantAgentCreationWithFileSearchAsync() + { + OpenAIAssistantDefinition definition = + new() + { + ModelId = "testmodel", + EnableFileSearch = true, + }; + + await this.VerifyAgentCreationAsync(definition); + } + /// /// Verify the invocation and response of /// for an agent with a vector-store-id (for file-search). @@ -106,6 +123,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithVectorStoreAsync() new() { ModelId = "testmodel", + EnableFileSearch = true, VectorStoreId = "#vs1", }; @@ -563,7 +581,7 @@ private static void ValidateAgentDefinition(OpenAIAssistantAgent agent, OpenAIAs Assert.Equal(hasCodeInterpreter, agent.Tools.OfType().Any()); bool hasFileSearch = false; - if (!string.IsNullOrWhiteSpace(sourceDefinition.VectorStoreId)) + if (sourceDefinition.EnableFileSearch) { hasFileSearch = true; ++expectedToolCount; @@ -667,7 +685,7 @@ public static string CreateAgentPayload(OpenAIAssistantDefinition definition) bool hasCodeInterpreter = definition.EnableCodeInterpreter; bool hasCodeInterpreterFiles = (definition.CodeInterpreterFileIds?.Count ?? 0) > 0; - bool hasFileSearch = !string.IsNullOrWhiteSpace(definition.VectorStoreId); + bool hasFileSearch = definition.EnableFileSearch; if (!hasCodeInterpreter && !hasFileSearch) { builder.AppendLine(@" ""tools"": [],"); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index a91a043febfb..08ff94ac0c5b 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -27,6 +27,7 @@ public void VerifyOpenAIAssistantDefinitionInitialState() Assert.Null(definition.ExecutionOptions); Assert.Null(definition.Temperature); Assert.Null(definition.TopP); + Assert.False(definition.EnableFileSearch); Assert.Null(definition.VectorStoreId); Assert.Null(definition.CodeInterpreterFileIds); Assert.False(definition.EnableCodeInterpreter); @@ -47,6 +48,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() ModelId = "testmodel", Instructions = "testinstructions", Description = "testdescription", + EnableFileSearch = true, VectorStoreId = "#vs", Metadata = new Dictionary() { { "a", "1" } }, Temperature = 2, @@ -69,6 +71,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Assert.Equal("testmodel", definition.ModelId); Assert.Equal("testinstructions", definition.Instructions); Assert.Equal("testdescription", definition.Description); + Assert.True(definition.EnableFileSearch); Assert.Equal("#vs", definition.VectorStoreId); Assert.Equal(2, definition.Temperature); Assert.Equal(0, definition.TopP); From 5b54de481c2de9f22bce75ce5391f254d7187574 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 6 Aug 2024 17:04:01 -0700 Subject: [PATCH 087/121] Serialization tests and attributes --- .../Internal/AssistantRunOptionsFactory.cs | 1 - .../OpenAI/OpenAIAssistantDefinition.cs | 13 ++++++ .../OpenAIAssistantInvocationOptions.cs | 12 +++++ .../OpenAI/OpenAIThreadCreationOptions.cs | 5 ++ .../UnitTests/OpenAI/AssertCollection.cs | 46 +++++++++++++++++++ .../OpenAI/OpenAIAssistantDefinitionTests.cs | 32 +++++++++++++ .../OpenAIAssistantInvocationOptionsTests.cs | 24 ++++++++++ .../OpenAIThreadCreationOptionsTests.cs | 28 +++++++++-- 8 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/AssertCollection.cs diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs index 03f0b5ca067a..981c646254af 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs @@ -31,7 +31,6 @@ public static RunCreationOptions GenerateOptions(OpenAIAssistantDefinition defin ParallelToolCallsEnabled = ResolveExecutionSetting(invocationOptions?.ParallelToolCallsEnabled, definition.ExecutionOptions?.ParallelToolCallsEnabled), ResponseFormat = ResolveExecutionSetting(invocationOptions?.EnableJsonResponse, definition.EnableJsonResponse) ?? false ? AssistantResponseFormat.JsonObject : null, Temperature = ResolveExecutionSetting(invocationOptions?.Temperature, definition.Temperature), - //ToolConstraint - Not Currently Supported (https://github.com/microsoft/semantic-kernel/issues/6795) TruncationStrategy = truncationMessageCount.HasValue ? RunTruncationStrategy.CreateLastMessagesStrategy(truncationMessageCount.Value) : null, }; diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index b79101e98434..f52d468c8d6f 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -16,6 +17,7 @@ public sealed class OpenAIAssistantDefinition /// /// The description of the assistant. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Description { get; init; } /// @@ -26,31 +28,37 @@ public sealed class OpenAIAssistantDefinition /// /// The system instructions for the assistant to use. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Instructions { get; init; } /// /// The name of the assistant. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Name { get; init; } /// /// Optional file-ids made available to the code_interpreter tool, if enabled. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IReadOnlyList? CodeInterpreterFileIds { get; init; } /// /// Set if code-interpreter is enabled. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool EnableCodeInterpreter { get; init; } /// /// Set if file-search is enabled. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool EnableFileSearch { get; init; } /// /// Set if json response-format is enabled. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool EnableJsonResponse { get; init; } /// @@ -58,11 +66,13 @@ public sealed class OpenAIAssistantDefinition /// storing additional information about that object in a structured format.Keys /// may be up to 64 characters in length and values may be up to 512 characters in length. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IReadOnlyDictionary? Metadata { get; init; } /// /// The sampling temperature to use, between 0 and 2. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public float? Temperature { get; init; } /// @@ -73,15 +83,18 @@ public sealed class OpenAIAssistantDefinition /// /// Recommended to set this or temperature but not both. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public float? TopP { get; init; } /// /// Requires file-search if specified. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? VectorStoreId { get; init; } /// /// Default execution options for each agent invocation. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public OpenAIAssistantExecutionOptions? ExecutionOptions { get; init; } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs index 1aa0c3ffa745..0653c83a13e2 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -14,47 +15,56 @@ public sealed class OpenAIAssistantInvocationOptions /// /// Override the AI model targeted by the agent. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? ModelName { get; init; } /// /// Set if code_interpreter tool is enabled. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool EnableCodeInterpreter { get; init; } /// /// Set if file_search tool is enabled. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool EnableFileSearch { get; init; } /// /// Set if json response-format is enabled. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? EnableJsonResponse { get; init; } /// /// The maximum number of completion tokens that may be used over the course of the run. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? MaxCompletionTokens { get; init; } /// /// The maximum number of prompt tokens that may be used over the course of the run. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? MaxPromptTokens { get; init; } /// /// Enables parallel function calling during tool use. Enabled by default. /// Use this property to disable. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? ParallelToolCallsEnabled { get; init; } /// /// When set, the thread will be truncated to the N most recent messages in the thread. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? TruncationMessageCount { get; init; } /// /// The sampling temperature to use, between 0 and 2. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public float? Temperature { get; init; } /// @@ -65,6 +75,7 @@ public sealed class OpenAIAssistantInvocationOptions /// /// Recommended to set this or temperature but not both. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public float? TopP { get; init; } /// @@ -72,5 +83,6 @@ public sealed class OpenAIAssistantInvocationOptions /// storing additional information about that object in a structured format.Keys /// may be up to 64 characters in length and values may be up to 512 characters in length. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IReadOnlyDictionary? Metadata { get; init; } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs index d2e8eb012e17..3f39c43d03dc 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -11,16 +12,19 @@ public sealed class OpenAIThreadCreationOptions /// /// Optional file-ids made available to the code_interpreter tool, if enabled. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IReadOnlyList? CodeInterpreterFileIds { get; init; } /// /// Optional messages to initialize thread with.. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IReadOnlyList? Messages { get; init; } /// /// Enables file-search if specified. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? VectorStoreId { get; init; } /// @@ -28,5 +32,6 @@ public sealed class OpenAIThreadCreationOptions /// storing additional information about that object in a structured format.Keys /// may be up to 64 characters in length and values may be up to 512 characters in length. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IReadOnlyDictionary? Metadata { get; init; } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/AssertCollection.cs b/dotnet/src/Agents/UnitTests/OpenAI/AssertCollection.cs new file mode 100644 index 000000000000..cd51c736ac18 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/AssertCollection.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI; + +internal static class AssertCollection +{ + public static void Equal(IReadOnlyList? source, IReadOnlyList? target, Func? adapter = null) + { + if (source == null) + { + Assert.Null(target); + return; + } + + Assert.NotNull(target); + Assert.Equal(source.Count, target.Count); + + adapter ??= (x) => x; + + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(adapter(source[i]), adapter(target[i])); + } + } + + public static void Equal(IReadOnlyDictionary? source, IReadOnlyDictionary? target) + { + if (source == null) + { + Assert.Null(target); + return; + } + + Assert.NotNull(target); + Assert.Equal(source.Count, target.Count); + + foreach ((TKey key, TValue value) in source) + { + Assert.True(target.TryGetValue(key, out TValue? targetValue)); + Assert.Equal(value, targetValue); + } + } +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index 08ff94ac0c5b..fa8d903419a5 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Text.Json; using Microsoft.SemanticKernel.Agents.OpenAI; using Xunit; @@ -32,6 +33,8 @@ public void VerifyOpenAIAssistantDefinitionInitialState() Assert.Null(definition.CodeInterpreterFileIds); Assert.False(definition.EnableCodeInterpreter); Assert.False(definition.EnableJsonResponse); + + ValidateSerialization(definition); } /// @@ -84,5 +87,34 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Assert.Single(definition.CodeInterpreterFileIds); Assert.True(definition.EnableCodeInterpreter); Assert.True(definition.EnableJsonResponse); + + ValidateSerialization(definition); + } + + private static void ValidateSerialization(OpenAIAssistantDefinition source) + { + string json = JsonSerializer.Serialize(source); + + OpenAIAssistantDefinition? target = JsonSerializer.Deserialize(json); + + Assert.NotNull(target); + Assert.Equal(source.Id, target.Id); + Assert.Equal(source.Name, target.Name); + Assert.Equal(source.ModelId, target.ModelId); + Assert.Equal(source.Instructions, target.Instructions); + Assert.Equal(source.Description, target.Description); + Assert.Equal(source.EnableFileSearch, target.EnableFileSearch); + Assert.Equal(source.VectorStoreId, target.VectorStoreId); + Assert.Equal(source.Temperature, target.Temperature); + Assert.Equal(source.TopP, target.TopP); + Assert.Equal(source.EnableFileSearch, target.EnableFileSearch); + Assert.Equal(source.VectorStoreId, target.VectorStoreId); + Assert.Equal(source.EnableCodeInterpreter, target.EnableCodeInterpreter); + Assert.Equal(source.ExecutionOptions?.MaxCompletionTokens, target.ExecutionOptions?.MaxCompletionTokens); + Assert.Equal(source.ExecutionOptions?.MaxPromptTokens, target.ExecutionOptions?.MaxPromptTokens); + Assert.Equal(source.ExecutionOptions?.TruncationMessageCount, target.ExecutionOptions?.TruncationMessageCount); + Assert.Equal(source.ExecutionOptions?.ParallelToolCallsEnabled, target.ExecutionOptions?.ParallelToolCallsEnabled); + AssertCollection.Equal(source.CodeInterpreterFileIds, target.CodeInterpreterFileIds); + AssertCollection.Equal(source.Metadata, target.Metadata); } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs index 1d63a6e2e9c0..692dee85f1aa 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Text.Json; using Microsoft.SemanticKernel.Agents.OpenAI; using Xunit; @@ -29,6 +30,8 @@ public void OpenAIAssistantInvocationOptionsInitialState() Assert.Null(options.EnableJsonResponse); Assert.False(options.EnableCodeInterpreter); Assert.False(options.EnableFileSearch); + + ValidateSerialization(options); } /// @@ -64,5 +67,26 @@ public void OpenAIAssistantInvocationOptionsAssignment() Assert.True(options.EnableCodeInterpreter); Assert.True(options.EnableJsonResponse); Assert.True(options.EnableFileSearch); + + ValidateSerialization(options); + } + + private static void ValidateSerialization(OpenAIAssistantInvocationOptions source) + { + string json = JsonSerializer.Serialize(source); + + OpenAIAssistantInvocationOptions? target = JsonSerializer.Deserialize(json); + + Assert.NotNull(target); + Assert.Equal(source.ModelName, target.ModelName); + Assert.Equal(source.Temperature, target.Temperature); + Assert.Equal(source.TopP, target.TopP); + Assert.Equal(source.MaxCompletionTokens, target.MaxCompletionTokens); + Assert.Equal(source.MaxPromptTokens, target.MaxPromptTokens); + Assert.Equal(source.TruncationMessageCount, target.TruncationMessageCount); + Assert.Equal(source.EnableCodeInterpreter, target.EnableCodeInterpreter); + Assert.Equal(source.EnableJsonResponse, target.EnableJsonResponse); + Assert.Equal(source.EnableFileSearch, target.EnableFileSearch); + AssertCollection.Equal(source.Metadata, target.Metadata); } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs index d4e680efee09..496f429f0793 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Text.Json; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; @@ -24,6 +25,8 @@ public void OpenAIThreadCreationOptionsInitialState() Assert.Null(options.Metadata); Assert.Null(options.VectorStoreId); Assert.Null(options.CodeInterpreterFileIds); + + ValidateSerialization(options); } /// @@ -32,7 +35,7 @@ public void OpenAIThreadCreationOptionsInitialState() [Fact] public void OpenAIThreadCreationOptionsAssignment() { - OpenAIThreadCreationOptions definition = + OpenAIThreadCreationOptions options = new() { Messages = [new ChatMessageContent(AuthorRole.User, "test")], @@ -41,9 +44,24 @@ public void OpenAIThreadCreationOptionsAssignment() CodeInterpreterFileIds = ["file1"], }; - Assert.Single(definition.Messages); - Assert.Single(definition.Metadata); - Assert.Equal("#vs", definition.VectorStoreId); - Assert.Single(definition.CodeInterpreterFileIds); + Assert.Single(options.Messages); + Assert.Single(options.Metadata); + Assert.Equal("#vs", options.VectorStoreId); + Assert.Single(options.CodeInterpreterFileIds); + + ValidateSerialization(options); + } + + private static void ValidateSerialization(OpenAIThreadCreationOptions source) + { + string json = JsonSerializer.Serialize(source); + + OpenAIThreadCreationOptions? target = JsonSerializer.Deserialize(json); + + Assert.NotNull(target); + Assert.Equal(source.VectorStoreId, target.VectorStoreId); + AssertCollection.Equal(source.CodeInterpreterFileIds, target.CodeInterpreterFileIds); + AssertCollection.Equal(source.Messages, target.Messages, m => m.Items.Count); // ChatMessageContent already validated for deep serialization + AssertCollection.Equal(source.Metadata, target.Metadata); } } From d029bd49e8820420567c496fd60b02ad32227aae Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 6 Aug 2024 17:09:59 -0700 Subject: [PATCH 088/121] Image DataUrl support --- .../src/Agents/OpenAI/Internal/AssistantMessageFactory.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs index 8b65961e2677..4c31a1bcf291 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs @@ -52,11 +52,7 @@ public static IEnumerable GetMessageContents(ChatMessageContent } else if (string.IsNullOrWhiteSpace(imageContent.DataUri)) { - //SDK BUG - BAD SIGNATURE (https://github.com/openai/openai-dotnet/issues/135) - // URI does not accept the format used for `DataUri` - // Approach is inefficient anyway... - //yield return MessageContent.FromImageUrl(new Uri(imageContent.DataUri!)); - throw new KernelException($"{nameof(ImageContent.DataUri)} not supported for assistant input."); + yield return MessageContent.FromImageUrl(new(imageContent.DataUri!)); } } else if (content is FileReferenceContent fileContent) From a7cdba519afdcefbc146b241bad515d069d0acc9 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 6 Aug 2024 17:12:49 -0700 Subject: [PATCH 089/121] Remove duplicate sample --- .../Agents/OpenAIAssistant_FileSearch.cs | 85 ------------------- 1 file changed, 85 deletions(-) delete mode 100644 dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs deleted file mode 100644 index c73934421a7c..000000000000 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileSearch.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.ChatCompletion; -using OpenAI.Files; -using OpenAI.VectorStores; -using Resources; - -namespace Agents; - -/// -/// Demonstrate using retrieval on . -/// -public class OpenAIAssistant_FileSearch(ITestOutputHelper output) : BaseAgentsTest(output) -{ - /// - /// Retrieval tool not supported on Azure OpenAI. - /// - protected override bool ForceOpenAI => true; - - [Fact] - public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync() - { - OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); - - FileClient fileClient = config.CreateFileClient(); - - OpenAIFileInfo uploadFile = - await fileClient.UploadFileAsync( - new BinaryData(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!), - "travelinfo.txt", - FileUploadPurpose.Assistants); - - VectorStoreClient vectorStoreClient = config.CreateVectorStoreClient(); - VectorStoreCreationOptions vectorStoreOptions = - new() - { - FileIds = [uploadFile.Id] - }; - VectorStore vectorStore = await vectorStoreClient.CreateVectorStoreAsync(vectorStoreOptions); - - // Define the agent - OpenAIAssistantAgent agent = - await OpenAIAssistantAgent.CreateAsync( - kernel: new(), - config, - new() - { - VectorStoreId = vectorStore.Id, - ModelId = this.Model, - Metadata = AssistantSampleMetadata, - }); - - // Create a chat for agent interaction. - AgentGroupChat chat = new(); - - // Respond to user input - try - { - await InvokeAgentAsync("Where did sam go?"); - await InvokeAgentAsync("When does the flight leave Seattle?"); - await InvokeAgentAsync("What is the hotel contact info at the destination?"); - } - finally - { - await agent.DeleteAsync(); - await vectorStoreClient.DeleteVectorStoreAsync(vectorStore); - await fileClient.DeleteFileAsync(uploadFile); - } - - // Local function to invoke agent and display the conversation messages. - async Task InvokeAgentAsync(string input) - { - ChatMessageContent message = new(AuthorRole.User, input); - chat.AddChatMessage(new(AuthorRole.User, input)); - this.WriteAgentChatMessage(message); - - await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) - { - this.WriteAgentChatMessage(response); - } - } - } -} From 13f19867a17c6d0c12fa18c48c0e92df42cdad6d Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 6 Aug 2024 18:23:10 -0700 Subject: [PATCH 090/121] Update concept samples --- .../Agents/ChatCompletion_Streaming.cs | 4 +- .../Agents/ComplexChat_NestedShopper.cs | 7 +- .../Concepts/Agents/MixedChat_Files.cs | 10 +-- .../Concepts/Agents/MixedChat_Images.cs | 6 +- .../Agents/OpenAIAssistant_ChartMaker.cs | 6 +- .../OpenAIAssistant_FileManipulation.cs | 8 +- .../samples/AgentUtilities/BaseAgentsTest.cs | 89 +++++++++++-------- 7 files changed, 61 insertions(+), 69 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs index 7e74e425536c..071acc59a3f4 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs @@ -91,8 +91,8 @@ private async Task InvokeAgentAsync(ChatCompletionAgent agent, ChatHistory chat, { // Display full response and capture in chat history ChatMessageContent response = new(AuthorRole.Assistant, builder.ToString()) { AuthorName = agent.Name }; - chat.Add(message); - this.WriteAgentChatMessage(message); + chat.Add(response); + this.WriteAgentChatMessage(response); } } diff --git a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs index e12dee448370..0d7b27917d78 100644 --- a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs +++ b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -using Azure; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; @@ -16,8 +15,6 @@ namespace Agents; /// public class ComplexChat_NestedShopper(ITestOutputHelper output) : BaseAgentsTest(output) { - protected override bool ForceOpenAI => true; - private const string InternalLeaderName = "InternalLeader"; private const string InternalLeaderInstructions = """ @@ -157,7 +154,7 @@ public async Task NestedChatWithAggregatorAgentAsync() await foreach (ChatMessageContent message in chat.GetChatMessagesAsync(personalShopperAgent).Reverse()) { - WriteAgentChatMessage(message); + this.WriteAgentChatMessage(message); } async Task InvokeChatAsync(string input) @@ -168,7 +165,7 @@ async Task InvokeChatAsync(string input) await foreach (ChatMessageContent response in chat.InvokeAsync(personalShopperAgent)) { - WriteAgentChatMessage(response); + this.WriteAgentChatMessage(response); } Console.WriteLine($"\n# IS COMPLETE: {chat.IsComplete}"); diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs index f14ad8d1222d..2b80ff5cee6f 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs @@ -14,11 +14,6 @@ namespace Agents; /// public class MixedChat_Files(ITestOutputHelper output) : BaseAgentsTest(output) { - /// - /// Target OpenAI services. - /// - protected override bool ForceOpenAI => true; - private const string SummaryInstructions = "Summarize the entire conversation for the user in natural language."; [Fact] @@ -43,8 +38,8 @@ await OpenAIAssistantAgent.CreateAsync( config, new() { - EnableCodeInterpreter = true, // Enable code-interpreter - CodeInterpreterFileIds = [uploadFile.Id], // Associate uploaded file with assistant + EnableCodeInterpreter = true, + CodeInterpreterFileIds = [uploadFile.Id], // Associate uploaded file with assistant code-interpreter ModelId = this.Model, Metadata = AssistantSampleMetadata, }); @@ -89,6 +84,7 @@ async Task InvokeAgentAsync(Agent agent, string? input = null) await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { this.WriteAgentChatMessage(response); + await this.DownloadResponseContentAsync(fileClient, response); } } } diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs index e1bf8b2b7068..25f94de4c11e 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs @@ -13,11 +13,6 @@ namespace Agents; /// public class MixedChat_Images(ITestOutputHelper output) : BaseAgentsTest(output) { - /// - /// Target OpenAI services. - /// - protected override bool ForceOpenAI => true; - private const string AnalystName = "Analyst"; private const string AnalystInstructions = "Create charts as requested without explanation."; @@ -97,6 +92,7 @@ async Task InvokeAgentAsync(Agent agent, string? input = null) await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { this.WriteAgentChatMessage(response); + await this.DownloadResponseImageAsync(fileClient, response); } } } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index af8990096a65..512fa5bbb0a2 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -13,11 +13,6 @@ namespace Agents; /// public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseAgentsTest(output) { - /// - /// Target Open AI services. - /// - protected override bool ForceOpenAI => true; - private const string AgentName = "ChartMaker"; private const string AgentInstructions = "Create charts as requested without explanation."; @@ -78,6 +73,7 @@ async Task InvokeAgentAsync(string input) await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { this.WriteAgentChatMessage(response); + await this.DownloadResponseImageAsync(fileClient, response); } } } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index 0f92b31ffb04..f25e17600b99 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -13,11 +13,6 @@ namespace Agents; /// public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseAgentsTest(output) { - /// - /// Target OpenAI services. - /// - protected override bool ForceOpenAI => true; - [Fact] public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() { @@ -38,8 +33,8 @@ await OpenAIAssistantAgent.CreateAsync( config, new() { + EnableCodeInterpreter = true, CodeInterpreterFileIds = [uploadFile.Id], - EnableCodeInterpreter = true, // Enable code-interpreter ModelId = this.Model, Metadata = AssistantSampleMetadata, }); @@ -70,6 +65,7 @@ async Task InvokeAgentAsync(string input) await foreach (ChatMessageContent response in chat.InvokeAsync(agent)) { this.WriteAgentChatMessage(response); + await this.DownloadResponseContentAsync(fileClient, response); } } } diff --git a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs index 34211fb97661..7bfbd3fd4df0 100644 --- a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs +++ b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.ObjectModel; +using System.Diagnostics; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.OpenAI; using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI.Files; /// /// Base class for samples that demonstrate the usage of agents. @@ -37,23 +39,6 @@ protected OpenAIServiceConfiguration GetOpenAIConfiguration() OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); - /// - /// %%% REMOVE ??? - /// - protected async Task WriteAgentResponseAsync(IAsyncEnumerable messages, ChatHistory? history = null) - { - await foreach (ChatMessageContent message in messages) - { - if (history != null && - !message.Items.Any(i => i is FunctionCallContent || i is FunctionResultContent)) - { - history.Add(message); - } - - this.WriteAgentChatMessage(message); - } - } - /// /// Common method to write formatted agent chat content to the console. /// @@ -73,19 +58,12 @@ protected void WriteAgentChatMessage(ChatMessageContent message) if (item is AnnotationContent annotation) { Console.WriteLine($" [{item.GetType().Name}] {annotation.Quote}: File #{annotation.FileId}"); - //BinaryData fileContent = await fileClient.DownloadFileAsync(annotation.FileId!); // %%% COMMON - //Console.WriteLine($"\n{Encoding.Default.GetString(fileContent.ToArray())}"); - //Console.WriteLine($"\t[{item.GetType().Name}] {functionCall.Id}"); } - if (item is FileReferenceContent fileReference) + else if (item is FileReferenceContent fileReference) { Console.WriteLine($" [{item.GetType().Name}] File #{fileReference.FileId}"); - //BinaryData fileContent = await fileClient.DownloadFileAsync(fileReference.FileId!); // %%% COMMON - //string filePath = Path.ChangeExtension(Path.GetTempFileName(), ".png"); - //await File.WriteAllBytesAsync($"{filePath}.png", fileContent.ToArray()); - //Console.WriteLine($"\t* Local path - {filePath}"); } - if (item is ImageContent image) + else if (item is ImageContent image) { Console.WriteLine($" [{item.GetType().Name}] {image.Uri?.ToString() ?? image.DataUri ?? $"{image.Data?.Length} bytes"}"); } @@ -100,19 +78,52 @@ protected void WriteAgentChatMessage(ChatMessageContent message) } } - //private async Task DownloadFileContentAsync(string fileId) - //{ - // string filePath = Path.Combine(Environment.CurrentDirectory, $"{fileId}.jpg"); - // BinaryData content = await fileClient.DownloadFileAsync(fileId); - // File.WriteAllBytes(filePath, content.ToArray()); + protected async Task DownloadResponseContentAsync(FileClient client, ChatMessageContent message) + { + foreach (KernelContent item in message.Items) + { + if (item is AnnotationContent annotation) + { + await this.DownloadFileContentAsync(client, annotation.FileId!); + } + } + } - // Process.Start( - // new ProcessStartInfo - // { - // FileName = "cmd.exe", - // Arguments = $"/C start {filePath}" - // }); + protected async Task DownloadResponseImageAsync(FileClient client, ChatMessageContent message) + { + foreach (KernelContent item in message.Items) + { + if (item is FileReferenceContent fileReference) + { + await this.DownloadFileContentAsync(client, fileReference.FileId, launchViewer: true); + } + } + } + + private async Task DownloadFileContentAsync(FileClient client, string fileId, bool launchViewer = false) + { + OpenAIFileInfo fileInfo = client.GetFile(fileId); + if (fileInfo.Purpose == OpenAIFilePurpose.AssistantsOutput) + { + string filePath = Path.Combine(Path.GetTempPath(), Path.GetFileName(fileInfo.Filename)); + if (launchViewer) + { + filePath = Path.ChangeExtension(filePath, ".png"); + } + + BinaryData content = await client.DownloadFileAsync(fileId); + File.WriteAllBytes(filePath, content.ToArray()); + Console.WriteLine($" File #{fileId} saved to: {filePath}"); - // return filePath; - //} + if (launchViewer) + { + Process.Start( + new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = $"/C start {filePath}" + }); + } + } + } } From 7dc67fd76da0780f809cf5fe0206c101086d3656 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 6 Aug 2024 18:28:57 -0700 Subject: [PATCH 091/121] Whitespace --- .../samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs b/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs index c0d2d2151a3f..1ddd018c4c14 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs @@ -19,7 +19,7 @@ public class Step09_Assistant_Vision(ITestOutputHelper output) : BaseAgentsTest( [Fact] public async Task UseSingleAssistantAgentAsync() { -// Define the agent + // Define the agent OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( From e74aa194f11a00fc8a4a32d84ab630d5b7b0732c Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 6 Aug 2024 18:32:29 -0700 Subject: [PATCH 092/121] Namespace --- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index c55b0dd42b91..9c7802a2ef61 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.CompilerServices; From 41482ba31dbb2566d42f999c3b0c9b87fd47e850 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 7 Aug 2024 09:23:22 -0700 Subject: [PATCH 093/121] "Breaking Glass" Checkpoint --- .../Concepts/Agents/MixedChat_Agents.cs | 2 +- .../Concepts/Agents/MixedChat_Files.cs | 6 +- .../Concepts/Agents/MixedChat_Images.cs | 6 +- .../Agents/OpenAIAssistant_ChartMaker.cs | 6 +- .../OpenAIAssistant_FileManipulation.cs | 6 +- .../Step08_Assistant.cs | 2 +- .../Step09_Assistant_Vision.cs | 8 +- .../Step10_AssistantTool_CodeInterpreter.cs | 2 +- .../Step11_AssistantTool_FileSearch.cs | 8 +- .../OpenAIServiceConfigurationExtensions.cs | 26 --- .../OpenAI/Internal/OpenAIClientFactory.cs | 106 ----------- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 62 +++---- .../src/Agents/OpenAI/OpenAIClientProvider.cs | 172 ++++++++++++++++++ .../OpenAI/OpenAIServiceConfiguration.cs | 84 --------- ...enAIServiceConfigurationExtensionsTests.cs | 40 ---- .../Internal/OpenAIClientFactoryTests.cs | 87 --------- .../OpenAI/OpenAIAssistantAgentTests.cs | 6 +- .../OpenAI/OpenAIClientProviderTests.cs | 71 ++++++++ .../OpenAI/OpenAIServiceConfigurationTests.cs | 84 --------- .../Agents/OpenAIAssistantAgentTests.cs | 6 +- .../samples/AgentUtilities/BaseAgentsTest.cs | 8 +- 21 files changed, 301 insertions(+), 497 deletions(-) delete mode 100644 dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs delete mode 100644 dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs create mode 100644 dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs delete mode 100644 dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs delete mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs delete mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientProviderTests.cs delete mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIServiceConfigurationTests.cs diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs index 18ab8a673ca1..c387ff5704c2 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs @@ -47,7 +47,7 @@ public async Task ChatWithOpenAIAssistantAgentAndChatCompletionAgentAsync() OpenAIAssistantAgent agentWriter = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: this.GetOpenAIConfiguration(), + provider: this.GetClientProvider(), definition: new() { Instructions = CopyWriterInstructions, diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs index 2b80ff5cee6f..982e41417c13 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs @@ -19,9 +19,9 @@ public class MixedChat_Files(ITestOutputHelper output) : BaseAgentsTest(output) [Fact] public async Task AnalyzeFileAndGenerateReportAsync() { - OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); + OpenAIClientProvider provider = this.GetClientProvider(); - FileClient fileClient = config.CreateFileClient(); + FileClient fileClient = provider.Client.GetFileClient(); OpenAIFileInfo uploadFile = await fileClient.UploadFileAsync( @@ -35,7 +35,7 @@ await fileClient.UploadFileAsync( OpenAIAssistantAgent analystAgent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config, + provider, new() { EnableCodeInterpreter = true, diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs index 25f94de4c11e..4fae255c9b86 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs @@ -22,15 +22,15 @@ public class MixedChat_Images(ITestOutputHelper output) : BaseAgentsTest(output) [Fact] public async Task AnalyzeDataAndGenerateChartAsync() { - OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); + OpenAIClientProvider provider = this.GetClientProvider(); - FileClient fileClient = config.CreateFileClient(); + FileClient fileClient = provider.Client.GetFileClient(); // Define the agents OpenAIAssistantAgent analystAgent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config, + provider, new() { Instructions = AnalystInstructions, diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index 512fa5bbb0a2..08aee21c8707 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -19,15 +19,15 @@ public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseAgentsTe [Fact] public async Task GenerateChartWithOpenAIAssistantAgentAsync() { - OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); + OpenAIClientProvider provider = this.GetClientProvider(); - FileClient fileClient = config.CreateFileClient(); + FileClient fileClient = provider.Client.GetFileClient(); // Define the agent OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config, + provider, new() { Instructions = AgentInstructions, diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index f25e17600b99..bca6118041e4 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -16,9 +16,9 @@ public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseAg [Fact] public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync() { - OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); + OpenAIClientProvider provider = this.GetClientProvider(); - FileClient fileClient = config.CreateFileClient(); + FileClient fileClient = provider.Client.GetFileClient(); OpenAIFileInfo uploadFile = await fileClient.UploadFileAsync( @@ -30,7 +30,7 @@ await fileClient.UploadFileAsync( OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config, + provider, new() { EnableCodeInterpreter = true, diff --git a/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs b/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs index bf3ddbac47f8..c937c7de9c70 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs @@ -23,7 +23,7 @@ public async Task UseSingleAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: this.GetOpenAIConfiguration(), + provider: this.GetClientProvider(), new() { Instructions = HostInstructions, diff --git a/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs b/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs index 1ddd018c4c14..29a6e108df24 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs @@ -20,11 +20,11 @@ public class Step09_Assistant_Vision(ITestOutputHelper output) : BaseAgentsTest( public async Task UseSingleAssistantAgentAsync() { // Define the agent - OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); + OpenAIClientProvider provider = this.GetClientProvider(); OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config, + provider, new() { ModelId = this.Model, @@ -33,7 +33,7 @@ await OpenAIAssistantAgent.CreateAsync( // Upload an image await using Stream imageStream = EmbeddedResource.ReadStream("cat.jpg")!; - string fileId = await OpenAIAssistantAgent.UploadFileAsync(config, imageStream, "cat.jpg"); + string fileId = await agent.UploadFileAsync(imageStream, "cat.jpg"); // Create a thread for the agent conversation. string threadId = await agent.CreateThreadAsync(new OpenAIThreadCreationOptions { Metadata = AssistantSampleMetadata }); @@ -51,7 +51,7 @@ await OpenAIAssistantAgent.CreateAsync( { await agent.DeleteThreadAsync(threadId); await agent.DeleteAsync(); - await config.CreateFileClient().DeleteFileAsync(fileId); + await provider.Client.GetFileClient().DeleteFileAsync(fileId); } // Local function to invoke agent and display the conversation messages. diff --git a/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs index 596bd690fcc1..d623c8a28b7b 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs @@ -17,7 +17,7 @@ public async Task UseCodeInterpreterToolWithAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: this.GetOpenAIConfiguration(), + provider: this.GetClientProvider(), new() { EnableCodeInterpreter = true, diff --git a/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs b/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs index 5769db1178ea..bfcd93dd3ecb 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs @@ -17,11 +17,11 @@ public class Step11_AssistantTool_FileSearch(ITestOutputHelper output) : BaseAge public async Task UseFileSearchToolWithAssistantAgentAsync() { // Define the agent - OpenAIServiceConfiguration config = this.GetOpenAIConfiguration(); + OpenAIClientProvider provider = this.GetClientProvider(); OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - config: this.GetOpenAIConfiguration(), + provider: this.GetClientProvider(), new() { EnableFileSearch = true, @@ -30,12 +30,12 @@ await OpenAIAssistantAgent.CreateAsync( }); // Upload file - Using a table of fictional employees. - FileClient fileClient = config.CreateFileClient(); + FileClient fileClient = provider.Client.GetFileClient(); await using Stream stream = EmbeddedResource.ReadStream("employees.pdf")!; OpenAIFileInfo fileInfo = await fileClient.UploadFileAsync(stream, "employees.pdf", FileUploadPurpose.Assistants); // Create a vector-store - VectorStoreClient vectorStoreClient = config.CreateVectorStoreClient(); + VectorStoreClient vectorStoreClient = provider.Client.GetVectorStoreClient(); VectorStore vectorStore = await vectorStoreClient.CreateVectorStoreAsync( new VectorStoreCreationOptions() diff --git a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs deleted file mode 100644 index 9b3a55b9e6fe..000000000000 --- a/dotnet/src/Agents/OpenAI/Extensions/OpenAIServiceConfigurationExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel.Agents.OpenAI.Internal; -using OpenAI.Files; -using OpenAI.VectorStores; - -namespace Microsoft.SemanticKernel.Agents.OpenAI; - -/// -/// Extension method for creating OpenAI clients from a . -/// -public static class OpenAIServiceConfigurationExtensions -{ - /// - /// Provide a newly created based on the specified configuration. - /// - /// The configuration - public static FileClient CreateFileClient(this OpenAIServiceConfiguration configuration) - => OpenAIClientFactory.CreateClient(configuration).GetFileClient(); - - /// - /// Provide a newly created based on the specified configuration. - /// - /// The configuration - public static VectorStoreClient CreateVectorStoreClient(this OpenAIServiceConfiguration configuration) - => OpenAIClientFactory.CreateClient(configuration).GetVectorStoreClient(); -} diff --git a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs b/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs deleted file mode 100644 index 97c3cc978f68..000000000000 --- a/dotnet/src/Agents/OpenAI/Internal/OpenAIClientFactory.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System.ClientModel.Primitives; -using System.Net.Http; -using System.Threading; -using Azure.AI.OpenAI; -using Microsoft.SemanticKernel.Http; -using OpenAI; - -namespace Microsoft.SemanticKernel.Agents.OpenAI.Internal; - -/// -/// Factory for creating . -/// -internal static class OpenAIClientFactory -{ - /// - /// Avoids an exception from OpenAI Client when a custom endpoint is provided without an API key. - /// - private const string SingleSpaceKey = " "; - - /// - /// Creates an OpenAI client based on the provided configuration. - /// - /// Configuration required to target a specific Open AI service - /// An initialized Open AI client - public static OpenAIClient CreateClient(OpenAIServiceConfiguration config) - { - // Inspect options - switch (config.Type) - { - case OpenAIServiceConfiguration.OpenAIServiceType.AzureOpenAI: - { - AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(config); - - if (config.Credential is not null) - { - return new AzureOpenAIClient(config.Endpoint, config.Credential, clientOptions); - } - - if (!string.IsNullOrEmpty(config.ApiKey)) - { - return new AzureOpenAIClient(config.Endpoint, config.ApiKey!, clientOptions); - } - - throw new KernelException($"Unsupported configuration state: {config.Type}. No api-key or credential present."); - } - case OpenAIServiceConfiguration.OpenAIServiceType.OpenAI: - { - OpenAIClientOptions clientOptions = CreateOpenAIClientOptions(config); - return new OpenAIClient(config.ApiKey ?? SingleSpaceKey, clientOptions); - } - default: - throw new KernelException($"Unsupported configuration type: {config.Type}"); - } - } - - private static AzureOpenAIClientOptions CreateAzureClientOptions(OpenAIServiceConfiguration config) - { - AzureOpenAIClientOptions options = - new() - { - ApplicationId = HttpHeaderConstant.Values.UserAgent, - Endpoint = config.Endpoint, - }; - - ConfigureClientOptions(config.HttpClient, options); - - return options; - } - - private static OpenAIClientOptions CreateOpenAIClientOptions(OpenAIServiceConfiguration config) - { - OpenAIClientOptions options = - new() - { - ApplicationId = HttpHeaderConstant.Values.UserAgent, - Endpoint = config.Endpoint ?? config.HttpClient?.BaseAddress, - }; - - ConfigureClientOptions(config.HttpClient, options); - - return options; - } - - private static void ConfigureClientOptions(HttpClient? httpClient, OpenAIClientOptions options) - { - options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall); - - if (httpClient is not null) - { - options.Transport = new HttpClientPipelineTransport(httpClient); - options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable retry policy if and only if a custom HttpClient is provided. - options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable default timeout - } - } - - private static GenericActionPipelinePolicy CreateRequestHeaderPolicy(string headerName, string headerValue) - => - new((message) => - { - if (message?.Request?.Headers?.TryGetValue(headerName, out string? _) == false) - { - message.Request.Headers.Set(headerName, headerValue); - } - }); -} diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 9c7802a2ef61..b13afaecf901 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -26,6 +26,7 @@ public sealed class OpenAIAssistantAgent : KernelAgent internal const string OptionsMetadataKey = "__run_options"; + private readonly OpenAIClientProvider _provider; private readonly Assistant _assistant; private readonly AssistantClient _client; private readonly string[] _channelKeys; @@ -55,23 +56,23 @@ public sealed class OpenAIAssistantAgent : KernelAgent /// Define a new . /// /// The containing services, plugins, and other state for use throughout the operation. - /// Configuration for accessing the API service. + /// Configuration for accessing the API service. /// The assistant definition. /// The to monitor for cancellation requests. The default is . /// An instance public static async Task CreateAsync( Kernel kernel, - OpenAIServiceConfiguration config, + OpenAIClientProvider provider, OpenAIAssistantDefinition definition, CancellationToken cancellationToken = default) { // Validate input Verify.NotNull(kernel, nameof(kernel)); - Verify.NotNull(config, nameof(config)); + Verify.NotNull(provider, nameof(provider)); Verify.NotNull(definition, nameof(definition)); // Create the client - AssistantClient client = CreateClient(config); + AssistantClient client = CreateClient(provider); // Create the assistant AssistantCreationOptions assistantCreationOptions = CreateAssistantCreationOptions(definition); @@ -79,7 +80,7 @@ public static async Task CreateAsync( // Instantiate the agent return - new OpenAIAssistantAgent(client, model, DefineChannelKeys(config)) + new OpenAIAssistantAgent(model, provider, client) { Kernel = kernel, }; @@ -88,15 +89,15 @@ public static async Task CreateAsync( /// /// Retrieve a list of assistant definitions: . /// - /// Configuration for accessing the API service. + /// Configuration for accessing the API service. /// The to monitor for cancellation requests. The default is . /// An list of objects. public static async IAsyncEnumerable ListDefinitionsAsync( - OpenAIServiceConfiguration config, + OpenAIClientProvider provider, [EnumeratorCancellation] CancellationToken cancellationToken = default) { // Create the client - AssistantClient client = CreateClient(config); + AssistantClient client = CreateClient(provider); // Query and enumerate assistant definitions await foreach (Assistant model in client.GetAssistantsAsync(ListOrder.NewestFirst, cancellationToken).ConfigureAwait(false)) @@ -109,25 +110,25 @@ public static async IAsyncEnumerable ListDefinitionsA /// Retrieve a by identifier. /// /// The containing services, plugins, and other state for use throughout the operation. - /// Configuration for accessing the API service. + /// Configuration for accessing the API service. /// The agent identifier /// The to monitor for cancellation requests. The default is . /// An instance public static async Task RetrieveAsync( Kernel kernel, - OpenAIServiceConfiguration config, + OpenAIClientProvider provider, string id, CancellationToken cancellationToken = default) { // Create the client - AssistantClient client = CreateClient(config); + AssistantClient client = CreateClient(provider); // Retrieve the assistant Assistant model = await client.GetAssistantAsync(id).ConfigureAwait(false); // SDK BUG - CANCEL TOKEN (https://github.com/microsoft/semantic-kernel/issues/7431) // Instantiate the agent return - new OpenAIAssistantAgent(client, model, DefineChannelKeys(config)) + new OpenAIAssistantAgent(model, provider, client) { Kernel = kernel, }; @@ -169,7 +170,6 @@ public async Task DeleteThreadAsync( /// /// Uploads an file for the purpose of using with assistant. /// - /// Configuration for accessing the API service. /// The content to upload /// The name of the file /// The to monitor for cancellation requests. The default is . @@ -177,9 +177,9 @@ public async Task DeleteThreadAsync( /// /// Use the directly for more advanced file operations. /// - public static async Task UploadFileAsync(OpenAIServiceConfiguration config, Stream stream, string name, CancellationToken cancellationToken = default) + public async Task UploadFileAsync(Stream stream, string name, CancellationToken cancellationToken = default) { - FileClient client = config.CreateFileClient(); + FileClient client = this._provider.Client.GetFileClient(); OpenAIFileInfo fileInfo = await client.UploadFileAsync(stream, name, FileUploadPurpose.Assistants, cancellationToken).ConfigureAwait(false); @@ -299,13 +299,14 @@ internal void ThrowIfDeleted() /// Initializes a new instance of the class. /// private OpenAIAssistantAgent( - AssistantClient client, Assistant model, - IEnumerable channelKeys) + OpenAIClientProvider provider, + AssistantClient client) { + this._provider = provider; this._assistant = model; - this._client = client; - this._channelKeys = channelKeys.ToArray(); + this._client = provider.Client.GetAssistantClient(); + this._channelKeys = provider.ConfigurationKeys.ToArray(); this.Definition = CreateAssistantDefinition(model); @@ -392,32 +393,19 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAss return assistantCreationOptions; } - private static AssistantClient CreateClient(OpenAIServiceConfiguration config) + private static AssistantClient CreateClient(OpenAIClientProvider config) { - OpenAIClient openAIClient = OpenAIClientFactory.CreateClient(config); - return openAIClient.GetAssistantClient(); + return config.Client.GetAssistantClient(); } - private static IEnumerable DefineChannelKeys(OpenAIServiceConfiguration config) + private static IEnumerable DefineChannelKeys(OpenAIClientProvider config) { // Distinguish from other channel types. yield return typeof(AgentChannel).FullName!; - // Distinguish between different Azure OpenAI endpoints or OpenAI services. - yield return config.Endpoint != null ? config.Endpoint.ToString() : "openai"; - - // Custom client receives dedicated channel. - if (config.HttpClient is not null) + foreach (string key in config.ConfigurationKeys) { - if (config.HttpClient.BaseAddress is not null) - { - yield return config.HttpClient.BaseAddress.AbsoluteUri; - } - - foreach (string header in config.HttpClient.DefaultRequestHeaders.SelectMany(h => h.Value)) - { - yield return header; - } + yield return key; } } } diff --git a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs new file mode 100644 index 000000000000..2a9c18dfb283 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using Azure.AI.OpenAI; +using Azure.Core; +using Microsoft.SemanticKernel.Http; +using OpenAI; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// %%% +/// +public sealed class OpenAIClientProvider +{ + /// + /// Avoids an exception from OpenAI Client when a custom endpoint is provided without an API key. + /// + private const string SingleSpaceKey = " "; + + /// + /// %%% + /// + public OpenAIClient Client { get; } + + internal IEnumerable ConfigurationKeys { get; } + + private OpenAIClientProvider(OpenAIClient client, IEnumerable keys) + { + this.Client = client; + this.ConfigurationKeys = keys; + } + + /// + /// Produce a based on . + /// + /// The API key + /// The service endpoint + /// Custom for HTTP requests. + public static OpenAIClientProvider ForAzureOpenAI(ApiKeyCredential apiKey, Uri endpoint, HttpClient? httpClient = null) + { + Verify.NotNull(apiKey, nameof(apiKey)); + Verify.NotNull(endpoint, nameof(endpoint)); + + AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(endpoint, httpClient); + + return new(new AzureOpenAIClient(endpoint, apiKey!, clientOptions), CreateConfigurationKeys(endpoint, httpClient)); + } + + /// + /// Produce a based on . + /// + /// The credentials + /// The service endpoint + /// Custom for HTTP requests. + public static OpenAIClientProvider ForAzureOpenAI(TokenCredential credential, Uri endpoint, HttpClient? httpClient = null) + { + Verify.NotNull(credential, nameof(credential)); + Verify.NotNull(endpoint, nameof(endpoint)); + + AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(endpoint, httpClient); + + return new(new AzureOpenAIClient(endpoint, credential, clientOptions), CreateConfigurationKeys(endpoint, httpClient)); + } + + /// + /// Produce a based on . + /// + /// An optional endpoint + /// Custom for HTTP requests. + public static OpenAIClientProvider ForOpenAI(Uri? endpoint = null, HttpClient? httpClient = null) + { + OpenAIClientOptions clientOptions = CreateOpenAIClientOptions(endpoint, httpClient); + return new(new OpenAIClient(SingleSpaceKey, clientOptions), CreateConfigurationKeys(endpoint, httpClient)); + } + + /// + /// Produce a based on . + /// + /// The API key + /// An optional endpoint + /// Custom for HTTP requests. + public static OpenAIClientProvider ForOpenAI(ApiKeyCredential apiKey, Uri? endpoint = null, HttpClient? httpClient = null) + { + OpenAIClientOptions clientOptions = CreateOpenAIClientOptions(endpoint, httpClient); + return new(new OpenAIClient(apiKey ?? SingleSpaceKey, clientOptions), CreateConfigurationKeys(endpoint, httpClient)); + } + + /// + /// %%% + /// + public static OpenAIClientProvider FromClient(OpenAIClient client) + { + return new(client, [client.GetType().FullName!, client.GetHashCode().ToString()]); + } + + private static AzureOpenAIClientOptions CreateAzureClientOptions(Uri? endpoint, HttpClient? httpClient) + { + AzureOpenAIClientOptions options = + new() + { + ApplicationId = HttpHeaderConstant.Values.UserAgent, + Endpoint = endpoint, + }; + + ConfigureClientOptions(httpClient, options); + + return options; + } + + private static OpenAIClientOptions CreateOpenAIClientOptions(Uri? endpoint, HttpClient? httpClient) + { + OpenAIClientOptions options = + new() + { + ApplicationId = HttpHeaderConstant.Values.UserAgent, + Endpoint = endpoint ?? httpClient?.BaseAddress, + }; + + ConfigureClientOptions(httpClient, options); + + return options; + } + + private static void ConfigureClientOptions(HttpClient? httpClient, OpenAIClientOptions options) + { + options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall); + + if (httpClient is not null) + { + options.Transport = new HttpClientPipelineTransport(httpClient); + options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable retry policy if and only if a custom HttpClient is provided. + options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable default timeout + } + } + + private static GenericActionPipelinePolicy CreateRequestHeaderPolicy(string headerName, string headerValue) + => + new((message) => + { + if (message?.Request?.Headers?.TryGetValue(headerName, out string? _) == false) + { + message.Request.Headers.Set(headerName, headerValue); + } + }); + + private static IEnumerable CreateConfigurationKeys(Uri? endpoint, HttpClient? httpClient) + { + if (endpoint != null) + { + yield return endpoint.ToString(); + } + + if (httpClient is not null) + { + if (httpClient.BaseAddress is not null) + { + yield return httpClient.BaseAddress.AbsoluteUri; + } + + foreach (string header in httpClient.DefaultRequestHeaders.SelectMany(h => h.Value)) + { + yield return header; + } + } + } +} diff --git a/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs b/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs deleted file mode 100644 index 1bc6431e5487..000000000000 --- a/dotnet/src/Agents/OpenAI/OpenAIServiceConfiguration.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using System.Net.Http; -using Azure.Core; - -namespace Microsoft.SemanticKernel.Agents.OpenAI; - -/// -/// Configuration to target a specific Open AI service. -/// -public sealed class OpenAIServiceConfiguration -{ - internal enum OpenAIServiceType - { - AzureOpenAI, - OpenAI, - } - - /// - /// Produce a that targets an Azure OpenAI endpoint using an API key. - /// - /// The API key - /// The service endpoint - /// Custom for HTTP requests. - public static OpenAIServiceConfiguration ForAzureOpenAI(string apiKey, Uri endpoint, HttpClient? httpClient = null) - { - Verify.NotNullOrWhiteSpace(apiKey, nameof(apiKey)); - Verify.NotNull(endpoint, nameof(endpoint)); - - return - new() - { - ApiKey = apiKey, - Endpoint = endpoint, - HttpClient = httpClient, - Type = OpenAIServiceType.AzureOpenAI, - }; - } - - /// - /// Produce a that targets an Azure OpenAI endpoint using an token credentials. - /// - /// The credentials - /// The service endpoint - /// Custom for HTTP requests. - public static OpenAIServiceConfiguration ForAzureOpenAI(TokenCredential credential, Uri endpoint, HttpClient? httpClient = null) - { - Verify.NotNull(credential, nameof(credential)); - Verify.NotNull(endpoint, nameof(endpoint)); - - return - new() - { - Credential = credential, - Endpoint = endpoint, - HttpClient = httpClient, - Type = OpenAIServiceType.AzureOpenAI, - }; - } - - /// - /// Produce a that targets OpenAI services using an API key. - /// - /// The API key - /// An optional endpoint - /// Custom for HTTP requests. - public static OpenAIServiceConfiguration ForOpenAI(string? apiKey, Uri? endpoint = null, HttpClient? httpClient = null) - { - return - new() - { - ApiKey = apiKey, - Endpoint = endpoint, - HttpClient = httpClient, - Type = OpenAIServiceType.OpenAI, - }; - } - - internal string? ApiKey { get; init; } - internal TokenCredential? Credential { get; init; } - internal Uri? Endpoint { get; init; } - internal HttpClient? HttpClient { get; init; } - internal OpenAIServiceType Type { get; init; } -} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs deleted file mode 100644 index 650503a94e5e..000000000000 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIServiceConfigurationExtensionsTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using Microsoft.SemanticKernel.Agents.OpenAI; -using OpenAI.Files; -using OpenAI.VectorStores; -using Xunit; - -namespace SemanticKernel.Agents.UnitTests.OpenAI.Extensions; - -/// -/// Unit testing of . -/// -public class OpenAIServiceConfigurationExtensionsTests -{ - /// - /// Verify can produce a - /// - [Fact] - public void OpenAIServiceConfigurationExtensionsCreateFileClientTest() - { - OpenAIServiceConfiguration configOpenAI = OpenAIServiceConfiguration.ForOpenAI("key", new Uri("https://localhost")); - Assert.IsType(configOpenAI.CreateFileClient()); - - OpenAIServiceConfiguration configAzure = OpenAIServiceConfiguration.ForAzureOpenAI("key", new Uri("https://localhost")); - Assert.IsType(configOpenAI.CreateFileClient()); - } - - /// - /// Verify can produce a - /// - [Fact] - public void OpenAIServiceConfigurationExtensionsCreateVectorStoreTest() - { - OpenAIServiceConfiguration configOpenAI = OpenAIServiceConfiguration.ForOpenAI("key", new Uri("https://localhost")); - Assert.IsType(configOpenAI.CreateVectorStoreClient()); - - OpenAIServiceConfiguration configAzure = OpenAIServiceConfiguration.ForAzureOpenAI("key", new Uri("https://localhost")); - Assert.IsType(configOpenAI.CreateVectorStoreClient()); - } -} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs deleted file mode 100644 index df2387c917e3..000000000000 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/OpenAIClientFactoryTests.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using System.Net.Http; -using Azure.Core; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.Agents.OpenAI.Internal; -using Moq; -using OpenAI; -using Xunit; - -namespace SemanticKernel.Agents.UnitTests.OpenAI.Internal; - -/// -/// Unit testing of . -/// -public class OpenAIClientFactoryTests -{ - /// - /// Verify that the factory can create a client for Azure OpenAI. - /// - [Fact] - public void VerifyOpenAIClientFactoryTargetAzureByKey() - { - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForAzureOpenAI("key", new Uri("https://localhost")); - OpenAIClient client = OpenAIClientFactory.CreateClient(config); - Assert.NotNull(client); - } - - /// - /// Verify that the factory can create a client for Azure OpenAI. - /// - [Fact] - public void VerifyOpenAIClientFactoryTargetAzureByCredential() - { - Mock mockCredential = new(); - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForAzureOpenAI(mockCredential.Object, new Uri("https://localhost")); - OpenAIClient client = OpenAIClientFactory.CreateClient(config); - Assert.NotNull(client); - } - - /// - /// Verify that the factory throws exception for null credential. - /// - [Fact] - public void VerifyOpenAIClientFactoryTargetAzureNullCredential() - { - OpenAIServiceConfiguration config = new() { Type = OpenAIServiceConfiguration.OpenAIServiceType.AzureOpenAI }; - Assert.Throws(() => OpenAIClientFactory.CreateClient(config)); - } - - /// - /// Verify that the factory throws exception for null credential. - /// - [Fact] - public void VerifyOpenAIClientFactoryTargetUnknownTypes() - { - OpenAIServiceConfiguration config = new() { Type = (OpenAIServiceConfiguration.OpenAIServiceType)99 }; - Assert.Throws(() => OpenAIClientFactory.CreateClient(config)); - } - - /// - /// Verify that the factory can create a client for various OpenAI service configurations. - /// - [Theory] - [InlineData(null, null)] - [InlineData("key", null)] - [InlineData("key", "http://myproxy:9819")] - public void VerifyOpenAIClientFactoryTargetOpenAI(string? key, string? endpoint) - { - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(key, endpoint != null ? new Uri(endpoint) : null); - OpenAIClient client = OpenAIClientFactory.CreateClient(config); - Assert.NotNull(client); - } - - /// - /// Verify that the factory can create a client with http proxy. - /// - [Fact] - public void VerifyOpenAIClientFactoryWithHttpClient() - { - using HttpClient httpClient = new() { BaseAddress = new Uri("http://myproxy:9819") }; - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: null, httpClient: httpClient); - OpenAIClient client = OpenAIClientFactory.CreateClient(config); - Assert.NotNull(client); - } -} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index 59073cdb1802..a1a1959e9cff 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -634,10 +634,10 @@ private Task CreateAgentAsync() definition); } - private OpenAIServiceConfiguration CreateTestConfiguration(bool targetAzure = false) + private OpenAIClientProvider CreateTestConfiguration(bool targetAzure = false) => targetAzure ? - OpenAIServiceConfiguration.ForAzureOpenAI(apiKey: "fakekey", endpoint: new Uri("https://localhost"), this._httpClient) : - OpenAIServiceConfiguration.ForOpenAI(apiKey: "fakekey", endpoint: null, this._httpClient); + OpenAIClientProvider.ForAzureOpenAI(apiKey: "fakekey", endpoint: new Uri("https://localhost"), this._httpClient) : + OpenAIClientProvider.ForOpenAI(apiKey: "fakekey", endpoint: null, this._httpClient); private void SetupResponse(HttpStatusCode statusCode, string content) { diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientProviderTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientProviderTests.cs new file mode 100644 index 000000000000..dfb033f31d3c --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientProviderTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Net.Http; +using Azure.Core; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Moq; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI; + +/// +/// Unit testing of . +/// +public class OpenAIClientProviderTests +{ + /// + /// Verify that provisioning of client for Azure OpenAI. + /// + [Fact] + public void VerifyOpenAIClientFactoryTargetAzureByKey() + { + OpenAIClientProvider provider = OpenAIClientProvider.ForAzureOpenAI("key", new Uri("https://localhost")); + Assert.NotNull(provider.Client); + } + + /// + /// Verify that provisioning of client for Azure OpenAI. + /// + [Fact] + public void VerifyOpenAIClientFactoryTargetAzureByCredential() + { + Mock mockCredential = new(); + OpenAIClientProvider provider = OpenAIClientProvider.ForAzureOpenAI(mockCredential.Object, new Uri("https://localhost")); + Assert.NotNull(provider.Client); + } + + /// + /// Verify that provisioning of client for OpenAI. + /// + [Theory] + [InlineData(null)] + [InlineData("http://myproxy:9819")] + public void VerifyOpenAIClientFactoryTargetOpenAINoKey(string? endpoint) + { + OpenAIClientProvider provider = OpenAIClientProvider.ForOpenAI(endpoint != null ? new Uri(endpoint) : null); + Assert.NotNull(provider.Client); + } + + /// + /// Verify that provisioning of client for OpenAI. + /// + [Theory] + [InlineData("key", null)] + [InlineData("key", "http://myproxy:9819")] + public void VerifyOpenAIClientFactoryTargetOpenAIByKey(string key, string? endpoint) + { + OpenAIClientProvider provider = OpenAIClientProvider.ForOpenAI(key, endpoint != null ? new Uri(endpoint) : null); + Assert.NotNull(provider.Client); + } + + /// + /// Verify that the factory can create a client with http proxy. + /// + [Fact] + public void VerifyOpenAIClientFactoryWithHttpClient() + { + using HttpClient httpClient = new() { BaseAddress = new Uri("http://myproxy:9819") }; + OpenAIClientProvider provider = OpenAIClientProvider.ForOpenAI(httpClient: httpClient); + Assert.NotNull(provider.Client); + } +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIServiceConfigurationTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIServiceConfigurationTests.cs deleted file mode 100644 index dce5d5c9ceaf..000000000000 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIServiceConfigurationTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System; -using System.Net.Http; -using Azure.Core; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Moq; -using Xunit; - -namespace SemanticKernel.Agents.UnitTests.OpenAI; - -/// -/// Unit testing of . -/// -public class OpenAIServiceConfigurationTests -{ - /// - /// Verify Open AI service configuration. - /// - [Fact] - public void VerifyOpenAIAssistantConfiguration() - { - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: "testkey"); - - Assert.Equal(OpenAIServiceConfiguration.OpenAIServiceType.OpenAI, config.Type); - Assert.Equal("testkey", config.ApiKey); - Assert.Null(config.Credential); - Assert.Null(config.Endpoint); - Assert.Null(config.HttpClient); - } - - /// - /// Verify Open AI service configuration with endpoint. - /// - [Fact] - public void VerifyOpenAIAssistantProxyConfiguration() - { - using HttpClient client = new(); - - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForOpenAI(apiKey: "testkey", endpoint: new Uri("https://localhost"), client); - - Assert.Equal(OpenAIServiceConfiguration.OpenAIServiceType.OpenAI, config.Type); - Assert.Equal("testkey", config.ApiKey); - Assert.Null(config.Credential); - Assert.NotNull(config.Endpoint); - Assert.Equal("https://localhost/", config.Endpoint.ToString()); - Assert.NotNull(config.HttpClient); - } - - /// - /// Verify Azure Open AI service configuration with API key. - /// - [Fact] - public void VerifyAzureOpenAIAssistantApiKeyConfiguration() - { - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForAzureOpenAI(apiKey: "testkey", endpoint: new Uri("https://localhost")); - - Assert.Equal(OpenAIServiceConfiguration.OpenAIServiceType.AzureOpenAI, config.Type); - Assert.Equal("testkey", config.ApiKey); - Assert.Null(config.Credential); - Assert.NotNull(config.Endpoint); - Assert.Equal("https://localhost/", config.Endpoint.ToString()); - Assert.Null(config.HttpClient); - } - - /// - /// Verify Azure Open AI service configuration with API key. - /// - [Fact] - public void VerifyAzureOpenAIAssistantCredentialConfiguration() - { - using HttpClient client = new(); - - Mock credential = new(); - - OpenAIServiceConfiguration config = OpenAIServiceConfiguration.ForAzureOpenAI(credential.Object, endpoint: new Uri("https://localhost"), client); - - Assert.Equal(OpenAIServiceConfiguration.OpenAIServiceType.AzureOpenAI, config.Type); - Assert.Null(config.ApiKey); - Assert.NotNull(config.Credential); - Assert.NotNull(config.Endpoint); - Assert.Equal("https://localhost/", config.Endpoint.ToString()); - Assert.NotNull(config.HttpClient); - } -} diff --git a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs index f7dee91db903..a4458f6cb470 100644 --- a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs @@ -36,7 +36,7 @@ public async Task OpenAIAssistantAgentTestAsync(string input, string expectedAns Assert.NotNull(openAISettings); await this.ExecuteAgentAsync( - OpenAIServiceConfiguration.ForOpenAI(openAISettings.ApiKey), + OpenAIClientProvider.ForOpenAI(openAISettings.ApiKey), openAISettings.ModelId, input, expectedAnswerContains); @@ -54,14 +54,14 @@ public async Task AzureOpenAIAssistantAgentAsync(string input, string expectedAn Assert.NotNull(azureOpenAIConfiguration); await this.ExecuteAgentAsync( - OpenAIServiceConfiguration.ForAzureOpenAI(azureOpenAIConfiguration.ApiKey, new Uri(azureOpenAIConfiguration.Endpoint)), + OpenAIClientProvider.ForAzureOpenAI(azureOpenAIConfiguration.ApiKey, new Uri(azureOpenAIConfiguration.Endpoint)), azureOpenAIConfiguration.ChatDeploymentName!, input, expectedAnswerContains); } private async Task ExecuteAgentAsync( - OpenAIServiceConfiguration config, + OpenAIClientProvider config, string modelName, string input, string expected) diff --git a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs index 7bfbd3fd4df0..e86c1b77f4c1 100644 --- a/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs +++ b/dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs @@ -31,13 +31,13 @@ public abstract class BaseAgentsTest(ITestOutputHelper output) : BaseTest(output }); /// - /// Provide a according to the configuration settings. + /// Provide a according to the configuration settings. /// - protected OpenAIServiceConfiguration GetOpenAIConfiguration() + protected OpenAIClientProvider GetClientProvider() => this.UseOpenAIConfig ? - OpenAIServiceConfiguration.ForOpenAI(this.ApiKey) : - OpenAIServiceConfiguration.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); + OpenAIClientProvider.ForOpenAI(this.ApiKey) : + OpenAIClientProvider.ForAzureOpenAI(this.ApiKey, new Uri(this.Endpoint!)); /// /// Common method to write formatted agent chat content to the console. From a372a679d654a70ad0a7030185b84ad9e79bc69f Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 7 Aug 2024 09:25:38 -0700 Subject: [PATCH 094/121] Comments --- dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs index 2a9c18dfb283..8c4f4e477d64 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// %%% +/// Provides an for use by . /// public sealed class OpenAIClientProvider { @@ -24,7 +24,7 @@ public sealed class OpenAIClientProvider private const string SingleSpaceKey = " "; /// - /// %%% + /// An active client instance. /// public OpenAIClient Client { get; } @@ -92,7 +92,7 @@ public static OpenAIClientProvider ForOpenAI(ApiKeyCredential apiKey, Uri? endpo } /// - /// %%% + /// Directly provide a client instance. /// public static OpenAIClientProvider FromClient(OpenAIClient client) { From bdaa4b61141f24f5660da0f4f3ac004a4cdfc97a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 7 Aug 2024 09:36:21 -0700 Subject: [PATCH 095/121] One more comment --- dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs index 8c4f4e477d64..8cc879f2d0b4 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs @@ -28,6 +28,9 @@ public sealed class OpenAIClientProvider /// public OpenAIClient Client { get; } + /// + /// Configuration keys required for management. + /// internal IEnumerable ConfigurationKeys { get; } private OpenAIClientProvider(OpenAIClient client, IEnumerable keys) From 2b138e500b0939b129d08a8fbf3864a11c87e820 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 7 Aug 2024 12:20:16 -0700 Subject: [PATCH 096/121] Include agent-base-test in solution --- dotnet/SK-dotnet.sln | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index f9a0d8f29d28..228dd14c7abd 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -277,7 +277,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStartedWithAgents", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{77E141BA-AF5E-4C01-A970-6C07AC3CD55A}" ProjectSection(SolutionItems) = preProject + src\InternalUtilities\samples\ConfigurationNotFoundException.cs = src\InternalUtilities\samples\ConfigurationNotFoundException.cs + src\InternalUtilities\samples\EnumerableExtensions.cs = src\InternalUtilities\samples\EnumerableExtensions.cs + src\InternalUtilities\samples\Env.cs = src\InternalUtilities\samples\Env.cs + src\InternalUtilities\samples\ObjectExtensions.cs = src\InternalUtilities\samples\ObjectExtensions.cs + src\InternalUtilities\samples\PlanExtensions.cs = src\InternalUtilities\samples\PlanExtensions.cs + src\InternalUtilities\samples\RepoFiles.cs = src\InternalUtilities\samples\RepoFiles.cs src\InternalUtilities\samples\SamplesInternalUtilities.props = src\InternalUtilities\samples\SamplesInternalUtilities.props + src\InternalUtilities\samples\TextOutputHelperExtensions.cs = src\InternalUtilities\samples\TextOutputHelperExtensions.cs + src\InternalUtilities\samples\XunitLogger.cs = src\InternalUtilities\samples\XunitLogger.cs + src\InternalUtilities\samples\YourAppException.cs = src\InternalUtilities\samples\YourAppException.cs EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Functions.Prompty", "src\Functions\Functions.Prompty\Functions.Prompty.csproj", "{12B06019-740B-466D-A9E0-F05BC123A47D}" @@ -344,20 +353,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kernel-functions-generator", "samples\Demos\CreateChatGptPlugin\MathPlugin\kernel-functions-generator\kernel-functions-generator.csproj", "{4326A974-F027-4ABD-A220-382CC6BB0801}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{EE454832-085F-4D37-B19B-F94F7FC6984A}" - ProjectSection(SolutionItems) = preProject - src\InternalUtilities\samples\InternalUtilities\BaseTest.cs = src\InternalUtilities\samples\InternalUtilities\BaseTest.cs - src\InternalUtilities\samples\InternalUtilities\ConfigurationNotFoundException.cs = src\InternalUtilities\samples\InternalUtilities\ConfigurationNotFoundException.cs - src\InternalUtilities\samples\InternalUtilities\EmbeddedResource.cs = src\InternalUtilities\samples\InternalUtilities\EmbeddedResource.cs - src\InternalUtilities\samples\InternalUtilities\EnumerableExtensions.cs = src\InternalUtilities\samples\InternalUtilities\EnumerableExtensions.cs - src\InternalUtilities\samples\InternalUtilities\Env.cs = src\InternalUtilities\samples\InternalUtilities\Env.cs - src\InternalUtilities\samples\InternalUtilities\JsonResultTranslator.cs = src\InternalUtilities\samples\InternalUtilities\JsonResultTranslator.cs - src\InternalUtilities\samples\InternalUtilities\ObjectExtensions.cs = src\InternalUtilities\samples\InternalUtilities\ObjectExtensions.cs - src\InternalUtilities\samples\InternalUtilities\RepoFiles.cs = src\InternalUtilities\samples\InternalUtilities\RepoFiles.cs - src\InternalUtilities\samples\InternalUtilities\TestConfiguration.cs = src\InternalUtilities\samples\InternalUtilities\TestConfiguration.cs - src\InternalUtilities\samples\InternalUtilities\TextOutputHelperExtensions.cs = src\InternalUtilities\samples\InternalUtilities\TextOutputHelperExtensions.cs - src\InternalUtilities\samples\InternalUtilities\XunitLogger.cs = src\InternalUtilities\samples\InternalUtilities\XunitLogger.cs - src\InternalUtilities\samples\InternalUtilities\YourAppException.cs = src\InternalUtilities\samples\InternalUtilities\YourAppException.cs - EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Agents", "Agents", "{5C6C30E0-7AC1-47F4-8244-57B066B43FD8}" ProjectSection(SolutionItems) = preProject @@ -987,7 +982,7 @@ Global {738DCDB1-EFA8-4913-AD4C-6FC3F09B0A0C} = {2E79AD99-632F-411F-B3A5-1BAF3F5F89AB} {8642A03F-D840-4B2E-B092-478300000F83} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C} {ACD8C464-AEC9-45F6-A458-50A84F353DB7} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C} - {38374C62-0263-4FE8-A18C-70FC8132912B} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263} + {38374C62-0263-4FE8-A18C-70FC8132912B} = {00000000-0000-0000-0000-000000000000} {E06818E3-00A5-41AC-97ED-9491070CDEA1} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263} {F8B82F6B-B16A-4F8C-9C41-E9CD8D79A098} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263} {6B268108-2AB5-4607-B246-06AD8410E60E} = {4BB70FB3-1EC0-4F4A-863B-273D6C6D4D9A} From 6855dbba077be31c0d390bc36e30be2372a75933 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 7 Aug 2024 12:21:25 -0700 Subject: [PATCH 097/121] Re-link samples/internalutilities to .sln --- dotnet/SK-dotnet.sln | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 228dd14c7abd..791ff8e35185 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -277,16 +277,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStartedWithAgents", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{77E141BA-AF5E-4C01-A970-6C07AC3CD55A}" ProjectSection(SolutionItems) = preProject - src\InternalUtilities\samples\ConfigurationNotFoundException.cs = src\InternalUtilities\samples\ConfigurationNotFoundException.cs - src\InternalUtilities\samples\EnumerableExtensions.cs = src\InternalUtilities\samples\EnumerableExtensions.cs - src\InternalUtilities\samples\Env.cs = src\InternalUtilities\samples\Env.cs - src\InternalUtilities\samples\ObjectExtensions.cs = src\InternalUtilities\samples\ObjectExtensions.cs - src\InternalUtilities\samples\PlanExtensions.cs = src\InternalUtilities\samples\PlanExtensions.cs - src\InternalUtilities\samples\RepoFiles.cs = src\InternalUtilities\samples\RepoFiles.cs src\InternalUtilities\samples\SamplesInternalUtilities.props = src\InternalUtilities\samples\SamplesInternalUtilities.props - src\InternalUtilities\samples\TextOutputHelperExtensions.cs = src\InternalUtilities\samples\TextOutputHelperExtensions.cs - src\InternalUtilities\samples\XunitLogger.cs = src\InternalUtilities\samples\XunitLogger.cs - src\InternalUtilities\samples\YourAppException.cs = src\InternalUtilities\samples\YourAppException.cs EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Functions.Prompty", "src\Functions\Functions.Prompty\Functions.Prompty.csproj", "{12B06019-740B-466D-A9E0-F05BC123A47D}" @@ -353,6 +344,20 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kernel-functions-generator", "samples\Demos\CreateChatGptPlugin\MathPlugin\kernel-functions-generator\kernel-functions-generator.csproj", "{4326A974-F027-4ABD-A220-382CC6BB0801}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{EE454832-085F-4D37-B19B-F94F7FC6984A}" + ProjectSection(SolutionItems) = preProject + src\InternalUtilities\samples\InternalUtilities\BaseTest.cs = src\InternalUtilities\samples\InternalUtilities\BaseTest.cs + src\InternalUtilities\samples\InternalUtilities\ConfigurationNotFoundException.cs = src\InternalUtilities\samples\InternalUtilities\ConfigurationNotFoundException.cs + src\InternalUtilities\samples\InternalUtilities\EmbeddedResource.cs = src\InternalUtilities\samples\InternalUtilities\EmbeddedResource.cs + src\InternalUtilities\samples\InternalUtilities\EnumerableExtensions.cs = src\InternalUtilities\samples\InternalUtilities\EnumerableExtensions.cs + src\InternalUtilities\samples\InternalUtilities\Env.cs = src\InternalUtilities\samples\InternalUtilities\Env.cs + src\InternalUtilities\samples\InternalUtilities\JsonResultTranslator.cs = src\InternalUtilities\samples\InternalUtilities\JsonResultTranslator.cs + src\InternalUtilities\samples\InternalUtilities\ObjectExtensions.cs = src\InternalUtilities\samples\InternalUtilities\ObjectExtensions.cs + src\InternalUtilities\samples\InternalUtilities\RepoFiles.cs = src\InternalUtilities\samples\InternalUtilities\RepoFiles.cs + src\InternalUtilities\samples\InternalUtilities\TestConfiguration.cs = src\InternalUtilities\samples\InternalUtilities\TestConfiguration.cs + src\InternalUtilities\samples\InternalUtilities\TextOutputHelperExtensions.cs = src\InternalUtilities\samples\InternalUtilities\TextOutputHelperExtensions.cs + src\InternalUtilities\samples\InternalUtilities\XunitLogger.cs = src\InternalUtilities\samples\InternalUtilities\XunitLogger.cs + src\InternalUtilities\samples\InternalUtilities\YourAppException.cs = src\InternalUtilities\samples\InternalUtilities\YourAppException.cs + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Agents", "Agents", "{5C6C30E0-7AC1-47F4-8244-57B066B43FD8}" ProjectSection(SolutionItems) = preProject From f4d2113ad9a98d9985894f36256784f3568dde9a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 7 Aug 2024 12:28:16 -0700 Subject: [PATCH 098/121] Re-add demo project to sln (merge weirdness?) --- dotnet/SK-dotnet.sln | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 791ff8e35185..b4580b4d1146 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -331,9 +331,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Qdrant.UnitTests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Redis.UnitTests", "src\Connectors\Connectors.Redis.UnitTests\Connectors.Redis.UnitTests.csproj", "{ACD8C464-AEC9-45F6-A458-50A84F353DB7}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepwisePlannerMigration", "samples\Demos\StepwisePlannerMigration\StepwisePlannerMigration.csproj", "{38374C62-0263-4FE8-A18C-70FC8132912B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AIModelRouter", "samples\Demos\AIModelRouter\AIModelRouter.csproj", "{E06818E3-00A5-41AC-97ED-9491070CDEA1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AIModelRouter", "samples\Demos\AIModelRouter\AIModelRouter.csproj", "{E06818E3-00A5-41AC-97ED-9491070CDEA1}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CreateChatGptPlugin", "CreateChatGptPlugin", "{F8B82F6B-B16A-4F8C-9C41-E9CD8D79A098}" EndProject @@ -364,6 +362,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Agents", "Agents", "{5C6C30 src\InternalUtilities\samples\AgentUtilities\BaseAgentsTest.cs = src\InternalUtilities\samples\AgentUtilities\BaseAgentsTest.cs EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepwisePlannerMigration", "samples\Demos\StepwisePlannerMigration\StepwisePlannerMigration.csproj", "{2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -851,10 +851,6 @@ Global {ACD8C464-AEC9-45F6-A458-50A84F353DB7}.Publish|Any CPU.Build.0 = Debug|Any CPU {ACD8C464-AEC9-45F6-A458-50A84F353DB7}.Release|Any CPU.ActiveCfg = Release|Any CPU {ACD8C464-AEC9-45F6-A458-50A84F353DB7}.Release|Any CPU.Build.0 = Release|Any CPU - {38374C62-0263-4FE8-A18C-70FC8132912B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {38374C62-0263-4FE8-A18C-70FC8132912B}.Publish|Any CPU.ActiveCfg = Debug|Any CPU - {38374C62-0263-4FE8-A18C-70FC8132912B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {38374C62-0263-4FE8-A18C-70FC8132912B}.Release|Any CPU.Build.0 = Release|Any CPU {E06818E3-00A5-41AC-97ED-9491070CDEA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E06818E3-00A5-41AC-97ED-9491070CDEA1}.Debug|Any CPU.Build.0 = Debug|Any CPU {E06818E3-00A5-41AC-97ED-9491070CDEA1}.Publish|Any CPU.ActiveCfg = Debug|Any CPU @@ -873,6 +869,12 @@ Global {4326A974-F027-4ABD-A220-382CC6BB0801}.Publish|Any CPU.Build.0 = Debug|Any CPU {4326A974-F027-4ABD-A220-382CC6BB0801}.Release|Any CPU.ActiveCfg = Release|Any CPU {4326A974-F027-4ABD-A220-382CC6BB0801}.Release|Any CPU.Build.0 = Release|Any CPU + {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Publish|Any CPU.ActiveCfg = Debug|Any CPU + {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Publish|Any CPU.Build.0 = Debug|Any CPU + {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -987,7 +989,6 @@ Global {738DCDB1-EFA8-4913-AD4C-6FC3F09B0A0C} = {2E79AD99-632F-411F-B3A5-1BAF3F5F89AB} {8642A03F-D840-4B2E-B092-478300000F83} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C} {ACD8C464-AEC9-45F6-A458-50A84F353DB7} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C} - {38374C62-0263-4FE8-A18C-70FC8132912B} = {00000000-0000-0000-0000-000000000000} {E06818E3-00A5-41AC-97ED-9491070CDEA1} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263} {F8B82F6B-B16A-4F8C-9C41-E9CD8D79A098} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263} {6B268108-2AB5-4607-B246-06AD8410E60E} = {4BB70FB3-1EC0-4F4A-863B-273D6C6D4D9A} @@ -995,6 +996,7 @@ Global {4326A974-F027-4ABD-A220-382CC6BB0801} = {4BB70FB3-1EC0-4F4A-863B-273D6C6D4D9A} {EE454832-085F-4D37-B19B-F94F7FC6984A} = {77E141BA-AF5E-4C01-A970-6C07AC3CD55A} {5C6C30E0-7AC1-47F4-8244-57B066B43FD8} = {77E141BA-AF5E-4C01-A970-6C07AC3CD55A} + {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} From 0d082e1594da0567c58a317ed732446d454e6ab3 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 8 Aug 2024 10:11:39 -0700 Subject: [PATCH 099/121] Fix channel-keys generation --- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index b13afaecf901..fb49364d86be 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -265,7 +265,16 @@ public async IAsyncEnumerable InvokeAsync( } /// - protected override IEnumerable GetChannelKeys() => this._channelKeys; + protected override IEnumerable GetChannelKeys() + { + // Distinguish from other channel types. + yield return typeof(OpenAIAssistantChannel).FullName!; + + foreach (string key in this._channelKeys) + { + yield return key; + } + } /// protected override async Task CreateChannelAsync(CancellationToken cancellationToken) From b8f1d0256b3209a0c9d56a622aea4653155a3027 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 8 Aug 2024 10:12:15 -0700 Subject: [PATCH 100/121] Allocate key-enumeration --- dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs index 8cc879f2d0b4..e291de57ee52 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs @@ -31,12 +31,12 @@ public sealed class OpenAIClientProvider /// /// Configuration keys required for management. /// - internal IEnumerable ConfigurationKeys { get; } + internal IReadOnlyList ConfigurationKeys { get; } private OpenAIClientProvider(OpenAIClient client, IEnumerable keys) { this.Client = client; - this.ConfigurationKeys = keys; + this.ConfigurationKeys = keys.ToArray(); } /// From fe3b835ff2ecd1cf743f73cd101b3ecc0c539469 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 8 Aug 2024 11:54:34 -0700 Subject: [PATCH 101/121] Final fixes --- dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs | 2 +- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 2d702675e5ba..e89257cb2fd0 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -167,7 +167,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(agent.Definition, invocationOptions); - options.ToolsOverride.AddRange(tools); // %%% VERIFY + options.ToolsOverride.AddRange(tools); ThreadRun run = await client.CreateRunAsync(threadId, agent.Id, options, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 8a86638be700..3e476bb7989b 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -56,9 +56,9 @@ public sealed class OpenAIAssistantAgent : KernelAgent public RunPollingOptions PollingOptions { get; } = new(); /// - /// Expose predefined tools merged with available kernel functions. + /// Expose predefined tools for run-processing. /// - internal IReadOnlyList Tools => [.. this._assistant.Tools, .. this.Kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))]; + internal IReadOnlyList Tools => this._assistant.Tools; /// /// Define a new . From 7cdfba6caf3e27e25c1540d2c91bf9885eb0af57 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 8 Aug 2024 12:20:18 -0700 Subject: [PATCH 102/121] Test enhancement --- dotnet/src/Agents/Core/ChatCompletionAgent.cs | 5 ++- .../ChatHistorySummarizationReducer.cs | 4 +- .../ChatHistorySummarizationReducerTests.cs | 37 +++++++++++++++++-- .../ChatHistoryTruncationReducerTests.cs | 28 +++++++++++++- 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/dotnet/src/Agents/Core/ChatCompletionAgent.cs b/dotnet/src/Agents/Core/ChatCompletionAgent.cs index 3423308325c2..2ade71b86a9a 100644 --- a/dotnet/src/Agents/Core/ChatCompletionAgent.cs +++ b/dotnet/src/Agents/Core/ChatCompletionAgent.cs @@ -65,7 +65,7 @@ await chatCompletionService.GetChatMessageContentsAsync( history.Add(message); } - foreach (ChatMessageContent message in messages ?? []) + foreach (ChatMessageContent message in messages) { message.AuthorName = this.Name; @@ -121,6 +121,9 @@ public async IAsyncEnumerable InvokeStreamingAsync( /// protected override IEnumerable GetChannelKeys() { + // Distinguish from other channel types. + yield return typeof(ChatHistoryChannel).FullName!; + // Agents with different reducers shall not share the same channel. // Agents with the same or equivalent reducer shall share the same channel. if (this.HistoryReducer != null) diff --git a/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs b/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs index a45bfa57011d..4f909e7e146d 100644 --- a/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs +++ b/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs @@ -154,7 +154,9 @@ public override bool Equals(object? obj) ChatHistorySummarizationReducer? other = obj as ChatHistorySummarizationReducer; return other != null && this._thresholdCount == other._thresholdCount && - this._targetCount == other._targetCount; + this._targetCount == other._targetCount && + this.UseSingleSummary == other.UseSingleSummary && + string.Equals(this.SummarizationInstructions, other.SummarizationInstructions, StringComparison.Ordinal); } /// diff --git a/dotnet/src/Agents/UnitTests/Core/History/ChatHistorySummarizationReducerTests.cs b/dotnet/src/Agents/UnitTests/Core/History/ChatHistorySummarizationReducerTests.cs index f464b6a8214a..7661cfcdf8cd 100644 --- a/dotnet/src/Agents/UnitTests/Core/History/ChatHistorySummarizationReducerTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/History/ChatHistorySummarizationReducerTests.cs @@ -23,7 +23,7 @@ public class ChatHistorySummarizationReducerTests [InlineData(-1)] [InlineData(-1, int.MaxValue)] [InlineData(int.MaxValue, -1)] - public void VerifyChatHistoryConstructorArgumentValidation(int targetCount, int? thresholdCount = null) + public void VerifyConstructorArgumentValidation(int targetCount, int? thresholdCount = null) { Mock mockCompletionService = this.CreateMockCompletionService(); @@ -34,7 +34,7 @@ public void VerifyChatHistoryConstructorArgumentValidation(int targetCount, int? /// Verify object state after initialization. /// [Fact] - public void VerifyChatHistoryInitializationState() + public void VerifyInitializationState() { Mock mockCompletionService = this.CreateMockCompletionService(); @@ -54,11 +54,42 @@ public void VerifyChatHistoryInitializationState() Assert.False(reducer.FailOnError); } + /// + /// Validate equality override. + /// + [Fact] + public void VerifyEquality() + { + Mock mockCompletionService = this.CreateMockCompletionService(); + + ChatHistorySummarizationReducer reducer1 = new(mockCompletionService.Object, 3, 3); + ChatHistorySummarizationReducer reducer2 = new(mockCompletionService.Object, 3, 3); + ChatHistorySummarizationReducer reducer3 = new(mockCompletionService.Object, 3, 3) { UseSingleSummary = false }; + ChatHistorySummarizationReducer reducer4 = new(mockCompletionService.Object, 3, 3) { SummarizationInstructions = "override" }; + ChatHistorySummarizationReducer reducer5 = new(mockCompletionService.Object, 4, 3); + ChatHistorySummarizationReducer reducer6 = new(mockCompletionService.Object, 3, 5); + ChatHistorySummarizationReducer reducer7 = new(mockCompletionService.Object, 3); + ChatHistorySummarizationReducer reducer8 = new(mockCompletionService.Object, 3); + + Assert.True(reducer1.Equals(reducer1)); + Assert.True(reducer1.Equals(reducer2)); + Assert.True(reducer7.Equals(reducer8)); + Assert.True(reducer3.Equals(reducer3)); + Assert.True(reducer4.Equals(reducer4)); + Assert.False(reducer1.Equals(reducer3)); + Assert.False(reducer1.Equals(reducer4)); + Assert.False(reducer1.Equals(reducer5)); + Assert.False(reducer1.Equals(reducer6)); + Assert.False(reducer1.Equals(reducer7)); + Assert.False(reducer1.Equals(reducer8)); + Assert.False(reducer1.Equals(null)); + } + /// /// Validate hash-code expresses reducer equivalency. /// [Fact] - public void VerifyChatHistoryHasCode() + public void VerifyHashCode() { HashSet reducers = []; diff --git a/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryTruncationReducerTests.cs b/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryTruncationReducerTests.cs index eebcf8fc6136..27675420264c 100644 --- a/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryTruncationReducerTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryTruncationReducerTests.cs @@ -21,16 +21,40 @@ public class ChatHistoryTruncationReducerTests [InlineData(-1)] [InlineData(-1, int.MaxValue)] [InlineData(int.MaxValue, -1)] - public void VerifyChatHistoryConstructorArgumentValidation(int targetCount, int? thresholdCount = null) + public void VerifyConstructorArgumentValidation(int targetCount, int? thresholdCount = null) { Assert.Throws(() => new ChatHistoryTruncationReducer(targetCount, thresholdCount)); } + /// + /// Validate equality override. + /// + [Fact] + public void VerifyEquality() + { + ChatHistoryTruncationReducer reducer1 = new(3, 3); + ChatHistoryTruncationReducer reducer2 = new(3, 3); + ChatHistoryTruncationReducer reducer3 = new(4, 3); + ChatHistoryTruncationReducer reducer4 = new(3, 5); + ChatHistoryTruncationReducer reducer5 = new(3); + ChatHistoryTruncationReducer reducer6 = new(3); + + Assert.True(reducer1.Equals(reducer1)); + Assert.True(reducer1.Equals(reducer2)); + Assert.True(reducer5.Equals(reducer6)); + Assert.True(reducer3.Equals(reducer3)); + Assert.False(reducer1.Equals(reducer3)); + Assert.False(reducer1.Equals(reducer4)); + Assert.False(reducer1.Equals(reducer5)); + Assert.False(reducer1.Equals(reducer6)); + Assert.False(reducer1.Equals(null)); + } + /// /// Validate hash-code expresses reducer equivalency. /// [Fact] - public void VerifyChatHistoryHasCode() + public void VerifyHashCode() { HashSet reducers = []; From 5c626564fb625fdf77307d8755913ea88196dfcb Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 8 Aug 2024 13:07:41 -0700 Subject: [PATCH 103/121] Up test coverage --- dotnet/src/Agents/Abstractions/AgentChat.cs | 2 +- dotnet/src/Agents/UnitTests/AgentChatTests.cs | 36 +++++++++++++++++++ dotnet/src/Agents/UnitTests/MockAgent.cs | 3 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Agents/Abstractions/AgentChat.cs b/dotnet/src/Agents/Abstractions/AgentChat.cs index f4654963444e..ca6cbdaab259 100644 --- a/dotnet/src/Agents/Abstractions/AgentChat.cs +++ b/dotnet/src/Agents/Abstractions/AgentChat.cs @@ -285,7 +285,7 @@ private void ClearActivitySignal() /// The activity signal is used to manage ability and visibility for taking actions based /// on conversation history. /// - private void SetActivityOrThrow() + protected void SetActivityOrThrow() { // Note: Interlocked is the absolute lightest synchronization mechanism available in dotnet. int wasActive = Interlocked.CompareExchange(ref this._isActive, 1, 0); diff --git a/dotnet/src/Agents/UnitTests/AgentChatTests.cs b/dotnet/src/Agents/UnitTests/AgentChatTests.cs index 49c36ae73c53..0f796badd440 100644 --- a/dotnet/src/Agents/UnitTests/AgentChatTests.cs +++ b/dotnet/src/Agents/UnitTests/AgentChatTests.cs @@ -3,9 +3,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; +using Moq; using Xunit; namespace SemanticKernel.Agents.UnitTests; @@ -55,6 +58,32 @@ public async Task VerifyAgentChatLifecycleAsync() await this.VerifyHistoryAsync(expectedCount: 5, chat.GetChatMessagesAsync(chat.Agent)); // Agent history } + /// + /// Verify throw exception for system message. + /// + [Fact] + public void VerifyAgentChatRejectsSystemMessge() + { + // Create chat + TestChat chat = new() { LoggerFactory = new Mock().Object }; + + // Verify system message not accepted + Assert.Throws(() => chat.AddChatMessage(new ChatMessageContent(AuthorRole.System, "hi"))); + } + + /// + /// Verify throw exception for if invoked when active. + /// + [Fact] + public async Task VerifyAgentChatThrowsWhenActiveAsync() + { + // Create chat + TestChat chat = new(); + + // Verify system message not accepted + await Assert.ThrowsAsync(() => chat.InvalidInvokeAsync().ToArrayAsync().AsTask()); + } + /// /// Verify the management of instances as they join . /// @@ -119,5 +148,12 @@ private sealed class TestChat : AgentChat public override IAsyncEnumerable InvokeAsync( CancellationToken cancellationToken = default) => this.InvokeAgentAsync(this.Agent, cancellationToken); + + public IAsyncEnumerable InvalidInvokeAsync( + CancellationToken cancellationToken = default) + { + this.SetActivityOrThrow(); + return this.InvokeAgentAsync(this.Agent, cancellationToken); + } } } diff --git a/dotnet/src/Agents/UnitTests/MockAgent.cs b/dotnet/src/Agents/UnitTests/MockAgent.cs index f3b833024001..51fbf36c6ab4 100644 --- a/dotnet/src/Agents/UnitTests/MockAgent.cs +++ b/dotnet/src/Agents/UnitTests/MockAgent.cs @@ -1,4 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -46,7 +47,7 @@ public IAsyncEnumerable InvokeStreamingAsync( /// protected internal override IEnumerable GetChannelKeys() { - yield return typeof(ChatHistoryChannel).FullName!; + yield return Guid.NewGuid().ToString(); } /// From 50774653b13313c4326f9e8aca0523f753c61f7a Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 8 Aug 2024 13:08:36 -0700 Subject: [PATCH 104/121] Typo --- dotnet/src/Agents/UnitTests/AgentChatTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/AgentChatTests.cs b/dotnet/src/Agents/UnitTests/AgentChatTests.cs index 0f796badd440..ed6682d101ba 100644 --- a/dotnet/src/Agents/UnitTests/AgentChatTests.cs +++ b/dotnet/src/Agents/UnitTests/AgentChatTests.cs @@ -62,7 +62,7 @@ public async Task VerifyAgentChatLifecycleAsync() /// Verify throw exception for system message. /// [Fact] - public void VerifyAgentChatRejectsSystemMessge() + public void VerifyAgentChatRejectsSystemMessage() { // Create chat TestChat chat = new() { LoggerFactory = new Mock().Object }; From bf3825ec93d3706d80a079b6de0e0f8fa9ae1b21 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 8 Aug 2024 13:23:39 -0700 Subject: [PATCH 105/121] Namespace + core coverage --- dotnet/src/Agents/UnitTests/AgentChatTests.cs | 1 - .../KernelFunctionSelectionStrategyTests.cs | 31 ++++++++++++++++--- .../KernelFunctionTerminationStrategyTests.cs | 9 +++++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/dotnet/src/Agents/UnitTests/AgentChatTests.cs b/dotnet/src/Agents/UnitTests/AgentChatTests.cs index ed6682d101ba..d9d6cd560e07 100644 --- a/dotnet/src/Agents/UnitTests/AgentChatTests.cs +++ b/dotnet/src/Agents/UnitTests/AgentChatTests.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; diff --git a/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionSelectionStrategyTests.cs b/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionSelectionStrategyTests.cs index af045e67873d..5b2453b47fe1 100644 --- a/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionSelectionStrategyTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionSelectionStrategyTests.cs @@ -27,12 +27,16 @@ public async Task VerifyKernelFunctionSelectionStrategyDefaultsAsync() KernelFunctionSelectionStrategy strategy = new(plugin.Single(), new()) { - ResultParser = (result) => result.GetValue() ?? string.Empty, + ResultParser = (result) => mockAgent.Object.Id, + AgentsVariableName = "agents", + HistoryVariableName = "history", }; Assert.Null(strategy.Arguments); Assert.NotNull(strategy.Kernel); Assert.NotNull(strategy.ResultParser); + Assert.NotEqual("agent", KernelFunctionSelectionStrategy.DefaultAgentsVariableName); + Assert.NotEqual("history", KernelFunctionSelectionStrategy.DefaultHistoryVariableName); Agent nextAgent = await strategy.NextAsync([mockAgent.Object], []); @@ -44,16 +48,35 @@ public async Task VerifyKernelFunctionSelectionStrategyDefaultsAsync() /// Verify strategy mismatch. /// [Fact] - public async Task VerifyKernelFunctionSelectionStrategyParsingAsync() + public async Task VerifyKernelFunctionSelectionStrategyThrowsOnNullResultAsync() { Mock mockAgent = new(); - KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin(string.Empty)); + KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin(mockAgent.Object.Id)); + + KernelFunctionSelectionStrategy strategy = + new(plugin.Single(), new()) + { + Arguments = new(new OpenAIPromptExecutionSettings()) { { "key", mockAgent.Object.Name } }, + ResultParser = (result) => "larry", + }; + + await Assert.ThrowsAsync(() => strategy.NextAsync([mockAgent.Object], [])); + } + + /// + /// Verify strategy mismatch. + /// + [Fact] + public async Task VerifyKernelFunctionSelectionStrategyThrowsOnBadResultAsync() + { + Mock mockAgent = new(); + KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin("")); KernelFunctionSelectionStrategy strategy = new(plugin.Single(), new()) { Arguments = new(new OpenAIPromptExecutionSettings()) { { "key", mockAgent.Object.Name } }, - ResultParser = (result) => result.GetValue() ?? string.Empty, + ResultParser = (result) => result.GetValue() ?? null!, }; await Assert.ThrowsAsync(() => strategy.NextAsync([mockAgent.Object], [])); diff --git a/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionTerminationStrategyTests.cs b/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionTerminationStrategyTests.cs index 6f0b446e5e7a..36ef6e7e6e79 100644 --- a/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionTerminationStrategyTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionTerminationStrategyTests.cs @@ -24,11 +24,18 @@ public async Task VerifyKernelFunctionTerminationStrategyDefaultsAsync() { KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin()); - KernelFunctionTerminationStrategy strategy = new(plugin.Single(), new()); + KernelFunctionTerminationStrategy strategy = + new(plugin.Single(), new()) + { + AgentVariableName = "agent", + HistoryVariableName = "history", + }; Assert.Null(strategy.Arguments); Assert.NotNull(strategy.Kernel); Assert.NotNull(strategy.ResultParser); + Assert.NotEqual("agent", KernelFunctionTerminationStrategy.DefaultAgentVariableName); + Assert.NotEqual("history", KernelFunctionTerminationStrategy.DefaultHistoryVariableName); Mock mockAgent = new(); From 590fbf3d96ad81d4c12ceadbf99f9957c7c97af7 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 8 Aug 2024 14:33:11 -0700 Subject: [PATCH 106/121] Service-selection coverage --- dotnet/src/Agents/Core/ChatCompletionAgent.cs | 6 +++--- .../UnitTests/Core/ChatCompletionAgentTests.cs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Agents/Core/ChatCompletionAgent.cs b/dotnet/src/Agents/Core/ChatCompletionAgent.cs index 2ade71b86a9a..91f5b864e725 100644 --- a/dotnet/src/Agents/Core/ChatCompletionAgent.cs +++ b/dotnet/src/Agents/Core/ChatCompletionAgent.cs @@ -38,7 +38,7 @@ public async IAsyncEnumerable InvokeAsync( kernel ??= this.Kernel; arguments ??= this.Arguments; - (IChatCompletionService chatCompletionService, PromptExecutionSettings? executionSettings) = this.GetChatCompletionService(kernel, arguments); + (IChatCompletionService chatCompletionService, PromptExecutionSettings? executionSettings) = GetChatCompletionService(kernel, arguments); ChatHistory chat = this.SetupAgentChatHistory(history); @@ -83,7 +83,7 @@ public async IAsyncEnumerable InvokeStreamingAsync( kernel ??= this.Kernel; arguments ??= this.Arguments; - (IChatCompletionService chatCompletionService, PromptExecutionSettings? executionSettings) = this.GetChatCompletionService(kernel, arguments); + (IChatCompletionService chatCompletionService, PromptExecutionSettings? executionSettings) = GetChatCompletionService(kernel, arguments); ChatHistory chat = this.SetupAgentChatHistory(history); @@ -148,7 +148,7 @@ protected override Task CreateChannelAsync(CancellationToken cance return Task.FromResult(channel); } - private (IChatCompletionService service, PromptExecutionSettings? executionSettings) GetChatCompletionService(Kernel kernel, KernelArguments? arguments) + internal static (IChatCompletionService service, PromptExecutionSettings? executionSettings) GetChatCompletionService(Kernel kernel, KernelArguments? arguments) { // Need to provide a KernelFunction to the service selector as a container for the execution-settings. KernelFunction nullPrompt = KernelFunctionFactory.CreateFromPrompt("placeholder", arguments?.ExecutionSettings?.Values); diff --git a/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs b/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs index c8a1c0578613..c99c98bdd4b9 100644 --- a/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs @@ -115,6 +115,24 @@ public async Task VerifyChatCompletionAgentStreamingAsync() Times.Once); } + /// + /// Verify the invocation and response of . + /// + [Fact] + public void VerifyChatCompletionServiceSelection() + { + Mock mockService = new(); + Kernel kernel = CreateKernel(mockService.Object); + + (IChatCompletionService service, PromptExecutionSettings? settings) = ChatCompletionAgent.GetChatCompletionService(kernel, null); + Assert.Null(settings); + + (service, settings) = ChatCompletionAgent.GetChatCompletionService(kernel, []); + Assert.Null(settings); + + Assert.Throws(() => ChatCompletionAgent.GetChatCompletionService(kernel, new KernelArguments(new PromptExecutionSettings() { ServiceId = "anything" }))); + } + private static Kernel CreateKernel(IChatCompletionService chatCompletionService) { var builder = Kernel.CreateBuilder(); From e46a93cdb83ae60b46e8013eb5252579067deb26 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Thu, 8 Aug 2024 14:49:43 -0700 Subject: [PATCH 107/121] Even deeper coverage --- .../Core/ChatCompletionAgentTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs b/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs index c99c98bdd4b9..f19453a74058 100644 --- a/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.History; using Microsoft.SemanticKernel.ChatCompletion; using Moq; using Xunit; @@ -133,6 +134,24 @@ public void VerifyChatCompletionServiceSelection() Assert.Throws(() => ChatCompletionAgent.GetChatCompletionService(kernel, new KernelArguments(new PromptExecutionSettings() { ServiceId = "anything" }))); } + /// + /// Verify the invocation and response of . + /// + [Fact] + public void VerifyChatCompletionChannelKeys() + { + ChatCompletionAgent agent1 = new(); + ChatCompletionAgent agent2 = new(); + ChatCompletionAgent agent3 = new() { HistoryReducer = new ChatHistoryTruncationReducer(50) }; + ChatCompletionAgent agent4 = new() { HistoryReducer = new ChatHistoryTruncationReducer(50) }; + ChatCompletionAgent agent5 = new() { HistoryReducer = new ChatHistoryTruncationReducer(100) }; + + Assert.Equal(agent1.GetChannelKeys(), agent2.GetChannelKeys()); + Assert.Equal(agent3.GetChannelKeys(), agent4.GetChannelKeys()); + Assert.NotEqual(agent1.GetChannelKeys(), agent3.GetChannelKeys()); + Assert.NotEqual(agent3.GetChannelKeys(), agent5.GetChannelKeys()); + } + private static Kernel CreateKernel(IChatCompletionService chatCompletionService) { var builder = Kernel.CreateBuilder(); From 9c19d597dbdb3d3b18ff539cc7b1315f2dccc9a5 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 9 Aug 2024 08:44:38 -0700 Subject: [PATCH 108/121] Update dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 3e476bb7989b..97b493eaef22 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -64,7 +64,7 @@ public sealed class OpenAIAssistantAgent : KernelAgent /// Define a new . /// /// The containing services, plugins, and other state for use throughout the operation. - /// Configuration for accessing the API service. + /// OpenAI client provider for accessing the API service. /// The assistant definition. /// The to monitor for cancellation requests. The default is . /// An instance From 176f3727a4877ac07d31f8177877095fce75d3d2 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 9 Aug 2024 08:45:24 -0700 Subject: [PATCH 109/121] Update dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs index 28b625d3eee8..c982c5b0dd81 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs @@ -4,7 +4,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// Defines agent execution options for each invocation. +/// Defines assistant execution options for each invocation. /// /// /// These options are persisted as a single entry of the agent's metadata with key: "__run_options" From 71bd060a1ed8847429704e8c16a1a9aaacc37863 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 9 Aug 2024 08:46:24 -0700 Subject: [PATCH 110/121] Update dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs index c982c5b0dd81..074b92831c92 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// Defines assistant execution options for each invocation. /// /// -/// These options are persisted as a single entry of the agent's metadata with key: "__run_options" +/// These options are persisted as a single entry of the assistant's metadata with key: "__run_options" /// public sealed class OpenAIAssistantExecutionOptions { From a24d151c92c1f2929c23bbcdc729c9b03608dc12 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 9 Aug 2024 08:48:27 -0700 Subject: [PATCH 111/121] Update dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- .../UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs index f624dd62152b..73adb0b2f5cd 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs @@ -24,7 +24,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsDefault() // Setup message with null metadata ChatMessageContent message = new(AuthorRole.User, "test"); - // Create options + // Act MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); // Assert From fd0d81e0abe6239f1d2edfc24691b0d8e78d3953 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 9 Aug 2024 08:48:41 -0700 Subject: [PATCH 112/121] Update dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- .../UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs index 73adb0b2f5cd..5d0e01ba9926 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs @@ -21,7 +21,7 @@ public class AssistantMessageFactoryTests [Fact] public void VerifyAssistantMessageAdapterCreateOptionsDefault() { - // Setup message with null metadata + // Arrange (Setup message with null metadata) ChatMessageContent message = new(AuthorRole.User, "test"); // Act From 7162e3483bab80bc8d88dd653d7801c7c91ad5f0 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 9 Aug 2024 08:56:08 -0700 Subject: [PATCH 113/121] Update dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 97b493eaef22..d60f01389b71 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -363,8 +363,7 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod string? vectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.SingleOrDefault(); bool enableJsonResponse = model.ResponseFormat is not null && model.ResponseFormat == AssistantResponseFormat.JsonObject; - return - new() + return new() { Id = model.Id, Name = model.Name, From 96c3ac0b54625dcca4fafda097c57da254f73284 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 9 Aug 2024 08:59:16 -0700 Subject: [PATCH 114/121] Update dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index e89257cb2fd0..1ef87f72779f 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -428,7 +428,7 @@ private static AnnotationContent GenerateAnnotationContent(TextAnnotation annota }; } - private static ChatMessageContent GenerateCodeInterpreterContent(string agentName, string code) + private static ChatMessageContent GenerateCodeInterpreterContent(string agentName, string pythonCode) { return new ChatMessageContent( From 58ce240bb65d3cdf5f98e105f35648372039c4a5 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:00:04 -0700 Subject: [PATCH 115/121] Update dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 1ef87f72779f..c34aa5f56a0c 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -48,7 +48,7 @@ public static async Task CreateThreadAsync(AssistantClient client, OpenA ToolResources = AssistantToolResourcesFactory.GenerateToolResources(options?.VectorStoreId, options?.CodeInterpreterFileIds), }; - if (options?.Messages != null) + if (options?.Messages is not null) { foreach (ChatMessageContent message in options.Messages) { From 56f0fea936608cd69deea60935e21979b3c3a472 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 9 Aug 2024 09:03:10 -0700 Subject: [PATCH 116/121] Indenting --- .../src/Agents/OpenAI/OpenAIClientProvider.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs index e291de57ee52..3e2e395a77ea 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs @@ -104,12 +104,11 @@ public static OpenAIClientProvider FromClient(OpenAIClient client) private static AzureOpenAIClientOptions CreateAzureClientOptions(Uri? endpoint, HttpClient? httpClient) { - AzureOpenAIClientOptions options = - new() - { - ApplicationId = HttpHeaderConstant.Values.UserAgent, - Endpoint = endpoint, - }; + AzureOpenAIClientOptions options = new() + { + ApplicationId = HttpHeaderConstant.Values.UserAgent, + Endpoint = endpoint, + }; ConfigureClientOptions(httpClient, options); @@ -118,12 +117,11 @@ private static AzureOpenAIClientOptions CreateAzureClientOptions(Uri? endpoint, private static OpenAIClientOptions CreateOpenAIClientOptions(Uri? endpoint, HttpClient? httpClient) { - OpenAIClientOptions options = - new() - { - ApplicationId = HttpHeaderConstant.Values.UserAgent, - Endpoint = endpoint ?? httpClient?.BaseAddress, - }; + OpenAIClientOptions options = new() + { + ApplicationId = HttpHeaderConstant.Values.UserAgent, + Endpoint = endpoint ?? httpClient?.BaseAddress, + }; ConfigureClientOptions(httpClient, options); From c45c1c8baa3a2ff8402a88badf0c700b212e45b2 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:13:40 -0700 Subject: [PATCH 117/121] Update dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> --- dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index c34aa5f56a0c..567b79fee57e 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -388,7 +388,7 @@ private static ChatMessageContent GenerateMessageContent(string? assistantName, // Process text content if (!string.IsNullOrEmpty(itemContent.Text)) { - content.Items.Add(new TextContent(itemContent.Text.Trim())); + content.Items.Add(new TextContent(itemContent.Text)); foreach (TextAnnotation annotation in itemContent.TextAnnotations) { From b0b35e7e10524c8c78622204dfce6268bea81953 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 9 Aug 2024 09:51:08 -0700 Subject: [PATCH 118/121] Updated based on comments --- .../Concepts/Agents/MixedChat_Agents.cs | 5 +- .../Concepts/Agents/MixedChat_Files.cs | 3 +- .../Concepts/Agents/MixedChat_Images.cs | 3 +- .../Agents/OpenAIAssistant_ChartMaker.cs | 3 +- .../OpenAIAssistant_FileManipulation.cs | 3 +- .../Step08_Assistant.cs | 5 +- .../Step09_Assistant_Vision.cs | 3 +- .../Step10_AssistantTool_CodeInterpreter.cs | 5 +- .../Step11_AssistantTool_FileSearch.cs | 5 +- .../src/Agents/Abstractions/AgentChannel.cs | 8 +++ .../Agents/Abstractions/AggregatorChannel.cs | 3 + .../OpenAI/Internal/AssistantThreadActions.cs | 4 +- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 41 +++++++------- .../OpenAI/OpenAIAssistantDefinition.cs | 14 ++++- .../AssistantRunOptionsFactoryTests.cs | 8 +-- .../OpenAI/OpenAIAssistantAgentTests.cs | 56 ++++++------------- .../OpenAI/OpenAIAssistantDefinitionTests.cs | 7 +-- .../Agents/OpenAIAssistantAgentTests.cs | 3 +- 18 files changed, 84 insertions(+), 95 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs index c387ff5704c2..21b19c1d342c 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs @@ -47,12 +47,11 @@ public async Task ChatWithOpenAIAssistantAgentAndChatCompletionAgentAsync() OpenAIAssistantAgent agentWriter = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - provider: this.GetClientProvider(), - definition: new() + clientProvider: this.GetClientProvider(), + definition: new(this.Model) { Instructions = CopyWriterInstructions, Name = CopyWriterName, - ModelId = this.Model, Metadata = AssistantSampleMetadata, }); diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs index 982e41417c13..0219c25f7712 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs @@ -36,11 +36,10 @@ await fileClient.UploadFileAsync( await OpenAIAssistantAgent.CreateAsync( kernel: new(), provider, - new() + new(this.Model) { EnableCodeInterpreter = true, CodeInterpreterFileIds = [uploadFile.Id], // Associate uploaded file with assistant code-interpreter - ModelId = this.Model, Metadata = AssistantSampleMetadata, }); diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs index 4fae255c9b86..142706e8506c 100644 --- a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs +++ b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs @@ -31,12 +31,11 @@ public async Task AnalyzeDataAndGenerateChartAsync() await OpenAIAssistantAgent.CreateAsync( kernel: new(), provider, - new() + new(this.Model) { Instructions = AnalystInstructions, Name = AnalystName, EnableCodeInterpreter = true, - ModelId = this.Model, Metadata = AssistantSampleMetadata, }); diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs index 08aee21c8707..cd81f7c4d187 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs @@ -28,12 +28,11 @@ public async Task GenerateChartWithOpenAIAssistantAgentAsync() await OpenAIAssistantAgent.CreateAsync( kernel: new(), provider, - new() + new(this.Model) { Instructions = AgentInstructions, Name = AgentName, EnableCodeInterpreter = true, - ModelId = this.Model, Metadata = AssistantSampleMetadata, }); diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs index bca6118041e4..dc4af2ad2743 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs @@ -31,11 +31,10 @@ await fileClient.UploadFileAsync( await OpenAIAssistantAgent.CreateAsync( kernel: new(), provider, - new() + new(this.Model) { EnableCodeInterpreter = true, CodeInterpreterFileIds = [uploadFile.Id], - ModelId = this.Model, Metadata = AssistantSampleMetadata, }); diff --git a/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs b/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs index c937c7de9c70..ba4ab065c2a6 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs @@ -23,12 +23,11 @@ public async Task UseSingleAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - provider: this.GetClientProvider(), - new() + clientProvider: this.GetClientProvider(), + new(this.Model) { Instructions = HostInstructions, Name = HostName, - ModelId = this.Model, Metadata = AssistantSampleMetadata, }); diff --git a/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs b/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs index 29a6e108df24..62845f2c4366 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs @@ -25,9 +25,8 @@ public async Task UseSingleAssistantAgentAsync() await OpenAIAssistantAgent.CreateAsync( kernel: new(), provider, - new() + new(this.Model) { - ModelId = this.Model, Metadata = AssistantSampleMetadata, }); diff --git a/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs b/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs index d623c8a28b7b..1205771d66be 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs @@ -17,11 +17,10 @@ public async Task UseCodeInterpreterToolWithAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - provider: this.GetClientProvider(), - new() + clientProvider: this.GetClientProvider(), + new(this.Model) { EnableCodeInterpreter = true, - ModelId = this.Model, Metadata = AssistantSampleMetadata, }); diff --git a/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs b/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs index bfcd93dd3ecb..d34cadaf3707 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs @@ -21,11 +21,10 @@ public async Task UseFileSearchToolWithAssistantAgentAsync() OpenAIAssistantAgent agent = await OpenAIAssistantAgent.CreateAsync( kernel: new(), - provider: this.GetClientProvider(), - new() + clientProvider: this.GetClientProvider(), + new(this.Model) { EnableFileSearch = true, - ModelId = this.Model, Metadata = AssistantSampleMetadata, }); diff --git a/dotnet/src/Agents/Abstractions/AgentChannel.cs b/dotnet/src/Agents/Abstractions/AgentChannel.cs index 9788464a2adb..73469ed723b5 100644 --- a/dotnet/src/Agents/Abstractions/AgentChannel.cs +++ b/dotnet/src/Agents/Abstractions/AgentChannel.cs @@ -31,6 +31,10 @@ public abstract class AgentChannel /// The agent actively interacting with the chat. /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. + /// + /// In the enumeration returned by this method, a message is considered visible if it is intended to be displayed to the user. + /// Example of a non-visible message is function-content for functions that are automatically executed. + /// protected internal abstract IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync( Agent agent, CancellationToken cancellationToken = default); @@ -59,6 +63,10 @@ public abstract class AgentChannel : AgentChannel where TAgent : Agent /// The agent actively interacting with the chat. /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. + /// + /// In the enumeration returned by this method, a message is considered visible if it is intended to be displayed to the user. + /// Example of a non-visible message is function-content for functions that are automatically executed. + /// protected internal abstract IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync( TAgent agent, CancellationToken cancellationToken = default); diff --git a/dotnet/src/Agents/Abstractions/AggregatorChannel.cs b/dotnet/src/Agents/Abstractions/AggregatorChannel.cs index 73561a4eba8b..0c6bc252891d 100644 --- a/dotnet/src/Agents/Abstractions/AggregatorChannel.cs +++ b/dotnet/src/Agents/Abstractions/AggregatorChannel.cs @@ -13,11 +13,13 @@ internal sealed class AggregatorChannel(AgentChat chat) : AgentChannel protected internal override IAsyncEnumerable GetHistoryAsync(CancellationToken cancellationToken = default) { return this._chat.GetChatMessagesAsync(cancellationToken); } + /// protected internal override async IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync(AggregatorAgent agent, [EnumeratorCancellation] CancellationToken cancellationToken = default) { ChatMessageContent? lastMessage = null; @@ -47,6 +49,7 @@ protected internal override IAsyncEnumerable GetHistoryAsync } } + /// protected internal override Task ReceiveAsync(IEnumerable history, CancellationToken cancellationToken = default) { // Always receive the initial history from the owning chat. diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index c34aa5f56a0c..5d508123cc95 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -133,6 +133,8 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist /// /// Invoke the assistant on the specified thread. + /// In the enumeration returned by this method, a message is considered visible if it is intended to be displayed to the user. + /// Example of a non-visible message is function-content for functions that are automatically executed. /// /// The assistant agent to interact with the thread. /// The assistant client @@ -434,7 +436,7 @@ private static ChatMessageContent GenerateCodeInterpreterContent(string agentNam new ChatMessageContent( AuthorRole.Assistant, [ - new TextContent(code) + new TextContent(pythonCode) ]) { AuthorName = agentName, diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index d60f01389b71..f5c4a3588cf8 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -70,17 +70,17 @@ public sealed class OpenAIAssistantAgent : KernelAgent /// An instance public static async Task CreateAsync( Kernel kernel, - OpenAIClientProvider provider, + OpenAIClientProvider clientProvider, OpenAIAssistantDefinition definition, CancellationToken cancellationToken = default) { // Validate input Verify.NotNull(kernel, nameof(kernel)); - Verify.NotNull(provider, nameof(provider)); + Verify.NotNull(clientProvider, nameof(clientProvider)); Verify.NotNull(definition, nameof(definition)); // Create the client - AssistantClient client = CreateClient(provider); + AssistantClient client = CreateClient(clientProvider); // Create the assistant AssistantCreationOptions assistantCreationOptions = CreateAssistantCreationOptions(definition); @@ -88,7 +88,7 @@ public static async Task CreateAsync( // Instantiate the agent return - new OpenAIAssistantAgent(model, provider, client) + new OpenAIAssistantAgent(model, clientProvider, client) { Kernel = kernel, }; @@ -363,23 +363,22 @@ private static OpenAIAssistantDefinition CreateAssistantDefinition(Assistant mod string? vectorStoreId = model.ToolResources?.FileSearch?.VectorStoreIds?.SingleOrDefault(); bool enableJsonResponse = model.ResponseFormat is not null && model.ResponseFormat == AssistantResponseFormat.JsonObject; - return new() - { - Id = model.Id, - Name = model.Name, - Description = model.Description, - Instructions = model.Instructions, - CodeInterpreterFileIds = fileIds, - EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), - EnableFileSearch = model.Tools.Any(t => t is FileSearchToolDefinition), - Metadata = model.Metadata, - ModelId = model.Model, - EnableJsonResponse = enableJsonResponse, - TopP = model.NucleusSamplingFactor, - Temperature = model.Temperature, - VectorStoreId = string.IsNullOrWhiteSpace(vectorStoreId) ? null : vectorStoreId, - ExecutionOptions = options, - }; + return new(model.Model) + { + Id = model.Id, + Name = model.Name, + Description = model.Description, + Instructions = model.Instructions, + CodeInterpreterFileIds = fileIds, + EnableCodeInterpreter = model.Tools.Any(t => t is CodeInterpreterToolDefinition), + EnableFileSearch = model.Tools.Any(t => t is FileSearchToolDefinition), + Metadata = model.Metadata, + EnableJsonResponse = enableJsonResponse, + TopP = model.NucleusSamplingFactor, + Temperature = model.Temperature, + VectorStoreId = string.IsNullOrWhiteSpace(vectorStoreId) ? null : vectorStoreId, + ExecutionOptions = options, + }; } private static AssistantCreationOptions CreateAssistantCreationOptions(OpenAIAssistantDefinition definition) diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs index f52d468c8d6f..7b7015aa3b4a 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantDefinition.cs @@ -12,7 +12,7 @@ public sealed class OpenAIAssistantDefinition /// /// Identifies the AI model targeted by the agent. /// - public string ModelId { get; init; } = string.Empty; + public string ModelId { get; } /// /// The description of the assistant. @@ -97,4 +97,16 @@ public sealed class OpenAIAssistantDefinition /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public OpenAIAssistantExecutionOptions? ExecutionOptions { get; init; } + + /// + /// Initializes a new instance of the class. + /// + /// The targeted model + [JsonConstructor] + public OpenAIAssistantDefinition(string modelId) + { + Verify.NotNullOrWhiteSpace(modelId); + + this.ModelId = modelId; + } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs index 325e93969f20..704cf0252852 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs @@ -19,7 +19,7 @@ public class AssistantRunOptionsFactoryTests public void AssistantRunOptionsFactoryExecutionOptionsNullTest() { OpenAIAssistantDefinition definition = - new() + new("gpt-anything") { Temperature = 0.5F, }; @@ -38,7 +38,7 @@ public void AssistantRunOptionsFactoryExecutionOptionsNullTest() public void AssistantRunOptionsFactoryExecutionOptionsEquivalentTest() { OpenAIAssistantDefinition definition = - new() + new("gpt-anything") { Temperature = 0.5F, }; @@ -62,7 +62,7 @@ public void AssistantRunOptionsFactoryExecutionOptionsEquivalentTest() public void AssistantRunOptionsFactoryExecutionOptionsOverrideTest() { OpenAIAssistantDefinition definition = - new() + new("gpt-anything") { Temperature = 0.5F, ExecutionOptions = @@ -95,7 +95,7 @@ public void AssistantRunOptionsFactoryExecutionOptionsOverrideTest() public void AssistantRunOptionsFactoryExecutionOptionsMetadataTest() { OpenAIAssistantDefinition definition = - new() + new("gpt-anything") { Temperature = 0.5F, ExecutionOptions = diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index a1a1959e9cff..bee75be9d80c 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -32,11 +32,7 @@ public sealed class OpenAIAssistantAgentTests : IDisposable [Fact] public async Task VerifyOpenAIAssistantAgentCreationEmptyAsync() { - OpenAIAssistantDefinition definition = - new() - { - ModelId = "testmodel", - }; + OpenAIAssistantDefinition definition = new("testmodel"); await this.VerifyAgentCreationAsync(definition); } @@ -49,9 +45,8 @@ public async Task VerifyOpenAIAssistantAgentCreationEmptyAsync() public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", Name = "testname", Description = "testdescription", Instructions = "testinstructions", @@ -68,9 +63,8 @@ public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", EnableCodeInterpreter = true, }; @@ -85,9 +79,8 @@ public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterAsync() public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterFilesAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", EnableCodeInterpreter = true, CodeInterpreterFileIds = ["file1", "file2"], }; @@ -103,9 +96,8 @@ public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterFilesAsyn public async Task VerifyOpenAIAssistantAgentCreationWithFileSearchAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", EnableFileSearch = true, }; @@ -120,9 +112,8 @@ public async Task VerifyOpenAIAssistantAgentCreationWithFileSearchAsync() public async Task VerifyOpenAIAssistantAgentCreationWithVectorStoreAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", EnableFileSearch = true, VectorStoreId = "#vs1", }; @@ -138,9 +129,8 @@ public async Task VerifyOpenAIAssistantAgentCreationWithVectorStoreAsync() public async Task VerifyOpenAIAssistantAgentCreationWithMetadataAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", Metadata = new Dictionary() { { "a", "1" }, @@ -159,9 +149,8 @@ public async Task VerifyOpenAIAssistantAgentCreationWithMetadataAsync() public async Task VerifyOpenAIAssistantAgentCreationWithJsonResponseAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", EnableJsonResponse = true, }; @@ -176,9 +165,8 @@ public async Task VerifyOpenAIAssistantAgentCreationWithJsonResponseAsync() public async Task VerifyOpenAIAssistantAgentCreationWithTemperatureAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", Temperature = 2.0F, }; @@ -193,9 +181,8 @@ public async Task VerifyOpenAIAssistantAgentCreationWithTemperatureAsync() public async Task VerifyOpenAIAssistantAgentCreationWithTopPAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", TopP = 2.0F, }; @@ -210,10 +197,9 @@ public async Task VerifyOpenAIAssistantAgentCreationWithTopPAsync() public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", - ExecutionOptions = new(), + ExecutionOptions = new OpenAIAssistantExecutionOptions(), }; await this.VerifyAgentCreationAsync(definition); @@ -227,9 +213,8 @@ public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAsy public async Task VerifyOpenAIAssistantAgentCreationWithExecutionOptionsAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", ExecutionOptions = new() { @@ -249,9 +234,8 @@ public async Task VerifyOpenAIAssistantAgentCreationWithExecutionOptionsAsync() public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAndMetadataAsync() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { - ModelId = "testmodel", ExecutionOptions = new(), Metadata = new Dictionary() { @@ -269,11 +253,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAnd [Fact] public async Task VerifyOpenAIAssistantAgentRetrievalAsync() { - OpenAIAssistantDefinition definition = - new() - { - ModelId = "testmodel", - }; + OpenAIAssistantDefinition definition = new("testmodel"); this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentPayload(definition)); @@ -619,11 +599,7 @@ private static void ValidateAgentDefinition(OpenAIAssistantAgent agent, OpenAIAs private Task CreateAgentAsync() { - OpenAIAssistantDefinition definition = - new() - { - ModelId = "testmodel", - }; + OpenAIAssistantDefinition definition = new("testmodel"); this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentPayload(definition)); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index fa8d903419a5..a13261b58fdf 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -17,10 +17,10 @@ public class OpenAIAssistantDefinitionTests [Fact] public void VerifyOpenAIAssistantDefinitionInitialState() { - OpenAIAssistantDefinition definition = new(); + OpenAIAssistantDefinition definition = new("testmodel"); Assert.Equal(string.Empty, definition.Id); - Assert.Equal(string.Empty, definition.ModelId); + Assert.Equal("testmodel", definition.ModelId); Assert.Null(definition.Name); Assert.Null(definition.Instructions); Assert.Null(definition.Description); @@ -44,11 +44,10 @@ public void VerifyOpenAIAssistantDefinitionInitialState() public void VerifyOpenAIAssistantDefinitionAssignment() { OpenAIAssistantDefinition definition = - new() + new("testmodel") { Id = "testid", Name = "testname", - ModelId = "testmodel", Instructions = "testinstructions", Description = "testdescription", EnableFileSearch = true, diff --git a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs index a4458f6cb470..0dc1ae952c20 100644 --- a/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/IntegrationTests/Agents/OpenAIAssistantAgentTests.cs @@ -76,10 +76,9 @@ private async Task ExecuteAgentAsync( await OpenAIAssistantAgent.CreateAsync( kernel, config, - new() + new(modelName) { Instructions = "Answer questions about the menu.", - ModelId = modelName, }); AgentGroupChat chat = new(); From 631eded8a1f916a22285a850a3da3d08027b4dbd Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 9 Aug 2024 11:40:49 -0700 Subject: [PATCH 119/121] Update unit-test comments --- .../Step04_KernelFunctionStrategies.cs | 2 +- .../src/Agents/UnitTests/AgentChannelTests.cs | 27 +++--- dotnet/src/Agents/UnitTests/AgentChatTests.cs | 33 +++---- .../Agents/UnitTests/AggregatorAgentTests.cs | 24 +++++- .../UnitTests/Core/AgentGroupChatTests.cs | 30 +++++++ .../Core/Chat/AgentGroupChatSettingsTests.cs | 7 ++ .../AggregatorTerminationStrategyTests.cs | 41 +++++---- .../KernelFunctionSelectionStrategyTests.cs | 33 ++++--- .../KernelFunctionTerminationStrategyTests.cs | 14 +-- .../Chat/RegExTerminationStrategyTests.cs | 20 +++-- .../Chat/SequentialSelectionStrategyTests.cs | 38 +++++--- .../Core/ChatCompletionAgentTests.cs | 34 ++++++-- .../UnitTests/Core/ChatHistoryChannelTests.cs | 22 ++--- .../ChatHistoryReducerExtensionsTests.cs | 39 +++++++-- .../ChatHistorySummarizationReducerTests.cs | 38 ++++++-- .../ChatHistoryTruncationReducerTests.cs | 21 ++++- .../Extensions/ChatHistoryExtensionsTests.cs | 4 + .../UnitTests/Internal/BroadcastQueueTests.cs | 31 ++++--- .../UnitTests/Internal/KeyEncoderTests.cs | 5 +- dotnet/src/Agents/UnitTests/MockAgent.cs | 2 +- .../Azure/AddHeaderRequestPolicyTests.cs | 5 +- .../Extensions/AuthorRoleExtensionsTests.cs | 3 + .../Extensions/KernelExtensionsTests.cs | 6 ++ .../KernelFunctionExtensionsTests.cs | 10 +++ .../Internal/AssistantMessageFactoryTests.cs | 45 +++++++--- .../AssistantRunOptionsFactoryTests.cs | 15 ++++ .../OpenAI/OpenAIAssistantAgentTests.cs | 86 +++++++++++++++++-- .../OpenAI/OpenAIAssistantDefinitionTests.cs | 6 ++ .../OpenAIAssistantInvocationOptionsTests.cs | 8 ++ .../OpenAI/OpenAIClientProviderTests.cs | 15 ++++ .../OpenAIThreadCreationOptionsTests.cs | 8 ++ .../OpenAI/RunPollingOptionsTests.cs | 6 ++ 32 files changed, 518 insertions(+), 160 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs b/dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs index 24a4a1dc70b5..36424e6c268b 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs @@ -73,7 +73,7 @@ No participant should take more than one turn in a row. - {{{CopyWriterName}}} Always follow these rules when selecting the next participant: - - After user input, it is {{{CopyWriterName}}}'a turn. + - After user input, it is {{{CopyWriterName}}}'s turn. - After {{{CopyWriterName}}}, it is {{{ReviewerName}}}'s turn. - After {{{ReviewerName}}}, it is {{{CopyWriterName}}}'s turn. diff --git a/dotnet/src/Agents/UnitTests/AgentChannelTests.cs b/dotnet/src/Agents/UnitTests/AgentChannelTests.cs index 2a680614a54f..17994a12e6a0 100644 --- a/dotnet/src/Agents/UnitTests/AgentChannelTests.cs +++ b/dotnet/src/Agents/UnitTests/AgentChannelTests.cs @@ -23,20 +23,26 @@ public class AgentChannelTests [Fact] public async Task VerifyAgentChannelUpcastAsync() { + // Arrange TestChannel channel = new(); + // Assert Assert.Equal(0, channel.InvokeCount); - var messages = channel.InvokeAgentAsync(new TestAgent()).ToArrayAsync(); + // Act + var messages = channel.InvokeAgentAsync(new MockAgent()).ToArrayAsync(); + // Assert Assert.Equal(1, channel.InvokeCount); + // Act await Assert.ThrowsAsync(() => channel.InvokeAgentAsync(new NextAgent()).ToArrayAsync().AsTask()); + // Assert Assert.Equal(1, channel.InvokeCount); } /// /// Not using mock as the goal here is to provide entrypoint to protected method. /// - private sealed class TestChannel : AgentChannel + private sealed class TestChannel : AgentChannel { public int InvokeCount { get; private set; } @@ -44,7 +50,7 @@ private sealed class TestChannel : AgentChannel => base.InvokeAsync(agent, cancellationToken); #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - protected internal override async IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync(TestAgent agent, [EnumeratorCancellation] CancellationToken cancellationToken = default) + protected internal override async IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync(MockAgent agent, [EnumeratorCancellation] CancellationToken cancellationToken = default) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { this.InvokeCount++; @@ -63,18 +69,5 @@ protected internal override Task ReceiveAsync(IEnumerable hi } } - private sealed class NextAgent : TestAgent; - - private class TestAgent : KernelAgent - { - protected internal override Task CreateChannelAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - protected internal override IEnumerable GetChannelKeys() - { - throw new NotImplementedException(); - } - } + private sealed class NextAgent : MockAgent; } diff --git a/dotnet/src/Agents/UnitTests/AgentChatTests.cs b/dotnet/src/Agents/UnitTests/AgentChatTests.cs index d9d6cd560e07..cd83ab8b9f45 100644 --- a/dotnet/src/Agents/UnitTests/AgentChatTests.cs +++ b/dotnet/src/Agents/UnitTests/AgentChatTests.cs @@ -23,36 +23,36 @@ public class AgentChatTests [Fact] public async Task VerifyAgentChatLifecycleAsync() { - // Create chat + // Arrange: Create chat TestChat chat = new(); - // Verify initial state + // Assert: Verify initial state Assert.False(chat.IsActive); await this.VerifyHistoryAsync(expectedCount: 0, chat.GetChatMessagesAsync()); // Primary history await this.VerifyHistoryAsync(expectedCount: 0, chat.GetChatMessagesAsync(chat.Agent)); // Agent history - // Inject history + // Act: Inject history chat.AddChatMessages([new ChatMessageContent(AuthorRole.User, "More")]); chat.AddChatMessages([new ChatMessageContent(AuthorRole.User, "And then some")]); - // Verify updated history + // Assert: Verify updated history await this.VerifyHistoryAsync(expectedCount: 2, chat.GetChatMessagesAsync()); // Primary history await this.VerifyHistoryAsync(expectedCount: 0, chat.GetChatMessagesAsync(chat.Agent)); // Agent hasn't joined - // Invoke with input & verify (agent joins chat) + // Act: Invoke with input & verify (agent joins chat) chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, "hi")); await chat.InvokeAsync().ToArrayAsync(); - Assert.Equal(1, chat.Agent.InvokeCount); - // Verify updated history + // Assert: Verify updated history + Assert.Equal(1, chat.Agent.InvokeCount); await this.VerifyHistoryAsync(expectedCount: 4, chat.GetChatMessagesAsync()); // Primary history await this.VerifyHistoryAsync(expectedCount: 4, chat.GetChatMessagesAsync(chat.Agent)); // Agent history - // Invoke without input & verify + // Act: Invoke without input await chat.InvokeAsync().ToArrayAsync(); - Assert.Equal(2, chat.Agent.InvokeCount); - // Verify final history + // Assert: Verify final history + Assert.Equal(2, chat.Agent.InvokeCount); await this.VerifyHistoryAsync(expectedCount: 5, chat.GetChatMessagesAsync()); // Primary history await this.VerifyHistoryAsync(expectedCount: 5, chat.GetChatMessagesAsync(chat.Agent)); // Agent history } @@ -63,10 +63,10 @@ public async Task VerifyAgentChatLifecycleAsync() [Fact] public void VerifyAgentChatRejectsSystemMessage() { - // Create chat + // Arrange: Create chat TestChat chat = new() { LoggerFactory = new Mock().Object }; - // Verify system message not accepted + // Assert and Act: Verify system message not accepted Assert.Throws(() => chat.AddChatMessage(new ChatMessageContent(AuthorRole.System, "hi"))); } @@ -76,10 +76,10 @@ public void VerifyAgentChatRejectsSystemMessage() [Fact] public async Task VerifyAgentChatThrowsWhenActiveAsync() { - // Create chat + // Arrange: Create chat TestChat chat = new(); - // Verify system message not accepted + // Assert and Act: Verify system message not accepted await Assert.ThrowsAsync(() => chat.InvalidInvokeAsync().ToArrayAsync().AsTask()); } @@ -89,13 +89,14 @@ public async Task VerifyAgentChatThrowsWhenActiveAsync() [Fact(Skip = "Not 100% reliable for github workflows, but useful for dev testing.")] public async Task VerifyGroupAgentChatConcurrencyAsync() { + // Arrange TestChat chat = new(); Task[] tasks; int isActive = 0; - // Queue concurrent tasks + // Act: Queue concurrent tasks object syncObject = new(); lock (syncObject) { @@ -117,7 +118,7 @@ public async Task VerifyGroupAgentChatConcurrencyAsync() await Task.Yield(); - // Verify failure + // Assert: Verify failure await Assert.ThrowsAsync(() => Task.WhenAll(tasks)); async Task SynchronizedInvokeAsync() diff --git a/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs b/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs index 1a607ea7e6c7..e6668c7ea568 100644 --- a/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs @@ -21,6 +21,7 @@ public class AggregatorAgentTests [InlineData(AggregatorMode.Flat, 2)] public async Task VerifyAggregatorAgentUsageAsync(AggregatorMode mode, int modeOffset) { + // Arrange Agent agent1 = CreateMockAgent(); Agent agent2 = CreateMockAgent(); Agent agent3 = CreateMockAgent(); @@ -44,38 +45,57 @@ public async Task VerifyAggregatorAgentUsageAsync(AggregatorMode mode, int modeO // Add message to outer chat (no agent has joined) uberChat.AddChatMessage(new ChatMessageContent(AuthorRole.User, "test uber")); + // Act var messages = await uberChat.GetChatMessagesAsync().ToArrayAsync(); + // Assert Assert.Single(messages); + // Act messages = await uberChat.GetChatMessagesAsync(uberAgent).ToArrayAsync(); + // Assert Assert.Empty(messages); // Agent hasn't joined chat, no broadcast + // Act messages = await groupChat.GetChatMessagesAsync().ToArrayAsync(); + // Assert Assert.Empty(messages); // Agent hasn't joined chat, no broadcast - // Add message to inner chat (not visible to parent) + // Arrange: Add message to inner chat (not visible to parent) groupChat.AddChatMessage(new ChatMessageContent(AuthorRole.User, "test inner")); + // Act messages = await uberChat.GetChatMessagesAsync().ToArrayAsync(); + // Assert Assert.Single(messages); + // Act messages = await uberChat.GetChatMessagesAsync(uberAgent).ToArrayAsync(); + // Assert Assert.Empty(messages); // Agent still hasn't joined chat + // Act messages = await groupChat.GetChatMessagesAsync().ToArrayAsync(); + // Assert Assert.Single(messages); - // Invoke outer chat (outer chat captures final inner message) + // Act: Invoke outer chat (outer chat captures final inner message) messages = await uberChat.InvokeAsync(uberAgent).ToArrayAsync(); + // Assert Assert.Equal(1 + modeOffset, messages.Length); // New messages generated from inner chat + // Act messages = await uberChat.GetChatMessagesAsync().ToArrayAsync(); + // Assert Assert.Equal(2 + modeOffset, messages.Length); // Total messages on uber chat + // Act messages = await groupChat.GetChatMessagesAsync().ToArrayAsync(); + // Assert Assert.Equal(5, messages.Length); // Total messages on inner chat once synchronized + // Act messages = await uberChat.GetChatMessagesAsync(uberAgent).ToArrayAsync(); + // Assert Assert.Equal(5, messages.Length); // Total messages on inner chat once synchronized (agent equivalent) } diff --git a/dotnet/src/Agents/UnitTests/Core/AgentGroupChatTests.cs b/dotnet/src/Agents/UnitTests/Core/AgentGroupChatTests.cs index ad7428f6f0b9..62420f90e62b 100644 --- a/dotnet/src/Agents/UnitTests/Core/AgentGroupChatTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/AgentGroupChatTests.cs @@ -23,12 +23,18 @@ public class AgentGroupChatTests [Fact] public void VerifyGroupAgentChatDefaultState() { + // Arrange AgentGroupChat chat = new(); + + // Assert Assert.Empty(chat.Agents); Assert.NotNull(chat.ExecutionSettings); Assert.False(chat.IsComplete); + // Act chat.IsComplete = true; + + // Assert Assert.True(chat.IsComplete); } @@ -38,21 +44,30 @@ public void VerifyGroupAgentChatDefaultState() [Fact] public async Task VerifyGroupAgentChatAgentMembershipAsync() { + // Arrange Agent agent1 = CreateMockAgent(); Agent agent2 = CreateMockAgent(); Agent agent3 = CreateMockAgent(); Agent agent4 = CreateMockAgent(); AgentGroupChat chat = new(agent1, agent2); + + // Assert Assert.Equal(2, chat.Agents.Count); + // Act chat.AddAgent(agent3); + // Assert Assert.Equal(3, chat.Agents.Count); + // Act var messages = await chat.InvokeAsync(agent4, isJoining: false).ToArrayAsync(); + // Assert Assert.Equal(3, chat.Agents.Count); + // Act messages = await chat.InvokeAsync(agent4).ToArrayAsync(); + // Assert Assert.Equal(4, chat.Agents.Count); } @@ -62,6 +77,7 @@ public async Task VerifyGroupAgentChatAgentMembershipAsync() [Fact] public async Task VerifyGroupAgentChatMultiTurnAsync() { + // Arrange Agent agent1 = CreateMockAgent(); Agent agent2 = CreateMockAgent(); Agent agent3 = CreateMockAgent(); @@ -81,10 +97,14 @@ public async Task VerifyGroupAgentChatMultiTurnAsync() IsComplete = true }; + // Act and Assert await Assert.ThrowsAsync(() => chat.InvokeAsync(CancellationToken.None).ToArrayAsync().AsTask()); + // Act chat.ExecutionSettings.TerminationStrategy.AutomaticReset = true; var messages = await chat.InvokeAsync(CancellationToken.None).ToArrayAsync(); + + // Assert Assert.Equal(9, messages.Length); Assert.False(chat.IsComplete); @@ -111,6 +131,7 @@ public async Task VerifyGroupAgentChatMultiTurnAsync() [Fact] public async Task VerifyGroupAgentChatFailedSelectionAsync() { + // Arrange AgentGroupChat chat = Create3AgentChat(); chat.ExecutionSettings = @@ -128,6 +149,7 @@ public async Task VerifyGroupAgentChatFailedSelectionAsync() // Remove max-limit in order to isolate the target behavior. chat.ExecutionSettings.TerminationStrategy.MaximumIterations = int.MaxValue; + // Act and Assert await Assert.ThrowsAsync(() => chat.InvokeAsync().ToArrayAsync().AsTask()); } @@ -137,6 +159,7 @@ public async Task VerifyGroupAgentChatFailedSelectionAsync() [Fact] public async Task VerifyGroupAgentChatMultiTurnTerminationAsync() { + // Arrange AgentGroupChat chat = Create3AgentChat(); chat.ExecutionSettings = @@ -150,7 +173,10 @@ public async Task VerifyGroupAgentChatMultiTurnTerminationAsync() } }; + // Act var messages = await chat.InvokeAsync(CancellationToken.None).ToArrayAsync(); + + // Assert Assert.Single(messages); Assert.True(chat.IsComplete); } @@ -161,6 +187,7 @@ public async Task VerifyGroupAgentChatMultiTurnTerminationAsync() [Fact] public async Task VerifyGroupAgentChatDiscreteTerminationAsync() { + // Arrange Agent agent1 = CreateMockAgent(); AgentGroupChat chat = @@ -178,7 +205,10 @@ public async Task VerifyGroupAgentChatDiscreteTerminationAsync() } }; + // Act var messages = await chat.InvokeAsync(agent1).ToArrayAsync(); + + // Assert Assert.Single(messages); Assert.True(chat.IsComplete); } diff --git a/dotnet/src/Agents/UnitTests/Core/Chat/AgentGroupChatSettingsTests.cs b/dotnet/src/Agents/UnitTests/Core/Chat/AgentGroupChatSettingsTests.cs index d17391ee24be..ecb5cd6eee33 100644 --- a/dotnet/src/Agents/UnitTests/Core/Chat/AgentGroupChatSettingsTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Chat/AgentGroupChatSettingsTests.cs @@ -16,7 +16,10 @@ public class AgentGroupChatSettingsTests [Fact] public void VerifyChatExecutionSettingsDefault() { + // Arrange AgentGroupChatSettings settings = new(); + + // Assert Assert.IsType(settings.TerminationStrategy); Assert.Equal(1, settings.TerminationStrategy.MaximumIterations); Assert.IsType(settings.SelectionStrategy); @@ -28,6 +31,7 @@ public void VerifyChatExecutionSettingsDefault() [Fact] public void VerifyChatExecutionContinuationStrategyDefault() { + // Arrange Mock strategyMock = new(); AgentGroupChatSettings settings = new() @@ -35,6 +39,7 @@ public void VerifyChatExecutionContinuationStrategyDefault() TerminationStrategy = strategyMock.Object }; + // Assert Assert.Equal(strategyMock.Object, settings.TerminationStrategy); } @@ -44,6 +49,7 @@ public void VerifyChatExecutionContinuationStrategyDefault() [Fact] public void VerifyChatExecutionSelectionStrategyDefault() { + // Arrange Mock strategyMock = new(); AgentGroupChatSettings settings = new() @@ -51,6 +57,7 @@ public void VerifyChatExecutionSelectionStrategyDefault() SelectionStrategy = strategyMock.Object }; + // Assert Assert.NotNull(settings.SelectionStrategy); Assert.Equal(strategyMock.Object, settings.SelectionStrategy); } diff --git a/dotnet/src/Agents/UnitTests/Core/Chat/AggregatorTerminationStrategyTests.cs b/dotnet/src/Agents/UnitTests/Core/Chat/AggregatorTerminationStrategyTests.cs index 6ad6fd75b18f..5af211c6cdf1 100644 --- a/dotnet/src/Agents/UnitTests/Core/Chat/AggregatorTerminationStrategyTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Chat/AggregatorTerminationStrategyTests.cs @@ -6,7 +6,6 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; -using Moq; using Xunit; namespace SemanticKernel.Agents.UnitTests.Core.Chat; @@ -22,7 +21,10 @@ public class AggregatorTerminationStrategyTests [Fact] public void VerifyAggregateTerminationStrategyInitialState() { + // Arrange AggregatorTerminationStrategy strategy = new(); + + // Assert Assert.Equal(AggregateTerminationCondition.All, strategy.Condition); } @@ -32,14 +34,16 @@ public void VerifyAggregateTerminationStrategyInitialState() [Fact] public async Task VerifyAggregateTerminationStrategyAnyAsync() { + // Arrange TerminationStrategy strategyMockTrue = new MockTerminationStrategy(terminationResult: true); TerminationStrategy strategyMockFalse = new MockTerminationStrategy(terminationResult: false); - Mock agentMock = new(); + MockAgent agentMock = new(); + // Act and Assert await VerifyResultAsync( expectedResult: true, - agentMock.Object, + agentMock, new(strategyMockTrue, strategyMockFalse) { Condition = AggregateTerminationCondition.Any, @@ -47,7 +51,7 @@ await VerifyResultAsync( await VerifyResultAsync( expectedResult: false, - agentMock.Object, + agentMock, new(strategyMockFalse, strategyMockFalse) { Condition = AggregateTerminationCondition.Any, @@ -55,7 +59,7 @@ await VerifyResultAsync( await VerifyResultAsync( expectedResult: true, - agentMock.Object, + agentMock, new(strategyMockTrue, strategyMockTrue) { Condition = AggregateTerminationCondition.Any, @@ -68,14 +72,16 @@ await VerifyResultAsync( [Fact] public async Task VerifyAggregateTerminationStrategyAllAsync() { + // Arrange TerminationStrategy strategyMockTrue = new MockTerminationStrategy(terminationResult: true); TerminationStrategy strategyMockFalse = new MockTerminationStrategy(terminationResult: false); - Mock agentMock = new(); + MockAgent agentMock = new(); + // Act and Assert await VerifyResultAsync( expectedResult: false, - agentMock.Object, + agentMock, new(strategyMockTrue, strategyMockFalse) { Condition = AggregateTerminationCondition.All, @@ -83,7 +89,7 @@ await VerifyResultAsync( await VerifyResultAsync( expectedResult: false, - agentMock.Object, + agentMock, new(strategyMockFalse, strategyMockFalse) { Condition = AggregateTerminationCondition.All, @@ -91,7 +97,7 @@ await VerifyResultAsync( await VerifyResultAsync( expectedResult: true, - agentMock.Object, + agentMock, new(strategyMockTrue, strategyMockTrue) { Condition = AggregateTerminationCondition.All, @@ -104,34 +110,39 @@ await VerifyResultAsync( [Fact] public async Task VerifyAggregateTerminationStrategyAgentAsync() { + // Arrange TerminationStrategy strategyMockTrue = new MockTerminationStrategy(terminationResult: true); TerminationStrategy strategyMockFalse = new MockTerminationStrategy(terminationResult: false); - Mock agentMockA = new(); - Mock agentMockB = new(); + MockAgent agentMockA = new(); + MockAgent agentMockB = new(); + // Act and Assert await VerifyResultAsync( expectedResult: false, - agentMockB.Object, + agentMockB, new(strategyMockTrue, strategyMockTrue) { - Agents = [agentMockA.Object], + Agents = [agentMockA], Condition = AggregateTerminationCondition.All, }); await VerifyResultAsync( expectedResult: true, - agentMockB.Object, + agentMockB, new(strategyMockTrue, strategyMockTrue) { - Agents = [agentMockB.Object], + Agents = [agentMockB], Condition = AggregateTerminationCondition.All, }); } private static async Task VerifyResultAsync(bool expectedResult, Agent agent, AggregatorTerminationStrategy strategyRoot) { + // Act var result = await strategyRoot.ShouldTerminateAsync(agent, []); + + // Assert Assert.Equal(expectedResult, result); } diff --git a/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionSelectionStrategyTests.cs b/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionSelectionStrategyTests.cs index 5b2453b47fe1..83cb9a3ea337 100644 --- a/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionSelectionStrategyTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionSelectionStrategyTests.cs @@ -5,7 +5,6 @@ using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; using Microsoft.SemanticKernel.Connectors.OpenAI; -using Moq; using Xunit; namespace SemanticKernel.Agents.UnitTests.Core.Chat; @@ -21,27 +20,31 @@ public class KernelFunctionSelectionStrategyTests [Fact] public async Task VerifyKernelFunctionSelectionStrategyDefaultsAsync() { - Mock mockAgent = new(); - KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin(mockAgent.Object.Id)); + // Arrange + MockAgent mockAgent = new(); + KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin(mockAgent.Id)); KernelFunctionSelectionStrategy strategy = new(plugin.Single(), new()) { - ResultParser = (result) => mockAgent.Object.Id, + ResultParser = (result) => mockAgent.Id, AgentsVariableName = "agents", HistoryVariableName = "history", }; + // Assert Assert.Null(strategy.Arguments); Assert.NotNull(strategy.Kernel); Assert.NotNull(strategy.ResultParser); Assert.NotEqual("agent", KernelFunctionSelectionStrategy.DefaultAgentsVariableName); Assert.NotEqual("history", KernelFunctionSelectionStrategy.DefaultHistoryVariableName); - Agent nextAgent = await strategy.NextAsync([mockAgent.Object], []); + // Act + Agent nextAgent = await strategy.NextAsync([mockAgent], []); + // Assert Assert.NotNull(nextAgent); - Assert.Equal(mockAgent.Object, nextAgent); + Assert.Equal(mockAgent, nextAgent); } /// @@ -50,17 +53,19 @@ public async Task VerifyKernelFunctionSelectionStrategyDefaultsAsync() [Fact] public async Task VerifyKernelFunctionSelectionStrategyThrowsOnNullResultAsync() { - Mock mockAgent = new(); - KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin(mockAgent.Object.Id)); + // Arrange + MockAgent mockAgent = new(); + KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin(mockAgent.Id)); KernelFunctionSelectionStrategy strategy = new(plugin.Single(), new()) { - Arguments = new(new OpenAIPromptExecutionSettings()) { { "key", mockAgent.Object.Name } }, + Arguments = new(new OpenAIPromptExecutionSettings()) { { "key", mockAgent.Name } }, ResultParser = (result) => "larry", }; - await Assert.ThrowsAsync(() => strategy.NextAsync([mockAgent.Object], [])); + // Act and Assert + await Assert.ThrowsAsync(() => strategy.NextAsync([mockAgent], [])); } /// @@ -69,17 +74,19 @@ public async Task VerifyKernelFunctionSelectionStrategyThrowsOnNullResultAsync() [Fact] public async Task VerifyKernelFunctionSelectionStrategyThrowsOnBadResultAsync() { - Mock mockAgent = new(); + // Arrange + MockAgent mockAgent = new(); KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin("")); KernelFunctionSelectionStrategy strategy = new(plugin.Single(), new()) { - Arguments = new(new OpenAIPromptExecutionSettings()) { { "key", mockAgent.Object.Name } }, + Arguments = new(new OpenAIPromptExecutionSettings()) { { "key", mockAgent.Name } }, ResultParser = (result) => result.GetValue() ?? null!, }; - await Assert.ThrowsAsync(() => strategy.NextAsync([mockAgent.Object], [])); + // Act and Assert + await Assert.ThrowsAsync(() => strategy.NextAsync([mockAgent], [])); } private sealed class TestPlugin(string agentName) diff --git a/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionTerminationStrategyTests.cs b/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionTerminationStrategyTests.cs index 36ef6e7e6e79..7ee5cf838bc3 100644 --- a/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionTerminationStrategyTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Chat/KernelFunctionTerminationStrategyTests.cs @@ -3,10 +3,8 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; using Microsoft.SemanticKernel.Connectors.OpenAI; -using Moq; using Xunit; namespace SemanticKernel.Agents.UnitTests.Core.Chat; @@ -22,6 +20,7 @@ public class KernelFunctionTerminationStrategyTests [Fact] public async Task VerifyKernelFunctionTerminationStrategyDefaultsAsync() { + // Arrange KernelPlugin plugin = KernelPluginFactory.CreateFromObject(new TestPlugin()); KernelFunctionTerminationStrategy strategy = @@ -31,15 +30,16 @@ public async Task VerifyKernelFunctionTerminationStrategyDefaultsAsync() HistoryVariableName = "history", }; + // Assert Assert.Null(strategy.Arguments); Assert.NotNull(strategy.Kernel); Assert.NotNull(strategy.ResultParser); Assert.NotEqual("agent", KernelFunctionTerminationStrategy.DefaultAgentVariableName); Assert.NotEqual("history", KernelFunctionTerminationStrategy.DefaultHistoryVariableName); - Mock mockAgent = new(); - - bool isTerminating = await strategy.ShouldTerminateAsync(mockAgent.Object, []); + // Act + MockAgent mockAgent = new(); + bool isTerminating = await strategy.ShouldTerminateAsync(mockAgent, []); Assert.True(isTerminating); } @@ -59,9 +59,9 @@ public async Task VerifyKernelFunctionTerminationStrategyParsingAsync() ResultParser = (result) => string.Equals("test", result.GetValue(), StringComparison.OrdinalIgnoreCase) }; - Mock mockAgent = new(); + MockAgent mockAgent = new(); - bool isTerminating = await strategy.ShouldTerminateAsync(mockAgent.Object, []); + bool isTerminating = await strategy.ShouldTerminateAsync(mockAgent, []); Assert.True(isTerminating); } diff --git a/dotnet/src/Agents/UnitTests/Core/Chat/RegExTerminationStrategyTests.cs b/dotnet/src/Agents/UnitTests/Core/Chat/RegExTerminationStrategyTests.cs index a1b739ae1d1e..196a89ded6e3 100644 --- a/dotnet/src/Agents/UnitTests/Core/Chat/RegExTerminationStrategyTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Chat/RegExTerminationStrategyTests.cs @@ -2,10 +2,8 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; using Microsoft.SemanticKernel.ChatCompletion; -using Moq; using Xunit; namespace SemanticKernel.Agents.UnitTests.Core.Chat; @@ -13,7 +11,7 @@ namespace SemanticKernel.Agents.UnitTests.Core.Chat; /// /// Unit testing of . /// -public class RegexTerminationStrategyTests +public partial class RegexTerminationStrategyTests { /// /// Verify abililty of strategy to match expression. @@ -21,10 +19,12 @@ public class RegexTerminationStrategyTests [Fact] public async Task VerifyExpressionTerminationStrategyAsync() { + // Arrange RegexTerminationStrategy strategy = new("test"); - Regex r = new("(?:^|\\W)test(?:$|\\W)"); + Regex r = MyRegex(); + // Act and Assert await VerifyResultAsync( expectedResult: false, new(r), @@ -38,9 +38,17 @@ await VerifyResultAsync( private static async Task VerifyResultAsync(bool expectedResult, RegexTerminationStrategy strategyRoot, string content) { + // Arrange ChatMessageContent message = new(AuthorRole.Assistant, content); - Mock agent = new(); - var result = await strategyRoot.ShouldTerminateAsync(agent.Object, [message]); + MockAgent agent = new(); + + // Act + var result = await strategyRoot.ShouldTerminateAsync(agent, [message]); + + // Assert Assert.Equal(expectedResult, result); } + + [GeneratedRegex("(?:^|\\W)test(?:$|\\W)")] + private static partial Regex MyRegex(); } diff --git a/dotnet/src/Agents/UnitTests/Core/Chat/SequentialSelectionStrategyTests.cs b/dotnet/src/Agents/UnitTests/Core/Chat/SequentialSelectionStrategyTests.cs index 04339a8309e4..8f7ff6b29d03 100644 --- a/dotnet/src/Agents/UnitTests/Core/Chat/SequentialSelectionStrategyTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Chat/SequentialSelectionStrategyTests.cs @@ -3,7 +3,6 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; -using Moq; using Xunit; namespace SemanticKernel.Agents.UnitTests.Core.Chat; @@ -19,28 +18,38 @@ public class SequentialSelectionStrategyTests [Fact] public async Task VerifySequentialSelectionStrategyTurnsAsync() { - Mock agent1 = new(); - Mock agent2 = new(); + // Arrange + MockAgent agent1 = new(); + MockAgent agent2 = new(); - Agent[] agents = [agent1.Object, agent2.Object]; + Agent[] agents = [agent1, agent2]; SequentialSelectionStrategy strategy = new(); - await VerifyNextAgent(agent1.Object); - await VerifyNextAgent(agent2.Object); - await VerifyNextAgent(agent1.Object); - await VerifyNextAgent(agent2.Object); - await VerifyNextAgent(agent1.Object); + // Act and Assert + await VerifyNextAgent(agent1); + await VerifyNextAgent(agent2); + await VerifyNextAgent(agent1); + await VerifyNextAgent(agent2); + await VerifyNextAgent(agent1); + // Arrange strategy.Reset(); - await VerifyNextAgent(agent1.Object); - // Verify index does not exceed current bounds. - agents = [agent1.Object]; - await VerifyNextAgent(agent1.Object); + // Act and Assert + await VerifyNextAgent(agent1); + + // Arrange: Verify index does not exceed current bounds. + agents = [agent1]; + + // Act and Assert + await VerifyNextAgent(agent1); async Task VerifyNextAgent(Agent agent1) { + // Act Agent? nextAgent = await strategy.NextAsync(agents, []); + + // Assert Assert.NotNull(nextAgent); Assert.Equal(agent1.Id, nextAgent.Id); } @@ -52,7 +61,10 @@ async Task VerifyNextAgent(Agent agent1) [Fact] public async Task VerifySequentialSelectionStrategyEmptyAsync() { + // Arrange SequentialSelectionStrategy strategy = new(); + + // Act and Assert await Assert.ThrowsAsync(() => strategy.NextAsync([], [])); } } diff --git a/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs b/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs index f19453a74058..01debd8ded5f 100644 --- a/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs @@ -23,6 +23,7 @@ public class ChatCompletionAgentTests [Fact] public void VerifyChatCompletionAgentDefinition() { + // Arrange ChatCompletionAgent agent = new() { @@ -31,6 +32,7 @@ public void VerifyChatCompletionAgentDefinition() Name = "test name", }; + // Assert Assert.NotNull(agent.Id); Assert.Equal("test instructions", agent.Instructions); Assert.Equal("test description", agent.Description); @@ -44,7 +46,8 @@ public void VerifyChatCompletionAgentDefinition() [Fact] public async Task VerifyChatCompletionAgentInvocationAsync() { - var mockService = new Mock(); + // Arrange + Mock mockService = new(); mockService.Setup( s => s.GetChatMessageContentsAsync( It.IsAny(), @@ -52,16 +55,18 @@ public async Task VerifyChatCompletionAgentInvocationAsync() It.IsAny(), It.IsAny())).ReturnsAsync([new(AuthorRole.Assistant, "what?")]); - var agent = - new ChatCompletionAgent() + ChatCompletionAgent agent = + new() { Instructions = "test instructions", Kernel = CreateKernel(mockService.Object), Arguments = [], }; - var result = await agent.InvokeAsync([]).ToArrayAsync(); + // Act + ChatMessageContent[] result = await agent.InvokeAsync([]).ToArrayAsync(); + // Assert Assert.Single(result); mockService.Verify( @@ -80,13 +85,14 @@ public async Task VerifyChatCompletionAgentInvocationAsync() [Fact] public async Task VerifyChatCompletionAgentStreamingAsync() { + // Arrange StreamingChatMessageContent[] returnContent = [ new(AuthorRole.Assistant, "wh"), new(AuthorRole.Assistant, "at?"), ]; - var mockService = new Mock(); + Mock mockService = new(); mockService.Setup( s => s.GetStreamingChatMessageContentsAsync( It.IsAny(), @@ -94,16 +100,18 @@ public async Task VerifyChatCompletionAgentStreamingAsync() It.IsAny(), It.IsAny())).Returns(returnContent.ToAsyncEnumerable()); - var agent = - new ChatCompletionAgent() + ChatCompletionAgent agent = + new() { Instructions = "test instructions", Kernel = CreateKernel(mockService.Object), Arguments = [], }; - var result = await agent.InvokeStreamingAsync([]).ToArrayAsync(); + // Act + StreamingChatMessageContent[] result = await agent.InvokeStreamingAsync([]).ToArrayAsync(); + // Assert Assert.Equal(2, result.Length); mockService.Verify( @@ -122,15 +130,23 @@ public async Task VerifyChatCompletionAgentStreamingAsync() [Fact] public void VerifyChatCompletionServiceSelection() { + // Arrange Mock mockService = new(); Kernel kernel = CreateKernel(mockService.Object); + // Act (IChatCompletionService service, PromptExecutionSettings? settings) = ChatCompletionAgent.GetChatCompletionService(kernel, null); + // Assert + Assert.Equal(mockService.Object, service); Assert.Null(settings); + // Act (service, settings) = ChatCompletionAgent.GetChatCompletionService(kernel, []); + // Assert + Assert.Equal(mockService.Object, service); Assert.Null(settings); + // Act and Assert Assert.Throws(() => ChatCompletionAgent.GetChatCompletionService(kernel, new KernelArguments(new PromptExecutionSettings() { ServiceId = "anything" }))); } @@ -140,12 +156,14 @@ public void VerifyChatCompletionServiceSelection() [Fact] public void VerifyChatCompletionChannelKeys() { + // Arrange ChatCompletionAgent agent1 = new(); ChatCompletionAgent agent2 = new(); ChatCompletionAgent agent3 = new() { HistoryReducer = new ChatHistoryTruncationReducer(50) }; ChatCompletionAgent agent4 = new() { HistoryReducer = new ChatHistoryTruncationReducer(50) }; ChatCompletionAgent agent5 = new() { HistoryReducer = new ChatHistoryTruncationReducer(100) }; + // Act ans Assert Assert.Equal(agent1.GetChannelKeys(), agent2.GetChannelKeys()); Assert.Equal(agent3.GetChannelKeys(), agent4.GetChannelKeys()); Assert.NotEqual(agent1.GetChannelKeys(), agent3.GetChannelKeys()); diff --git a/dotnet/src/Agents/UnitTests/Core/ChatHistoryChannelTests.cs b/dotnet/src/Agents/UnitTests/Core/ChatHistoryChannelTests.cs index 43aae918ad52..dfa9f59032c1 100644 --- a/dotnet/src/Agents/UnitTests/Core/ChatHistoryChannelTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/ChatHistoryChannelTests.cs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; +using Moq; using Xunit; namespace SemanticKernel.Agents.UnitTests.Core; @@ -22,21 +20,11 @@ public class ChatHistoryChannelTests [Fact] public async Task VerifyAgentWithoutIChatHistoryHandlerAsync() { - TestAgent agent = new(); // Not a IChatHistoryHandler + // Arrange + Mock agent = new(); // Not a IChatHistoryHandler ChatHistoryChannel channel = new(); // Requires IChatHistoryHandler - await Assert.ThrowsAsync(() => channel.InvokeAsync(agent).ToArrayAsync().AsTask()); - } - - private sealed class TestAgent : KernelAgent - { - protected internal override Task CreateChannelAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - protected internal override IEnumerable GetChannelKeys() - { - throw new NotImplementedException(); - } + // Act & Assert + await Assert.ThrowsAsync(() => channel.InvokeAsync(agent.Object).ToArrayAsync().AsTask()); } } diff --git a/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryReducerExtensionsTests.cs b/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryReducerExtensionsTests.cs index a75533474147..d9042305d9fa 100644 --- a/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryReducerExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryReducerExtensionsTests.cs @@ -30,8 +30,10 @@ public class ChatHistoryReducerExtensionsTests [InlineData(100, 0, int.MaxValue, 100)] public void VerifyChatHistoryExtraction(int messageCount, int startIndex, int? endIndex = null, int? expectedCount = null) { + // Arrange ChatHistory history = [.. MockHistoryGenerator.CreateSimpleHistory(messageCount)]; + // Act ChatMessageContent[] extractedHistory = history.Extract(startIndex, endIndex).ToArray(); int finalIndex = endIndex ?? messageCount - 1; @@ -39,6 +41,7 @@ public void VerifyChatHistoryExtraction(int messageCount, int startIndex, int? e expectedCount ??= finalIndex - startIndex + 1; + // Assert Assert.Equal(expectedCount, extractedHistory.Length); if (extractedHistory.Length > 0) @@ -58,16 +61,19 @@ public void VerifyChatHistoryExtraction(int messageCount, int startIndex, int? e [InlineData(100, 0)] public void VerifyGetFinalSummaryIndex(int summaryCount, int regularCount) { + // Arrange ChatHistory summaries = [.. MockHistoryGenerator.CreateSimpleHistory(summaryCount)]; foreach (ChatMessageContent summary in summaries) { summary.Metadata = new Dictionary() { { "summary", true } }; } + // Act ChatHistory history = [.. summaries, .. MockHistoryGenerator.CreateSimpleHistory(regularCount)]; int finalSummaryIndex = history.LocateSummarizationBoundary("summary"); + // Assert Assert.Equal(summaryCount, finalSummaryIndex); } @@ -77,17 +83,22 @@ public void VerifyGetFinalSummaryIndex(int summaryCount, int regularCount) [Fact] public async Task VerifyChatHistoryNotReducedAsync() { + // Arrange ChatHistory history = []; + Mock mockReducer = new(); + mockReducer.Setup(r => r.ReduceAsync(It.IsAny>(), default)).ReturnsAsync((IEnumerable?)null); + // Act bool isReduced = await history.ReduceAsync(null, default); + // Assert Assert.False(isReduced); Assert.Empty(history); - Mock mockReducer = new(); - mockReducer.Setup(r => r.ReduceAsync(It.IsAny>(), default)).ReturnsAsync((IEnumerable?)null); + // Act isReduced = await history.ReduceAsync(mockReducer.Object, default); + // Assert Assert.False(isReduced); Assert.Empty(history); } @@ -98,13 +109,16 @@ public async Task VerifyChatHistoryNotReducedAsync() [Fact] public async Task VerifyChatHistoryReducedAsync() { + // Arrange Mock mockReducer = new(); mockReducer.Setup(r => r.ReduceAsync(It.IsAny>(), default)).ReturnsAsync((IEnumerable?)[]); ChatHistory history = [.. MockHistoryGenerator.CreateSimpleHistory(10)]; + // Act bool isReduced = await history.ReduceAsync(mockReducer.Object, default); + // Assert Assert.True(isReduced); Assert.Empty(history); } @@ -124,11 +138,13 @@ public async Task VerifyChatHistoryReducedAsync() [InlineData(900, 500, int.MaxValue)] public void VerifyLocateSafeReductionIndexNone(int messageCount, int targetCount, int? thresholdCount = null) { - // Shape of history doesn't matter since reduction is not expected + // Arrange: Shape of history doesn't matter since reduction is not expected ChatHistory sourceHistory = [.. MockHistoryGenerator.CreateHistoryWithUserInput(messageCount)]; + // Act int reductionIndex = sourceHistory.LocateSafeReductionIndex(targetCount, thresholdCount); + // Assert Assert.Equal(0, reductionIndex); } @@ -146,11 +162,13 @@ public void VerifyLocateSafeReductionIndexNone(int messageCount, int targetCount [InlineData(1000, 500, 499)] public void VerifyLocateSafeReductionIndexFound(int messageCount, int targetCount, int? thresholdCount = null) { - // Generate history with only assistant messages + // Arrange: Generate history with only assistant messages ChatHistory sourceHistory = [.. MockHistoryGenerator.CreateSimpleHistory(messageCount)]; + // Act int reductionIndex = sourceHistory.LocateSafeReductionIndex(targetCount, thresholdCount); + // Assert Assert.True(reductionIndex > 0); Assert.Equal(targetCount, messageCount - reductionIndex); } @@ -170,17 +188,20 @@ public void VerifyLocateSafeReductionIndexFound(int messageCount, int targetCoun [InlineData(1000, 500, 499)] public void VerifyLocateSafeReductionIndexFoundWithUser(int messageCount, int targetCount, int? thresholdCount = null) { - // Generate history with alternating user and assistant messages + // Arrange: Generate history with alternating user and assistant messages ChatHistory sourceHistory = [.. MockHistoryGenerator.CreateHistoryWithUserInput(messageCount)]; + // Act int reductionIndex = sourceHistory.LocateSafeReductionIndex(targetCount, thresholdCount); + // Assert Assert.True(reductionIndex > 0); - // The reduction length should align with a user message, if threshold is specified + // Act: The reduction length should align with a user message, if threshold is specified bool hasThreshold = thresholdCount > 0; int expectedCount = targetCount + (hasThreshold && sourceHistory[^targetCount].Role != AuthorRole.User ? 1 : 0); + // Assert Assert.Equal(expectedCount, messageCount - reductionIndex); } @@ -201,14 +222,16 @@ public void VerifyLocateSafeReductionIndexFoundWithUser(int messageCount, int ta [InlineData(9)] public void VerifyLocateSafeReductionIndexWithFunctionContent(int targetCount, int? thresholdCount = null) { - // Generate a history with function call on index 5 and 9 and + // Arrange: Generate a history with function call on index 5 and 9 and // function result on index 6 and 10 (total length: 14) ChatHistory sourceHistory = [.. MockHistoryGenerator.CreateHistoryWithFunctionContent()]; ChatHistoryTruncationReducer reducer = new(targetCount, thresholdCount); + // Act int reductionIndex = sourceHistory.LocateSafeReductionIndex(targetCount, thresholdCount); + // Assert Assert.True(reductionIndex > 0); // The reduction length avoid splitting function call and result, regardless of threshold @@ -216,7 +239,7 @@ public void VerifyLocateSafeReductionIndexWithFunctionContent(int targetCount, i if (sourceHistory[sourceHistory.Count - targetCount].Items.Any(i => i is FunctionCallContent)) { - expectedCount += 1; + expectedCount++; } else if (sourceHistory[sourceHistory.Count - targetCount].Items.Any(i => i is FunctionResultContent)) { diff --git a/dotnet/src/Agents/UnitTests/Core/History/ChatHistorySummarizationReducerTests.cs b/dotnet/src/Agents/UnitTests/Core/History/ChatHistorySummarizationReducerTests.cs index 7661cfcdf8cd..53e93d0026c3 100644 --- a/dotnet/src/Agents/UnitTests/Core/History/ChatHistorySummarizationReducerTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/History/ChatHistorySummarizationReducerTests.cs @@ -25,8 +25,10 @@ public class ChatHistorySummarizationReducerTests [InlineData(int.MaxValue, -1)] public void VerifyConstructorArgumentValidation(int targetCount, int? thresholdCount = null) { + // Arrange Mock mockCompletionService = this.CreateMockCompletionService(); + // Act & Assert Assert.Throws(() => new ChatHistorySummarizationReducer(mockCompletionService.Object, targetCount, thresholdCount)); } @@ -36,13 +38,15 @@ public void VerifyConstructorArgumentValidation(int targetCount, int? thresholdC [Fact] public void VerifyInitializationState() { + // Arrange Mock mockCompletionService = this.CreateMockCompletionService(); - ChatHistorySummarizationReducer reducer = new(mockCompletionService.Object, 10); + // Assert Assert.Equal(ChatHistorySummarizationReducer.DefaultSummarizationPrompt, reducer.SummarizationInstructions); Assert.True(reducer.FailOnError); + // Act reducer = new(mockCompletionService.Object, 10) { @@ -50,6 +54,7 @@ public void VerifyInitializationState() SummarizationInstructions = "instructions", }; + // Assert Assert.NotEqual(ChatHistorySummarizationReducer.DefaultSummarizationPrompt, reducer.SummarizationInstructions); Assert.False(reducer.FailOnError); } @@ -60,6 +65,7 @@ public void VerifyInitializationState() [Fact] public void VerifyEquality() { + // Arrange Mock mockCompletionService = this.CreateMockCompletionService(); ChatHistorySummarizationReducer reducer1 = new(mockCompletionService.Object, 3, 3); @@ -71,6 +77,7 @@ public void VerifyEquality() ChatHistorySummarizationReducer reducer7 = new(mockCompletionService.Object, 3); ChatHistorySummarizationReducer reducer8 = new(mockCompletionService.Object, 3); + // Assert Assert.True(reducer1.Equals(reducer1)); Assert.True(reducer1.Equals(reducer2)); Assert.True(reducer7.Equals(reducer8)); @@ -91,15 +98,18 @@ public void VerifyEquality() [Fact] public void VerifyHashCode() { + // Arrange HashSet reducers = []; Mock mockCompletionService = this.CreateMockCompletionService(); + // Act int hashCode1 = GenerateHashCode(3, 4); int hashCode2 = GenerateHashCode(33, 44); int hashCode3 = GenerateHashCode(3000, 4000); int hashCode4 = GenerateHashCode(3000, 4000); + // Assert Assert.NotEqual(hashCode1, hashCode2); Assert.NotEqual(hashCode2, hashCode3); Assert.Equal(hashCode3, hashCode4); @@ -121,12 +131,15 @@ int GenerateHashCode(int targetCount, int thresholdCount) [Fact] public async Task VerifyChatHistoryReductionSilentFailureAsync() { + // Arrange Mock mockCompletionService = this.CreateMockCompletionService(throwException: true); IReadOnlyList sourceHistory = MockHistoryGenerator.CreateSimpleHistory(20).ToArray(); - ChatHistorySummarizationReducer reducer = new(mockCompletionService.Object, 10) { FailOnError = false }; + + // Act IEnumerable? reducedHistory = await reducer.ReduceAsync(sourceHistory); + // Assert Assert.Null(reducedHistory); } @@ -136,10 +149,12 @@ public async Task VerifyChatHistoryReductionSilentFailureAsync() [Fact] public async Task VerifyChatHistoryReductionThrowsOnFailureAsync() { + // Arrange Mock mockCompletionService = this.CreateMockCompletionService(throwException: true); IReadOnlyList sourceHistory = MockHistoryGenerator.CreateSimpleHistory(20).ToArray(); - ChatHistorySummarizationReducer reducer = new(mockCompletionService.Object, 10); + + // Act and Assert await Assert.ThrowsAsync(() => reducer.ReduceAsync(sourceHistory)); } @@ -149,12 +164,15 @@ public async Task VerifyChatHistoryReductionThrowsOnFailureAsync() [Fact] public async Task VerifyChatHistoryNotReducedAsync() { + // Arrange Mock mockCompletionService = this.CreateMockCompletionService(); IReadOnlyList sourceHistory = MockHistoryGenerator.CreateSimpleHistory(20).ToArray(); - ChatHistorySummarizationReducer reducer = new(mockCompletionService.Object, 20); + + // Act IEnumerable? reducedHistory = await reducer.ReduceAsync(sourceHistory); + // Assert Assert.Null(reducedHistory); } @@ -164,12 +182,15 @@ public async Task VerifyChatHistoryNotReducedAsync() [Fact] public async Task VerifyChatHistoryReducedAsync() { + // Arrange Mock mockCompletionService = this.CreateMockCompletionService(); IReadOnlyList sourceHistory = MockHistoryGenerator.CreateSimpleHistory(20).ToArray(); - ChatHistorySummarizationReducer reducer = new(mockCompletionService.Object, 10); + + // Act IEnumerable? reducedHistory = await reducer.ReduceAsync(sourceHistory); + // Assert ChatMessageContent[] messages = VerifyReducedHistory(reducedHistory, 11); VerifySummarization(messages[0]); } @@ -180,19 +201,24 @@ public async Task VerifyChatHistoryReducedAsync() [Fact] public async Task VerifyChatHistoryRereducedAsync() { + // Arrange Mock mockCompletionService = this.CreateMockCompletionService(); IReadOnlyList sourceHistory = MockHistoryGenerator.CreateSimpleHistory(20).ToArray(); - ChatHistorySummarizationReducer reducer = new(mockCompletionService.Object, 10); + + // Act IEnumerable? reducedHistory = await reducer.ReduceAsync(sourceHistory); reducedHistory = await reducer.ReduceAsync([.. reducedHistory!, .. sourceHistory]); + // Assert ChatMessageContent[] messages = VerifyReducedHistory(reducedHistory, 11); VerifySummarization(messages[0]); + // Act reducer = new(mockCompletionService.Object, 10) { UseSingleSummary = false }; reducedHistory = await reducer.ReduceAsync([.. reducedHistory!, .. sourceHistory]); + // Assert messages = VerifyReducedHistory(reducedHistory, 12); VerifySummarization(messages[0]); VerifySummarization(messages[1]); diff --git a/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryTruncationReducerTests.cs b/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryTruncationReducerTests.cs index 27675420264c..9d8b2e721fdf 100644 --- a/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryTruncationReducerTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryTruncationReducerTests.cs @@ -23,6 +23,7 @@ public class ChatHistoryTruncationReducerTests [InlineData(int.MaxValue, -1)] public void VerifyConstructorArgumentValidation(int targetCount, int? thresholdCount = null) { + // Act and Assert Assert.Throws(() => new ChatHistoryTruncationReducer(targetCount, thresholdCount)); } @@ -32,6 +33,7 @@ public void VerifyConstructorArgumentValidation(int targetCount, int? thresholdC [Fact] public void VerifyEquality() { + // Arrange ChatHistoryTruncationReducer reducer1 = new(3, 3); ChatHistoryTruncationReducer reducer2 = new(3, 3); ChatHistoryTruncationReducer reducer3 = new(4, 3); @@ -39,6 +41,7 @@ public void VerifyEquality() ChatHistoryTruncationReducer reducer5 = new(3); ChatHistoryTruncationReducer reducer6 = new(3); + // Assert Assert.True(reducer1.Equals(reducer1)); Assert.True(reducer1.Equals(reducer2)); Assert.True(reducer5.Equals(reducer6)); @@ -56,13 +59,16 @@ public void VerifyEquality() [Fact] public void VerifyHashCode() { + // Arrange HashSet reducers = []; + // Act int hashCode1 = GenerateHashCode(3, 4); int hashCode2 = GenerateHashCode(33, 44); int hashCode3 = GenerateHashCode(3000, 4000); int hashCode4 = GenerateHashCode(3000, 4000); + // Assert Assert.NotEqual(hashCode1, hashCode2); Assert.NotEqual(hashCode2, hashCode3); Assert.Equal(hashCode3, hashCode4); @@ -84,11 +90,14 @@ int GenerateHashCode(int targetCount, int thresholdCount) [Fact] public async Task VerifyChatHistoryNotReducedAsync() { + // Arrange IReadOnlyList sourceHistory = MockHistoryGenerator.CreateSimpleHistory(10).ToArray(); - ChatHistoryTruncationReducer reducer = new(20); + + // Act IEnumerable? reducedHistory = await reducer.ReduceAsync(sourceHistory); + // Assert Assert.Null(reducedHistory); } @@ -98,11 +107,14 @@ public async Task VerifyChatHistoryNotReducedAsync() [Fact] public async Task VerifyChatHistoryReducedAsync() { + // Arrange IReadOnlyList sourceHistory = MockHistoryGenerator.CreateSimpleHistory(20).ToArray(); - ChatHistoryTruncationReducer reducer = new(10); + + // Act IEnumerable? reducedHistory = await reducer.ReduceAsync(sourceHistory); + // Assert VerifyReducedHistory(reducedHistory, 10); } @@ -112,12 +124,15 @@ public async Task VerifyChatHistoryReducedAsync() [Fact] public async Task VerifyChatHistoryRereducedAsync() { + // Arrange IReadOnlyList sourceHistory = MockHistoryGenerator.CreateSimpleHistory(20).ToArray(); - ChatHistoryTruncationReducer reducer = new(10); + + // Act IEnumerable? reducedHistory = await reducer.ReduceAsync(sourceHistory); reducedHistory = await reducer.ReduceAsync([.. reducedHistory!, .. sourceHistory]); + // Assert VerifyReducedHistory(reducedHistory, 10); } diff --git a/dotnet/src/Agents/UnitTests/Extensions/ChatHistoryExtensionsTests.cs b/dotnet/src/Agents/UnitTests/Extensions/ChatHistoryExtensionsTests.cs index 14a938a7b169..d7f370e3734c 100644 --- a/dotnet/src/Agents/UnitTests/Extensions/ChatHistoryExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/Extensions/ChatHistoryExtensionsTests.cs @@ -19,10 +19,12 @@ public class ChatHistoryExtensionsTests [Fact] public void VerifyChatHistoryOrdering() { + // Arrange ChatHistory history = []; history.AddUserMessage("Hi"); history.AddAssistantMessage("Hi"); + // Act and Assert VerifyRole(AuthorRole.User, history.First()); VerifyRole(AuthorRole.Assistant, history.Last()); @@ -36,10 +38,12 @@ public void VerifyChatHistoryOrdering() [Fact] public async Task VerifyChatHistoryOrderingAsync() { + // Arrange ChatHistory history = []; history.AddUserMessage("Hi"); history.AddAssistantMessage("Hi"); + // Act and Assert VerifyRole(AuthorRole.User, history.First()); VerifyRole(AuthorRole.Assistant, history.Last()); diff --git a/dotnet/src/Agents/UnitTests/Internal/BroadcastQueueTests.cs b/dotnet/src/Agents/UnitTests/Internal/BroadcastQueueTests.cs index 452a0566e11f..96ed232fb109 100644 --- a/dotnet/src/Agents/UnitTests/Internal/BroadcastQueueTests.cs +++ b/dotnet/src/Agents/UnitTests/Internal/BroadcastQueueTests.cs @@ -22,8 +22,10 @@ public class BroadcastQueueTests [Fact] public void VerifyBroadcastQueueDefaultConfiguration() { + // Arrange BroadcastQueue queue = new(); + // Assert Assert.True(queue.BlockDuration.TotalSeconds > 0); } @@ -33,7 +35,7 @@ public void VerifyBroadcastQueueDefaultConfiguration() [Fact] public async Task VerifyBroadcastQueueReceiveAsync() { - // Create queue and channel. + // Arrange: Create queue and channel. BroadcastQueue queue = new() { @@ -42,23 +44,31 @@ public async Task VerifyBroadcastQueueReceiveAsync() TestChannel channel = new(); ChannelReference reference = new(channel, "test"); - // Verify initial state + // Act: Verify initial state await VerifyReceivingStateAsync(receiveCount: 0, queue, channel, "test"); + + // Assert Assert.Empty(channel.ReceivedMessages); - // Verify empty invocation with no channels. + // Act: Verify empty invocation with no channels. queue.Enqueue([], []); await VerifyReceivingStateAsync(receiveCount: 0, queue, channel, "test"); + + // Assert Assert.Empty(channel.ReceivedMessages); - // Verify empty invocation of channel. + // Act: Verify empty invocation of channel. queue.Enqueue([reference], []); await VerifyReceivingStateAsync(receiveCount: 1, queue, channel, "test"); + + // Assert Assert.Empty(channel.ReceivedMessages); - // Verify expected invocation of channel. + // Act: Verify expected invocation of channel. queue.Enqueue([reference], [new ChatMessageContent(AuthorRole.User, "hi")]); await VerifyReceivingStateAsync(receiveCount: 2, queue, channel, "test"); + + // Assert Assert.NotEmpty(channel.ReceivedMessages); } @@ -68,7 +78,7 @@ public async Task VerifyBroadcastQueueReceiveAsync() [Fact] public async Task VerifyBroadcastQueueFailureAsync() { - // Create queue and channel. + // Arrange: Create queue and channel. BroadcastQueue queue = new() { @@ -77,9 +87,10 @@ public async Task VerifyBroadcastQueueFailureAsync() BadChannel channel = new(); ChannelReference reference = new(channel, "test"); - // Verify expected invocation of channel. + // Act: Verify expected invocation of channel. queue.Enqueue([reference], [new ChatMessageContent(AuthorRole.User, "hi")]); + // Assert await Assert.ThrowsAsync(() => queue.EnsureSynchronizedAsync(reference)); await Assert.ThrowsAsync(() => queue.EnsureSynchronizedAsync(reference)); await Assert.ThrowsAsync(() => queue.EnsureSynchronizedAsync(reference)); @@ -91,7 +102,7 @@ public async Task VerifyBroadcastQueueFailureAsync() [Fact] public async Task VerifyBroadcastQueueConcurrencyAsync() { - // Create queue and channel. + // Arrange: Create queue and channel. BroadcastQueue queue = new() { @@ -100,7 +111,7 @@ public async Task VerifyBroadcastQueueConcurrencyAsync() TestChannel channel = new(); ChannelReference reference = new(channel, "test"); - // Enqueue multiple channels + // Act: Enqueue multiple channels for (int count = 0; count < 10; ++count) { queue.Enqueue([new(channel, $"test{count}")], [new ChatMessageContent(AuthorRole.User, "hi")]); @@ -112,7 +123,7 @@ public async Task VerifyBroadcastQueueConcurrencyAsync() await queue.EnsureSynchronizedAsync(new ChannelReference(channel, $"test{count}")); } - // Verify result + // Assert Assert.NotEmpty(channel.ReceivedMessages); Assert.Equal(10, channel.ReceivedMessages.Count); } diff --git a/dotnet/src/Agents/UnitTests/Internal/KeyEncoderTests.cs b/dotnet/src/Agents/UnitTests/Internal/KeyEncoderTests.cs index 0a9715f25115..13cc3203d58c 100644 --- a/dotnet/src/Agents/UnitTests/Internal/KeyEncoderTests.cs +++ b/dotnet/src/Agents/UnitTests/Internal/KeyEncoderTests.cs @@ -17,21 +17,24 @@ public class KeyEncoderTests [Fact] public void VerifyKeyEncoderUniqueness() { + // Act this.VerifyHashEquivalancy([]); this.VerifyHashEquivalancy(nameof(KeyEncoderTests)); this.VerifyHashEquivalancy(nameof(KeyEncoderTests), "http://localhost", "zoo"); - // Verify "well-known" value + // Assert: Verify "well-known" value string localHash = KeyEncoder.GenerateHash([typeof(ChatHistoryChannel).FullName!]); Assert.Equal("Vdx37EnWT9BS+kkCkEgFCg9uHvHNw1+hXMA4sgNMKs4=", localHash); } private void VerifyHashEquivalancy(params string[] keys) { + // Act string hash1 = KeyEncoder.GenerateHash(keys); string hash2 = KeyEncoder.GenerateHash(keys); string hash3 = KeyEncoder.GenerateHash(keys.Concat(["another"])); + // Assert Assert.Equal(hash1, hash2); Assert.NotEqual(hash1, hash3); } diff --git a/dotnet/src/Agents/UnitTests/MockAgent.cs b/dotnet/src/Agents/UnitTests/MockAgent.cs index 51fbf36c6ab4..2535446dae7b 100644 --- a/dotnet/src/Agents/UnitTests/MockAgent.cs +++ b/dotnet/src/Agents/UnitTests/MockAgent.cs @@ -15,7 +15,7 @@ namespace SemanticKernel.Agents.UnitTests; /// /// Mock definition of with a contract. /// -internal sealed class MockAgent : KernelAgent, IChatHistoryHandler +internal class MockAgent : KernelAgent, IChatHistoryHandler { public int InvokeCount { get; private set; } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs index 3c2945ad0fb9..6288c6a5aed8 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Azure/AddHeaderRequestPolicyTests.cs @@ -18,14 +18,17 @@ public class AddHeaderRequestPolicyTests [Fact] public void VerifyAddHeaderRequestPolicyExecution() { + // Arrange using HttpClientTransport clientTransport = new(); HttpPipeline pipeline = new(clientTransport); HttpMessage message = pipeline.CreateMessage(); - AddHeaderRequestPolicy policy = new(headerName: "testname", headerValue: "testvalue"); + + // Act policy.OnSendingRequest(message); + // Assert Assert.Single(message.Request.Headers); HttpHeader header = message.Request.Headers.Single(); Assert.Equal("testname", header.Name); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AuthorRoleExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AuthorRoleExtensionsTests.cs index 997596796be1..97dbf32903d6 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AuthorRoleExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AuthorRoleExtensionsTests.cs @@ -29,7 +29,10 @@ public void VerifyToMessageRole() private void VerifyRoleConversion(AuthorRole inputRole, MessageRole expectedRole) { + // Arrange MessageRole convertedRole = inputRole.ToMessageRole(); + + // Assert Assert.Equal(expectedRole, convertedRole); } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelExtensionsTests.cs index 3f982f3a7b47..70c27ccb2152 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelExtensionsTests.cs @@ -17,11 +17,15 @@ public class KernelExtensionsTests [Fact] public void VerifyGetKernelFunctionLookup() { + // Arrange Kernel kernel = new(); KernelPlugin plugin = KernelPluginFactory.CreateFromType(); kernel.Plugins.Add(plugin); + // Act KernelFunction function = kernel.GetKernelFunction($"{nameof(TestPlugin)}-{nameof(TestPlugin.TestFunction)}", '-'); + + // Assert Assert.NotNull(function); Assert.Equal(nameof(TestPlugin.TestFunction), function.Name); } @@ -32,10 +36,12 @@ public void VerifyGetKernelFunctionLookup() [Fact] public void VerifyGetKernelFunctionInvalid() { + // Arrange Kernel kernel = new(); KernelPlugin plugin = KernelPluginFactory.CreateFromType(); kernel.Plugins.Add(plugin); + // Act and Assert Assert.Throws(() => kernel.GetKernelFunction("a", '-')); Assert.Throws(() => kernel.GetKernelFunction("a-b", ':')); Assert.Throws(() => kernel.GetKernelFunction("a-b-c", '-')); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs index 6d690e909457..acf195840366 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelFunctionExtensionsTests.cs @@ -19,17 +19,27 @@ public class KernelFunctionExtensionsTests [Fact] public void VerifyKernelFunctionToFunctionTool() { + // Arrange KernelPlugin plugin = KernelPluginFactory.CreateFromType(); + + // Assert Assert.Equal(2, plugin.FunctionCount); + // Arrange KernelFunction f1 = plugin[nameof(TestPlugin.TestFunction1)]; KernelFunction f2 = plugin[nameof(TestPlugin.TestFunction2)]; + // Act FunctionToolDefinition definition1 = f1.ToToolDefinition("testplugin"); + + // Assert Assert.StartsWith($"testplugin-{nameof(TestPlugin.TestFunction1)}", definition1.FunctionName, StringComparison.Ordinal); Assert.Equal("test description", definition1.Description); + // Act FunctionToolDefinition definition2 = f2.ToToolDefinition("testplugin"); + + // Assert Assert.StartsWith($"testplugin-{nameof(TestPlugin.TestFunction2)}", definition2.FunctionName, StringComparison.Ordinal); Assert.Equal("test description", definition2.Description); } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs index 5d0e01ba9926..50dec2cb95ae 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs @@ -24,7 +24,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsDefault() // Arrange (Setup message with null metadata) ChatMessageContent message = new(AuthorRole.User, "test"); - // Act + // Act: Create options MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); // Assert @@ -38,17 +38,17 @@ public void VerifyAssistantMessageAdapterCreateOptionsDefault() [Fact] public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataEmpty() { - // Setup message with empty metadata + // Arrange Setup message with empty metadata ChatMessageContent message = new(AuthorRole.User, "test") { Metadata = new Dictionary() }; - // Create options + // Act: Create options MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); - // Validate + // Assert Assert.NotNull(options); Assert.Empty(options.Metadata); } @@ -59,7 +59,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataEmpty() [Fact] public void VerifyAssistantMessageAdapterCreateOptionsWithMetadata() { - // Setup message with metadata + // Arrange: Setup message with metadata ChatMessageContent message = new(AuthorRole.User, "test") { @@ -71,10 +71,10 @@ public void VerifyAssistantMessageAdapterCreateOptionsWithMetadata() } }; - // Create options + // Act: Create options MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); - // Validate + // Assert Assert.NotNull(options); Assert.NotEmpty(options.Metadata); Assert.Equal(2, options.Metadata.Count); @@ -88,7 +88,7 @@ public void VerifyAssistantMessageAdapterCreateOptionsWithMetadata() [Fact] public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataNull() { - // Setup message with null metadata value + // Arrange: Setup message with null metadata value ChatMessageContent message = new(AuthorRole.User, "test") { @@ -100,10 +100,10 @@ public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataNull() } }; - // Create options + // Act: Create options MessageCreationOptions options = AssistantMessageFactory.CreateOptions(message); - // Validate + // Assert Assert.NotNull(options); Assert.NotEmpty(options.Metadata); Assert.Equal(2, options.Metadata.Count); @@ -117,8 +117,13 @@ public void VerifyAssistantMessageAdapterCreateOptionsWithMetadataNull() [Fact] public void VerifyAssistantMessageAdapterGetMessageContentsWithText() { + // Arrange ChatMessageContent message = new(AuthorRole.User, items: [new TextContent("test")]); + + // Act MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); + + // Assert Assert.NotNull(contents); Assert.Single(contents); Assert.NotNull(contents.Single().Text); @@ -130,8 +135,13 @@ public void VerifyAssistantMessageAdapterGetMessageContentsWithText() [Fact] public void VerifyAssistantMessageAdapterGetMessageWithImageUrl() { + // Arrange ChatMessageContent message = new(AuthorRole.User, items: [new ImageContent(new Uri("https://localhost/myimage.png"))]); + + // Act MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); + + // Assert Assert.NotNull(contents); Assert.Single(contents); Assert.NotNull(contents.Single().ImageUrl); @@ -143,8 +153,13 @@ public void VerifyAssistantMessageAdapterGetMessageWithImageUrl() [Fact(Skip = "API bug with data Uri construction")] public void VerifyAssistantMessageAdapterGetMessageWithImageData() { + // Arrange ChatMessageContent message = new(AuthorRole.User, items: [new ImageContent(new byte[] { 1, 2, 3 }, "image/png")]); + + // Act MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); + + // Assert Assert.NotNull(contents); Assert.Single(contents); Assert.NotNull(contents.Single().ImageUrl); @@ -156,8 +171,13 @@ public void VerifyAssistantMessageAdapterGetMessageWithImageData() [Fact] public void VerifyAssistantMessageAdapterGetMessageWithImageFile() { + // Arrange ChatMessageContent message = new(AuthorRole.User, items: [new FileReferenceContent("file-id")]); + + // Act MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); + + // Assert Assert.NotNull(contents); Assert.Single(contents); Assert.NotNull(contents.Single().ImageFileId); @@ -169,6 +189,7 @@ public void VerifyAssistantMessageAdapterGetMessageWithImageFile() [Fact] public void VerifyAssistantMessageAdapterGetMessageWithAll() { + // Arrange ChatMessageContent message = new( AuthorRole.User, @@ -178,7 +199,11 @@ public void VerifyAssistantMessageAdapterGetMessageWithAll() new ImageContent(new Uri("https://localhost/myimage.png")), new FileReferenceContent("file-id") ]); + + // Act MessageContent[] contents = AssistantMessageFactory.GetMessageContents(message).ToArray(); + + // Assert Assert.NotNull(contents); Assert.Equal(3, contents.Length); } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs index 704cf0252852..d6bcf91b8a94 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs @@ -18,13 +18,17 @@ public class AssistantRunOptionsFactoryTests [Fact] public void AssistantRunOptionsFactoryExecutionOptionsNullTest() { + // Arrange OpenAIAssistantDefinition definition = new("gpt-anything") { Temperature = 0.5F, }; + // Act RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(definition, null); + + // Assert Assert.NotNull(options); Assert.Null(options.Temperature); Assert.Null(options.NucleusSamplingFactor); @@ -37,6 +41,7 @@ public void AssistantRunOptionsFactoryExecutionOptionsNullTest() [Fact] public void AssistantRunOptionsFactoryExecutionOptionsEquivalentTest() { + // Arrange OpenAIAssistantDefinition definition = new("gpt-anything") { @@ -49,7 +54,10 @@ public void AssistantRunOptionsFactoryExecutionOptionsEquivalentTest() Temperature = 0.5F, }; + // Act RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(definition, invocationOptions); + + // Assert Assert.NotNull(options); Assert.Null(options.Temperature); Assert.Null(options.NucleusSamplingFactor); @@ -61,6 +69,7 @@ public void AssistantRunOptionsFactoryExecutionOptionsEquivalentTest() [Fact] public void AssistantRunOptionsFactoryExecutionOptionsOverrideTest() { + // Arrange OpenAIAssistantDefinition definition = new("gpt-anything") { @@ -80,7 +89,10 @@ public void AssistantRunOptionsFactoryExecutionOptionsOverrideTest() EnableJsonResponse = true, }; + // Act RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(definition, invocationOptions); + + // Assert Assert.NotNull(options); Assert.Equal(0.9F, options.Temperature); Assert.Equal(8, options.TruncationStrategy.LastMessages); @@ -94,6 +106,7 @@ public void AssistantRunOptionsFactoryExecutionOptionsOverrideTest() [Fact] public void AssistantRunOptionsFactoryExecutionOptionsMetadataTest() { + // Arrange OpenAIAssistantDefinition definition = new("gpt-anything") { @@ -115,8 +128,10 @@ public void AssistantRunOptionsFactoryExecutionOptionsMetadataTest() }, }; + // Act RunCreationOptions options = AssistantRunOptionsFactory.GenerateOptions(definition, invocationOptions); + // Assert Assert.Equal(2, options.Metadata.Count); Assert.Equal("value", options.Metadata["key1"]); Assert.Equal(string.Empty, options.Metadata["key2"]); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs index bee75be9d80c..ef67c48f1473 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantAgentTests.cs @@ -32,8 +32,10 @@ public sealed class OpenAIAssistantAgentTests : IDisposable [Fact] public async Task VerifyOpenAIAssistantAgentCreationEmptyAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel"); + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -44,6 +46,7 @@ public async Task VerifyOpenAIAssistantAgentCreationEmptyAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { @@ -52,6 +55,7 @@ public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() Instructions = "testinstructions", }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -62,12 +66,14 @@ public async Task VerifyOpenAIAssistantAgentCreationPropertiesAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { EnableCodeInterpreter = true, }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -78,6 +84,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterFilesAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { @@ -85,6 +92,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterFilesAsyn CodeInterpreterFileIds = ["file1", "file2"], }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -95,12 +103,14 @@ public async Task VerifyOpenAIAssistantAgentCreationWithCodeInterpreterFilesAsyn [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithFileSearchAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { EnableFileSearch = true, }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -111,6 +121,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithFileSearchAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithVectorStoreAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { @@ -118,6 +129,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithVectorStoreAsync() VectorStoreId = "#vs1", }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -128,6 +140,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithVectorStoreAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithMetadataAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { @@ -138,6 +151,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithMetadataAsync() }, }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -148,12 +162,14 @@ public async Task VerifyOpenAIAssistantAgentCreationWithMetadataAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithJsonResponseAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { EnableJsonResponse = true, }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -164,12 +180,14 @@ public async Task VerifyOpenAIAssistantAgentCreationWithJsonResponseAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithTemperatureAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { Temperature = 2.0F, }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -180,12 +198,14 @@ public async Task VerifyOpenAIAssistantAgentCreationWithTemperatureAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithTopPAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { TopP = 2.0F, }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -196,12 +216,14 @@ public async Task VerifyOpenAIAssistantAgentCreationWithTopPAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { ExecutionOptions = new OpenAIAssistantExecutionOptions(), }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -212,6 +234,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAsy [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithExecutionOptionsAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { @@ -223,6 +246,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithExecutionOptionsAsync() } }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -233,6 +257,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithExecutionOptionsAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAndMetadataAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { @@ -244,6 +269,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAnd }, }; + // Act and Assert await this.VerifyAgentCreationAsync(definition); } @@ -253,6 +279,7 @@ public async Task VerifyOpenAIAssistantAgentCreationWithEmptyExecutionOptionsAnd [Fact] public async Task VerifyOpenAIAssistantAgentRetrievalAsync() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel"); this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateAgentPayload(definition)); @@ -263,6 +290,7 @@ await OpenAIAssistantAgent.RetrieveAsync( this.CreateTestConfiguration(), "#id"); + // Act and Assert ValidateAgentDefinition(agent, definition); } @@ -272,17 +300,23 @@ await OpenAIAssistantAgent.RetrieveAsync( [Fact] public async Task VerifyOpenAIAssistantAgentDeleteAsync() { + // Arrange OpenAIAssistantAgent agent = await this.CreateAgentAsync(); + // Assert Assert.False(agent.IsDeleted); + // Arrange this.SetupResponse(HttpStatusCode.OK, ResponseContent.DeleteAgent); + // Act await agent.DeleteAsync(); + // Assert Assert.True(agent.IsDeleted); + // Act await agent.DeleteAsync(); // Doesn't throw + // Assert Assert.True(agent.IsDeleted); - await Assert.ThrowsAsync(() => agent.AddChatMessageAsync("threadid", new(AuthorRole.User, "test"))); await Assert.ThrowsAsync(() => agent.InvokeAsync("threadid").ToArrayAsync().AsTask()); } @@ -293,16 +327,22 @@ public async Task VerifyOpenAIAssistantAgentDeleteAsync() [Fact] public async Task VerifyOpenAIAssistantAgentCreateThreadAsync() { + // Arrange OpenAIAssistantAgent agent = await this.CreateAgentAsync(); this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateThread); + // Act string threadId = await agent.CreateThreadAsync(); + // Assert Assert.NotNull(threadId); + // Arrange this.SetupResponse(HttpStatusCode.OK, ResponseContent.CreateThread); + // Act threadId = await agent.CreateThreadAsync(new()); + // Assert Assert.NotNull(threadId); } @@ -312,6 +352,7 @@ public async Task VerifyOpenAIAssistantAgentCreateThreadAsync() [Fact] public async Task VerifyOpenAIAssistantAgentChatTextMessageAsync() { + // Arrange OpenAIAssistantAgent agent = await this.CreateAgentAsync(); this.SetupResponses( @@ -323,7 +364,11 @@ public async Task VerifyOpenAIAssistantAgentChatTextMessageAsync() ResponseContent.GetTextMessage); AgentGroupChat chat = new(); + + // Act ChatMessageContent[] messages = await chat.InvokeAsync(agent).ToArrayAsync(); + + // Assert Assert.Single(messages); Assert.Single(messages[0].Items); Assert.IsType(messages[0].Items[0]); @@ -335,6 +380,7 @@ public async Task VerifyOpenAIAssistantAgentChatTextMessageAsync() [Fact] public async Task VerifyOpenAIAssistantAgentChatTextMessageWithAnnotationAsync() { + // Arrange OpenAIAssistantAgent agent = await this.CreateAgentAsync(); this.SetupResponses( @@ -346,7 +392,11 @@ public async Task VerifyOpenAIAssistantAgentChatTextMessageWithAnnotationAsync() ResponseContent.GetTextMessageWithAnnotation); AgentGroupChat chat = new(); + + // Act ChatMessageContent[] messages = await chat.InvokeAsync(agent).ToArrayAsync(); + + // Assert Assert.Single(messages); Assert.Equal(2, messages[0].Items.Count); Assert.NotNull(messages[0].Items.SingleOrDefault(c => c is TextContent)); @@ -359,6 +409,7 @@ public async Task VerifyOpenAIAssistantAgentChatTextMessageWithAnnotationAsync() [Fact] public async Task VerifyOpenAIAssistantAgentChatImageMessageAsync() { + // Arrange OpenAIAssistantAgent agent = await this.CreateAgentAsync(); this.SetupResponses( @@ -370,7 +421,11 @@ public async Task VerifyOpenAIAssistantAgentChatImageMessageAsync() ResponseContent.GetImageMessage); AgentGroupChat chat = new(); + + // Act ChatMessageContent[] messages = await chat.InvokeAsync(agent).ToArrayAsync(); + + // Assert Assert.Single(messages); Assert.Single(messages[0].Items); Assert.IsType(messages[0].Items[0]); @@ -382,7 +437,7 @@ public async Task VerifyOpenAIAssistantAgentChatImageMessageAsync() [Fact] public async Task VerifyOpenAIAssistantAgentGetMessagesAsync() { - // Create agent + // Arrange: Create agent OpenAIAssistantAgent agent = await this.CreateAgentAsync(); // Initialize agent channel @@ -395,18 +450,22 @@ public async Task VerifyOpenAIAssistantAgentGetMessagesAsync() ResponseContent.GetTextMessage); AgentGroupChat chat = new(); + + // Act ChatMessageContent[] messages = await chat.InvokeAsync(agent).ToArrayAsync(); + // Assert Assert.Single(messages); - // Setup messages + // Arrange: Setup messages this.SetupResponses( HttpStatusCode.OK, ResponseContent.ListMessagesPageMore, ResponseContent.ListMessagesPageMore, ResponseContent.ListMessagesPageFinal); - // Get messages and verify + // Act: Get messages messages = await chat.GetChatMessagesAsync(agent).ToArrayAsync(); + // Assert Assert.Equal(5, messages.Length); } @@ -416,7 +475,7 @@ public async Task VerifyOpenAIAssistantAgentGetMessagesAsync() [Fact] public async Task VerifyOpenAIAssistantAgentAddMessagesAsync() { - // Create agent + // Arrange: Create agent OpenAIAssistantAgent agent = await this.CreateAgentAsync(); // Initialize agent channel @@ -428,12 +487,18 @@ public async Task VerifyOpenAIAssistantAgentAddMessagesAsync() ResponseContent.MessageSteps, ResponseContent.GetTextMessage); AgentGroupChat chat = new(); + + // Act ChatMessageContent[] messages = await chat.InvokeAsync(agent).ToArrayAsync(); + // Assert Assert.Single(messages); + // Arrange chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, "hi")); + // Act messages = await chat.GetChatMessagesAsync().ToArrayAsync(); + // Assert Assert.Equal(2, messages.Length); } @@ -443,6 +508,7 @@ public async Task VerifyOpenAIAssistantAgentAddMessagesAsync() [Fact] public async Task VerifyOpenAIAssistantAgentListDefinitionAsync() { + // Arrange OpenAIAssistantAgent agent = await this.CreateAgentAsync(); this.SetupResponses( @@ -451,19 +517,24 @@ public async Task VerifyOpenAIAssistantAgentListDefinitionAsync() ResponseContent.ListAgentsPageMore, ResponseContent.ListAgentsPageFinal); + // Act var messages = await OpenAIAssistantAgent.ListDefinitionsAsync( this.CreateTestConfiguration()).ToArrayAsync(); + // Assert Assert.Equal(7, messages.Length); + // Arrange this.SetupResponses( HttpStatusCode.OK, ResponseContent.ListAgentsPageMore, ResponseContent.ListAgentsPageFinal); + // Act messages = await OpenAIAssistantAgent.ListDefinitionsAsync( this.CreateTestConfiguration()).ToArrayAsync(); + // Assert Assert.Equal(4, messages.Length); } @@ -473,6 +544,7 @@ await OpenAIAssistantAgent.ListDefinitionsAsync( [Fact] public async Task VerifyOpenAIAssistantAgentWithFunctionCallAsync() { + // Arrange OpenAIAssistantAgent agent = await this.CreateAgentAsync(); KernelPlugin plugin = KernelPluginFactory.CreateFromType(); @@ -490,7 +562,11 @@ public async Task VerifyOpenAIAssistantAgentWithFunctionCallAsync() ResponseContent.GetTextMessage); AgentGroupChat chat = new(); + + // Act ChatMessageContent[] messages = await chat.InvokeAsync(agent).ToArrayAsync(); + + // Assert Assert.Single(messages); Assert.Single(messages[0].Items); Assert.IsType(messages[0].Items[0]); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs index a13261b58fdf..f8547f375f13 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantDefinitionTests.cs @@ -17,8 +17,10 @@ public class OpenAIAssistantDefinitionTests [Fact] public void VerifyOpenAIAssistantDefinitionInitialState() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel"); + // Assert Assert.Equal(string.Empty, definition.Id); Assert.Equal("testmodel", definition.ModelId); Assert.Null(definition.Name); @@ -34,6 +36,7 @@ public void VerifyOpenAIAssistantDefinitionInitialState() Assert.False(definition.EnableCodeInterpreter); Assert.False(definition.EnableJsonResponse); + // Act and Assert ValidateSerialization(definition); } @@ -43,6 +46,7 @@ public void VerifyOpenAIAssistantDefinitionInitialState() [Fact] public void VerifyOpenAIAssistantDefinitionAssignment() { + // Arrange OpenAIAssistantDefinition definition = new("testmodel") { @@ -68,6 +72,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() EnableJsonResponse = true, }; + // Assert Assert.Equal("testid", definition.Id); Assert.Equal("testname", definition.Name); Assert.Equal("testmodel", definition.ModelId); @@ -87,6 +92,7 @@ public void VerifyOpenAIAssistantDefinitionAssignment() Assert.True(definition.EnableCodeInterpreter); Assert.True(definition.EnableJsonResponse); + // Act and Assert ValidateSerialization(definition); } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs index 692dee85f1aa..99cbe012f183 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs @@ -17,8 +17,10 @@ public class OpenAIAssistantInvocationOptionsTests [Fact] public void OpenAIAssistantInvocationOptionsInitialState() { + // Arrange OpenAIAssistantInvocationOptions options = new(); + // Assert Assert.Null(options.ModelName); Assert.Null(options.Metadata); Assert.Null(options.Temperature); @@ -31,6 +33,7 @@ public void OpenAIAssistantInvocationOptionsInitialState() Assert.False(options.EnableCodeInterpreter); Assert.False(options.EnableFileSearch); + // Act and Assert ValidateSerialization(options); } @@ -40,6 +43,7 @@ public void OpenAIAssistantInvocationOptionsInitialState() [Fact] public void OpenAIAssistantInvocationOptionsAssignment() { + // Arrange OpenAIAssistantInvocationOptions options = new() { @@ -56,6 +60,7 @@ public void OpenAIAssistantInvocationOptionsAssignment() EnableFileSearch = true, }; + // Assert Assert.Equal("testmodel", options.ModelName); Assert.Equal(2, options.Temperature); Assert.Equal(0, options.TopP); @@ -68,15 +73,18 @@ public void OpenAIAssistantInvocationOptionsAssignment() Assert.True(options.EnableJsonResponse); Assert.True(options.EnableFileSearch); + // Act and Assert ValidateSerialization(options); } private static void ValidateSerialization(OpenAIAssistantInvocationOptions source) { + // Act string json = JsonSerializer.Serialize(source); OpenAIAssistantInvocationOptions? target = JsonSerializer.Deserialize(json); + // Assert Assert.NotNull(target); Assert.Equal(source.ModelName, target.ModelName); Assert.Equal(source.Temperature, target.Temperature); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientProviderTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientProviderTests.cs index dfb033f31d3c..7799eb26c305 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientProviderTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientProviderTests.cs @@ -19,7 +19,10 @@ public class OpenAIClientProviderTests [Fact] public void VerifyOpenAIClientFactoryTargetAzureByKey() { + // Arrange OpenAIClientProvider provider = OpenAIClientProvider.ForAzureOpenAI("key", new Uri("https://localhost")); + + // Assert Assert.NotNull(provider.Client); } @@ -29,8 +32,11 @@ public void VerifyOpenAIClientFactoryTargetAzureByKey() [Fact] public void VerifyOpenAIClientFactoryTargetAzureByCredential() { + // Arrange Mock mockCredential = new(); OpenAIClientProvider provider = OpenAIClientProvider.ForAzureOpenAI(mockCredential.Object, new Uri("https://localhost")); + + // Assert Assert.NotNull(provider.Client); } @@ -42,7 +48,10 @@ public void VerifyOpenAIClientFactoryTargetAzureByCredential() [InlineData("http://myproxy:9819")] public void VerifyOpenAIClientFactoryTargetOpenAINoKey(string? endpoint) { + // Arrange OpenAIClientProvider provider = OpenAIClientProvider.ForOpenAI(endpoint != null ? new Uri(endpoint) : null); + + // Assert Assert.NotNull(provider.Client); } @@ -54,7 +63,10 @@ public void VerifyOpenAIClientFactoryTargetOpenAINoKey(string? endpoint) [InlineData("key", "http://myproxy:9819")] public void VerifyOpenAIClientFactoryTargetOpenAIByKey(string key, string? endpoint) { + // Arrange OpenAIClientProvider provider = OpenAIClientProvider.ForOpenAI(key, endpoint != null ? new Uri(endpoint) : null); + + // Assert Assert.NotNull(provider.Client); } @@ -64,8 +76,11 @@ public void VerifyOpenAIClientFactoryTargetOpenAIByKey(string key, string? endpo [Fact] public void VerifyOpenAIClientFactoryWithHttpClient() { + // Arrange using HttpClient httpClient = new() { BaseAddress = new Uri("http://myproxy:9819") }; OpenAIClientProvider provider = OpenAIClientProvider.ForOpenAI(httpClient: httpClient); + + // Assert Assert.NotNull(provider.Client); } } diff --git a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs index 496f429f0793..1689bec1f828 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs @@ -19,13 +19,16 @@ public class OpenAIThreadCreationOptionsTests [Fact] public void OpenAIThreadCreationOptionsInitialState() { + // Arrange OpenAIThreadCreationOptions options = new(); + // Assert Assert.Null(options.Messages); Assert.Null(options.Metadata); Assert.Null(options.VectorStoreId); Assert.Null(options.CodeInterpreterFileIds); + // Act and Assert ValidateSerialization(options); } @@ -35,6 +38,7 @@ public void OpenAIThreadCreationOptionsInitialState() [Fact] public void OpenAIThreadCreationOptionsAssignment() { + // Arrange OpenAIThreadCreationOptions options = new() { @@ -44,20 +48,24 @@ public void OpenAIThreadCreationOptionsAssignment() CodeInterpreterFileIds = ["file1"], }; + // Assert Assert.Single(options.Messages); Assert.Single(options.Metadata); Assert.Equal("#vs", options.VectorStoreId); Assert.Single(options.CodeInterpreterFileIds); + // Act and Assert ValidateSerialization(options); } private static void ValidateSerialization(OpenAIThreadCreationOptions source) { + // Act string json = JsonSerializer.Serialize(source); OpenAIThreadCreationOptions? target = JsonSerializer.Deserialize(json); + // Assert Assert.NotNull(target); Assert.Equal(source.VectorStoreId, target.VectorStoreId); AssertCollection.Equal(source.CodeInterpreterFileIds, target.CodeInterpreterFileIds); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs index 9ec3567c0987..e75a962dfc5e 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs @@ -16,8 +16,10 @@ public class RunPollingOptionsTests [Fact] public void RunPollingOptionsInitialStateTest() { + // Arrange RunPollingOptions options = new(); + // Assert Assert.Equal(RunPollingOptions.DefaultPollingInterval, options.RunPollingInterval); Assert.Equal(RunPollingOptions.DefaultPollingBackoff, options.RunPollingBackoff); Assert.Equal(RunPollingOptions.DefaultMessageSynchronizationDelay, options.MessageSynchronizationDelay); @@ -30,6 +32,7 @@ public void RunPollingOptionsInitialStateTest() [Fact] public void RunPollingOptionsAssignmentTest() { + // Arrange RunPollingOptions options = new() { @@ -39,6 +42,7 @@ public void RunPollingOptionsAssignmentTest() MessageSynchronizationDelay = TimeSpan.FromSeconds(5), }; + // Assert Assert.Equal(3, options.RunPollingInterval.TotalSeconds); Assert.Equal(4, options.RunPollingBackoff.TotalSeconds); Assert.Equal(5, options.MessageSynchronizationDelay.TotalSeconds); @@ -51,6 +55,7 @@ public void RunPollingOptionsAssignmentTest() [Fact] public void RunPollingOptionsGetIntervalTest() { + // Arrange RunPollingOptions options = new() { @@ -59,6 +64,7 @@ public void RunPollingOptionsGetIntervalTest() RunPollingBackoffThreshold = 8, }; + // Assert Assert.Equal(options.RunPollingInterval, options.GetPollingInterval(8)); Assert.Equal(options.RunPollingBackoff, options.GetPollingInterval(9)); } From 5463d1b654ff41533c4ce7ed38ee0e93b649ebdc Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 9 Aug 2024 12:29:48 -0700 Subject: [PATCH 120/121] Summary reducer optimization --- .../src/Agents/Core/History/ChatHistorySummarizationReducer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs b/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs index 4f909e7e146d..8c2f022830d1 100644 --- a/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs +++ b/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs @@ -80,7 +80,7 @@ Provide a concise and complete summarizion of the entire dialog that does not ex IEnumerable summarizedHistory = history.Extract( this.UseSingleSummary ? 0 : insertionPoint, - truncationIndex, + truncationIndex - 1, (m) => m.Items.Any(i => i is FunctionCallContent || i is FunctionResultContent)); try From 88cbd50b9f5fb125037f8e1e02f9d236e171aa44 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 9 Aug 2024 16:02:18 -0700 Subject: [PATCH 121/121] readme --- .../samples/GettingStartedWithAgents/README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/README.md b/dotnet/samples/GettingStartedWithAgents/README.md index 39952506548c..ed0e68802994 100644 --- a/dotnet/samples/GettingStartedWithAgents/README.md +++ b/dotnet/samples/GettingStartedWithAgents/README.md @@ -19,13 +19,17 @@ The getting started with agents examples include: Example|Description ---|--- -[Step1_Agent](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs)|How to create and use an agent. -[Step2_Plugins](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs)|How to associate plug-ins with an agent. -[Step3_Chat](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step3_Chat.cs)|How to create a conversation between agents. -[Step4_KernelFunctionStrategies](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step4_KernelFunctionStrategies.cs)|How to utilize a `KernelFunction` as a _chat strategy_. -[Step5_JsonResult](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step5_JsonResult.cs)|How to have an agent produce JSON. -[Step6_DependencyInjection](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs)|How to define dependency injection patterns for agents. -[Step7_OpenAIAssistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step7_OpenAIAssistant.cs)|How to create an Open AI Assistant agent. +[Step01_Agent](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step01_Agent.cs)|How to create and use an agent. +[Step02_Plugins](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step02_Plugins.cs)|How to associate plug-ins with an agent. +[Step03_Chat](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step03_Chat.cs)|How to create a conversation between agents. +[Step04_KernelFunctionStrategies](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs)|How to utilize a `KernelFunction` as a _chat strategy_. +[Step05_JsonResult](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step05_JsonResult.cs)|How to have an agent produce JSON. +[Step06_DependencyInjection](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step06_DependencyInjection.cs)|How to define dependency injection patterns for agents. +[Step07_Logging](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step07_Logging.cs)|How to enable logging for agents. +[Step08_Assistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs)|How to create an Open AI Assistant agent. +[Step09_Assistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs)|How to provide an image as input to an Open AI Assistant agent. +[Step10_Assistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter_.cs)|How to use the code-interpreter tool for an Open AI Assistant agent. +[Step11_Assistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs)|How to use the file-search tool for an Open AI Assistant agent. ## Legacy Agents