diff --git a/dotnet/samples/GettingStarted/AgentSample.cs b/dotnet/samples/GettingStarted/AgentSample.cs index 686f6bb383..d518607691 100644 --- a/dotnet/samples/GettingStarted/AgentSample.cs +++ b/dotnet/samples/GettingStarted/AgentSample.cs @@ -31,17 +31,17 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output) AzureAIAgentsPersistent } - protected Task GetChatClientAsync(ChatClientProviders provider, ChatClientAgentOptions options, CancellationToken cancellationToken = default) + protected IChatClient GetChatClient(ChatClientProviders provider, ChatClientAgentOptions options) => provider switch { - ChatClientProviders.OpenAIChatCompletion => GetOpenAIChatClientAsync(), - ChatClientProviders.OpenAIAssistant => GetOpenAIAssistantChatClientAsync(options, cancellationToken), - ChatClientProviders.AzureOpenAI => GetAzureOpenAIChatClientAsync(), - ChatClientProviders.AzureAIAgentsPersistent => GetAzureAIAgentPersistentClientAsync(options, cancellationToken), + ChatClientProviders.OpenAIChatCompletion => GetOpenAIChatClient(), + ChatClientProviders.OpenAIAssistant => GetOpenAIAssistantChatClient(options), + ChatClientProviders.AzureOpenAI => GetAzureOpenAIChatClient(), + ChatClientProviders.AzureAIAgentsPersistent => GetAzureAIAgentPersistentClient(options), ChatClientProviders.OpenAIResponses or ChatClientProviders.OpenAIResponses_InMemoryMessageThread or ChatClientProviders.OpenAIResponses_ConversationIdThread - => GetOpenAIResponsesClientAsync(), + => GetOpenAIResponsesClient(), _ => throw new NotSupportedException($"Provider {provider} is not supported.") }; @@ -69,76 +69,88 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output) return provider switch { ChatClientProviders.AzureAIAgentsPersistent => AzureAIAgentsPersistentAgentCleanUpAsync(agent, thread, cancellationToken), - + ChatClientProviders.OpenAIAssistant => OpenAIAssistantCleanUpAgentAsync(agent, thread, cancellationToken), // For other remaining provider sample types, no cleanup is needed as they don't offer a server-side agent/thread clean-up API. _ => Task.CompletedTask }; } + /// + /// Creates a server-side agent identifier based on the specified provider and options. + /// + /// The provider to use for creating the agent. + /// The options to configure the agent. + /// The to monitor for cancellation requests. The default is . + /// The identifier of the created agent, or if the provider does not use server-side agents. + /// Some server-side agent providers require an agent id reference to be created before it can be invoked. + protected Task AgentCreateAsync(ChatClientProviders provider, ChatClientAgentOptions options, CancellationToken cancellationToken = default) + { + return provider switch + { + ChatClientProviders.OpenAIAssistant => OpenAIAssistantCreateAgentAsync(options, cancellationToken), + ChatClientProviders.AzureAIAgentsPersistent => AzureAIAgentsPersistentCreateAgentAsync(options, cancellationToken), + _ => Task.FromResult(null) + }; + } + #region Private GetChatClient - private Task GetOpenAIChatClientAsync() - => Task.FromResult( - new ChatClient(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) - .AsIChatClient()); + private IChatClient GetOpenAIChatClient() + => new ChatClient(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) + .AsIChatClient(); - private Task GetAzureOpenAIChatClientAsync() - => Task.FromResult( - ((TestConfiguration.AzureOpenAI.ApiKey is null) - // Use Azure CLI credentials if API key is not provided. - ? new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new AzureCliCredential()) - : new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey))) - .GetChatClient(TestConfiguration.AzureOpenAI.DeploymentName) - .AsIChatClient()); + private IChatClient GetAzureOpenAIChatClient() + => ((TestConfiguration.AzureOpenAI.ApiKey is null) + // Use Azure CLI credentials if API key is not provided. + ? new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new AzureCliCredential()) + : new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey))) + .GetChatClient(TestConfiguration.AzureOpenAI.DeploymentName) + .AsIChatClient(); - private Task GetOpenAIResponsesClientAsync() - => Task.FromResult( - new OpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) - .AsIChatClient()); + private IChatClient GetOpenAIResponsesClient() + => new OpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) + .AsIChatClient(); - private async Task GetAzureAIAgentPersistentClientAsync(ChatClientAgentOptions options, CancellationToken cancellationToken) + private IChatClient GetAzureAIAgentPersistentClient(ChatClientAgentOptions options) + => new PersistentAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()) + .AsIChatClient(options.Id!); + + private NewOpenAIAssistantChatClient GetOpenAIAssistantChatClient(ChatClientAgentOptions options) + => new(new(TestConfiguration.OpenAI.ApiKey), options.Id!, null); + + #endregion + + #region Private AgentCreate + + private async Task AzureAIAgentsPersistentCreateAgentAsync(ChatClientAgentOptions options, CancellationToken cancellationToken) { - var persistentAgentsClient = new PersistentAgentsClient( + var persistentAgentsClient = new Azure.AI.Agents.Persistent.PersistentAgentsAdministrationClient( TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()); - // If the Id is not provided, create a new agent. - if (options.Id is null) - { - var persistentAgentResponse = await persistentAgentsClient.Administration.CreateAgentAsync( - model: TestConfiguration.AzureAI.DeploymentName, - name: options.Name, - instructions: options.Instructions, - cancellationToken: cancellationToken); + // Create a server side agent to work with. + var result = await persistentAgentsClient.CreateAgentAsync( + model: TestConfiguration.AzureAI.DeploymentName, + name: options.Name, + instructions: options.Instructions, + cancellationToken: cancellationToken); - var persistentAgent = persistentAgentResponse.Value; - - return persistentAgentsClient.AsIChatClient(persistentAgent.Id); - } - - return persistentAgentsClient.AsIChatClient(options.Id); + return result?.Value.Id; } - private async Task GetOpenAIAssistantChatClientAsync(ChatClientAgentOptions options, CancellationToken cancellationToken) + private async Task OpenAIAssistantCreateAgentAsync(ChatClientAgentOptions options, CancellationToken cancellationToken) { - var assistantClient = new AssistantClient(TestConfiguration.OpenAI.ApiKey); + var assistantClient = new OpenAI.Assistants.AssistantClient(TestConfiguration.OpenAI.ApiKey); + Assistant assistant = await assistantClient.CreateAssistantAsync( + TestConfiguration.OpenAI.ChatModelId, + new() + { + Name = options.Name, + Instructions = options.Instructions + }, + cancellationToken); - // If the Id is not provided, create a new assistant. - if (options.Id is null) - { - Assistant assistant = await assistantClient.CreateAssistantAsync( - TestConfiguration.OpenAI.ChatModelId, - new() - { - Name = options.Name, - Instructions = options.Instructions - }, - cancellationToken); - - return new NewOpenAIAssistantChatClient(assistantClient, assistant.Id, null); - } - - return new NewOpenAIAssistantChatClient(assistantClient, options.Id, null); + return assistant.Id; } #endregion @@ -162,5 +174,21 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output) } } + private async Task OpenAIAssistantCleanUpAgentAsync(ChatClientAgent agent, AgentThread? thread, CancellationToken cancellationToken) + { + var assistantClient = agent.ChatClient + .GetService() + ?? throw new InvalidOperationException("The provided chat client is not an OpenAI Assistant Chat Client"); + + // Delete the agent. + await assistantClient.DeleteAssistantAsync(agent.Id, cancellationToken); + + // If a thread is provided, delete it as well. + if (thread is not null) + { + await assistantClient.DeleteThreadAsync(thread.Id, cancellationToken); + } + } + #endregion } diff --git a/dotnet/samples/GettingStarted/GettingStarted.csproj b/dotnet/samples/GettingStarted/GettingStarted.csproj index 3389787893..4060091bd8 100644 --- a/dotnet/samples/GettingStarted/GettingStarted.csproj +++ b/dotnet/samples/GettingStarted/GettingStarted.csproj @@ -17,7 +17,9 @@ - + + + diff --git a/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs b/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs index 1f4805dcba..5d2b5b130f 100644 --- a/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs +++ b/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs @@ -20,10 +20,11 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A /// a unique interaction with no conversation history between them. /// [Theory] + [InlineData(ChatClientProviders.AzureAIAgentsPersistent)] [InlineData(ChatClientProviders.AzureOpenAI)] + [InlineData(ChatClientProviders.OpenAIAssistant)] [InlineData(ChatClientProviders.OpenAIChatCompletion)] [InlineData(ChatClientProviders.OpenAIResponses)] - [InlineData(ChatClientProviders.AzureAIAgentsPersistent)] public async Task RunWithoutThread(ChatClientProviders provider) { // Define the options for the chat client agent. @@ -33,8 +34,11 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A Instructions = ParrotInstructions, }; + // Create the server-side agent Id when applicable (depending on the provider). + agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions); + // Get the chat client to use for the agent. - using var chatClient = await base.GetChatClientAsync(provider, agentOptions); + using var chatClient = base.GetChatClient(provider, agentOptions); // Define the agent var agent = new ChatClientAgent(chatClient, agentOptions); @@ -53,7 +57,7 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A this.WriteResponseOutput(response); } - // Clean up the agent after use when applicable. + // Clean up the server-side agent after use when applicable (depending on the provider). await base.AgentCleanUpAsync(provider, agent); } @@ -61,8 +65,9 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A /// Demonstrate the usage of where a conversation history is maintained. /// [Theory] - [InlineData(ChatClientProviders.AzureOpenAI)] [InlineData(ChatClientProviders.AzureAIAgentsPersistent)] + [InlineData(ChatClientProviders.AzureOpenAI)] + [InlineData(ChatClientProviders.OpenAIAssistant)] [InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)] [InlineData(ChatClientProviders.OpenAIResponses_ConversationIdThread)] public async Task RunWithThread(ChatClientProviders provider) @@ -77,8 +82,11 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A ChatOptions = base.GetChatOptions(provider), }; + // Create the server-side agent Id when applicable (depending on the provider). + agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions); + // Get the chat client to use for the agent. - using var chatClient = await base.GetChatClientAsync(provider, agentOptions); + using var chatClient = base.GetChatClient(provider, agentOptions); // Define the agent var agent = new ChatClientAgent(chatClient, agentOptions); @@ -100,7 +108,7 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A this.WriteResponseOutput(response); } - // Clean up the agent and thread after use when applicable. + // Clean up the server-side agent and thread after use when applicable (depending on the provider). await base.AgentCleanUpAsync(provider, agent, thread); } @@ -111,6 +119,7 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A [Theory] [InlineData(ChatClientProviders.AzureOpenAI)] [InlineData(ChatClientProviders.AzureAIAgentsPersistent)] + [InlineData(ChatClientProviders.OpenAIAssistant)] [InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)] [InlineData(ChatClientProviders.OpenAIResponses_ConversationIdThread)] public async Task RunStreamingWithThread(ChatClientProviders provider) @@ -125,8 +134,11 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A ChatOptions = base.GetChatOptions(provider), }; + // Create the server-side agent Id when applicable (depending on the provider). + agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions); + // Get the chat client to use for the agent. - using var chatClient = await base.GetChatClientAsync(provider, agentOptions); + using var chatClient = base.GetChatClient(provider, agentOptions); // Define the agent var agent = new ChatClientAgent(chatClient, agentOptions); @@ -149,7 +161,7 @@ public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : A } } - // Clean up the agent and thread after use when applicable. + // Clean up the server-side agent and thread after use when applicable (depending on the provider). await base.AgentCleanUpAsync(provider, agent, thread); } } diff --git a/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingFunctionTools.cs b/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingFunctionTools.cs index c54b0bf07a..ca0b8934a8 100644 --- a/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingFunctionTools.cs +++ b/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingFunctionTools.cs @@ -38,7 +38,7 @@ public sealed class Step02_ChatClientAgent_UsingFunctionTools(ITestOutputHelper }; // Get the chat client to use for the agent. - using var chatClient = await base.GetChatClientAsync(provider, agentOptions); + using var chatClient = base.GetChatClient(provider, agentOptions); // Define the agent var agent = new ChatClientAgent(chatClient, agentOptions); @@ -86,7 +86,7 @@ public sealed class Step02_ChatClientAgent_UsingFunctionTools(ITestOutputHelper }; // Get the chat client to use for the agent. - using var chatClient = await base.GetChatClientAsync(provider, agentOptions); + using var chatClient = base.GetChatClient(provider, agentOptions); // Define the agent var agent = new ChatClientAgent(chatClient, agentOptions); diff --git a/dotnet/samples/GettingStarted/Steps/Step03_ChatClientAgent_UsingCodeInterpreterTools.cs b/dotnet/samples/GettingStarted/Steps/Step03_ChatClientAgent_UsingCodeInterpreterTools.cs index d87219d53b..20c92cfe76 100644 --- a/dotnet/samples/GettingStarted/Steps/Step03_ChatClientAgent_UsingCodeInterpreterTools.cs +++ b/dotnet/samples/GettingStarted/Steps/Step03_ChatClientAgent_UsingCodeInterpreterTools.cs @@ -17,13 +17,12 @@ namespace Steps; public sealed class Step03_ChatClientAgent_UsingCodeInterpreterTools(ITestOutputHelper output) : AgentSample(output) { [Theory] - [InlineData(ChatClientProviders.OpenAIAssistant)] [InlineData(ChatClientProviders.AzureAIAgentsPersistent)] + [InlineData(ChatClientProviders.OpenAIAssistant)] public async Task RunningWithFileReferenceAsync(ChatClientProviders provider) { var codeInterpreterTool = new NewHostedCodeInterpreterTool(); codeInterpreterTool.FileIds.Add(await UploadFileAsync("Resources/groceries.txt", provider)); - var agentOptions = new ChatClientAgentOptions { Name = "HelpfulAssistant", @@ -31,7 +30,10 @@ public sealed class Step03_ChatClientAgent_UsingCodeInterpreterTools(ITestOutput ChatOptions = new() { Tools = [codeInterpreterTool] } }; - using var chatClient = await base.GetChatClientAsync(provider, agentOptions); + // Create the server-side agent Id when applicable (depending on the provider). + agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions); + + using var chatClient = base.GetChatClient(provider, agentOptions); ChatClientAgent agent = new(chatClient, agentOptions); @@ -61,6 +63,9 @@ public sealed class Step03_ChatClientAgent_UsingCodeInterpreterTools(ITestOutput Console.WriteLine("Code interpreter Output:"); Console.WriteLine(codeInterpreterOutput.ToString()); + + // Clean up the server-side agent after use when applicable (depending on the provider). + await base.AgentCleanUpAsync(provider, agent, thread); } #region private diff --git a/dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_DependencyInjection.cs b/dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_DependencyInjection.cs new file mode 100644 index 0000000000..38bd819720 --- /dev/null +++ b/dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_DependencyInjection.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Extensions.DependencyInjection; + +namespace Steps; + +public sealed class Step04_ChatClientAgent_DependencyInjection(ITestOutputHelper output) : AgentSample(output) +{ + [Theory] + [InlineData(ChatClientProviders.AzureAIAgentsPersistent)] + [InlineData(ChatClientProviders.AzureOpenAI)] + [InlineData(ChatClientProviders.OpenAIAssistant)] + [InlineData(ChatClientProviders.OpenAIChatCompletion)] + [InlineData(ChatClientProviders.OpenAIResponses)] + public async Task RunningWithServiceCollection(ChatClientProviders provider) + { + // Adding multiple chat clients to the service collection. + var services = new ServiceCollection(); + + var agentOptions = new ChatClientAgentOptions + { + Name = "Parrot", + Instructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.", + + // Get chat options based on the store type, if needed. + ChatOptions = base.GetChatOptions(provider), + }; + + // Create the server-side agent Id when applicable (depending on the provider). + agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions); + + services.AddSingleton(agentOptions); + + services.AddChatClient((sp) => base.GetChatClient(provider, sp.GetRequiredService())); + + services.AddSingleton(); + + // Build the service provider. + using var serviceProvider = services.BuildServiceProvider(); + + // Get the agent from the service provider. + var agent = serviceProvider.GetRequiredService(); + + // Create the chat history thread to capture the agent interaction. + var thread = agent.GetNewThread(); + + Console.WriteLine($"Using chat client for provider: {agent.ChatClient.GetService()!.ProviderName}, for agent: {agent.Name}"); + + // Respond to user input, invoking functions where appropriate. + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); + + async Task RunAgentAsync(string input) + { + this.WriteUserMessage(input); + var response = await agent.RunAsync(input, thread); + this.WriteResponseOutput(response); + } + + // Clean up the agent and thread after use when applicable (depending on the provider). + await base.AgentCleanUpAsync(provider, agent, thread); + } +}