diff --git a/Directory.Packages.props b/Directory.Packages.props index ddc8a6da1d..b32c27a940 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -92,6 +92,8 @@ + + diff --git a/eng/Versions.props b/eng/Versions.props index 43b8ac325b..7cffa7ed19 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -39,6 +39,7 @@ 9.0.0-beta.24516.2 9.0.0-beta.24516.2 9.0.0-beta.24516.2 + 9.0.0-preview.9.24507.7 9.0.0-preview.9.24518.1 8.10.0 8.0.0 diff --git a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/App.razor b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/App.razor index 0f9b81fecd..7ff7bd32e3 100644 --- a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/App.razor +++ b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/App.razor @@ -7,12 +7,16 @@ - + - + + +@code { + IComponentRenderMode renderMode = new InteractiveServerRenderMode(prerender: false); +} diff --git a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/Pages/Home.razor b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/Pages/Home.razor index f989e760ce..543b9804ae 100644 --- a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/Pages/Home.razor +++ b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/Pages/Home.razor @@ -1,5 +1,4 @@ @page "/" -@rendermode @(new InteractiveServerRenderMode(prerender: false)) @using OpenAI @using OpenAI.Chat @inject OpenAIClient aiClient diff --git a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/Pages/UseIChatClient.razor b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/Pages/UseIChatClient.razor new file mode 100644 index 0000000000..69e3e68db2 --- /dev/null +++ b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Components/Pages/UseIChatClient.razor @@ -0,0 +1,37 @@ +@page "/useichatclient" +@using Microsoft.Extensions.AI +@inject IChatClient aiClient +@inject ILogger logger +@inject IConfiguration configuration + +
+ @foreach (var message in chatMessages.Where(m => m.Role == ChatRole.Assistant)) + { +

@message.Text

+ } + + +
+ +@code { + private List chatMessages = new List + { + new(ChatRole.System, "Pick a random topic and write a sentence of a fictional story about it.") + }; + + private async Task GenerateNextParagraph() + { + if (chatMessages.Count > 1) + { + chatMessages.Add(new (ChatRole.User, "Write the next sentence in the story.")); + } + + var response = await aiClient.CompleteAsync(chatMessages); + chatMessages.Add(response.Message); + } + + protected override async Task OnInitializedAsync() + { + await GenerateNextParagraph(); + } +} diff --git a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/OpenAIEndToEnd.WebStory.csproj b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/OpenAIEndToEnd.WebStory.csproj index ede52a8521..e9ab77317f 100644 --- a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/OpenAIEndToEnd.WebStory.csproj +++ b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/OpenAIEndToEnd.WebStory.csproj @@ -11,4 +11,6 @@ + + diff --git a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Program.cs b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Program.cs index 5b98bcffa3..3ca85cb813 100644 --- a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Program.cs +++ b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Program.cs @@ -7,7 +7,7 @@ builder.AddServiceDefaults(); -builder.AddAzureOpenAIClient("openai"); +builder.AddAzureOpenAIChatClient("openai"); // Add services to the container. builder.Services.AddRazorComponents() diff --git a/src/Components/Aspire.Azure.AI.OpenAI/Aspire.Azure.AI.OpenAI.csproj b/src/Components/Aspire.Azure.AI.OpenAI/Aspire.Azure.AI.OpenAI.csproj index 9dc56c3b4e..4c75ad910a 100644 --- a/src/Components/Aspire.Azure.AI.OpenAI/Aspire.Azure.AI.OpenAI.csproj +++ b/src/Components/Aspire.Azure.AI.OpenAI/Aspire.Azure.AI.OpenAI.csproj @@ -34,4 +34,6 @@ + + diff --git a/src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIChatClientExtensions.cs b/src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIChatClientExtensions.cs new file mode 100644 index 0000000000..b80c9962e2 --- /dev/null +++ b/src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIChatClientExtensions.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data.Common; +using Aspire.Azure.AI.OpenAI; +using Azure.AI.OpenAI; +using Azure.Core.Extensions; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using OpenAI; + +namespace Microsoft.Extensions.Hosting; + +/// +/// Provides extension methods for registering as a singleton in the services provided by the . +/// +public static class AspireAzureOpenAIChatClientExtensions +{ + private const string DeployentKey = "Deployment"; + private const string ModelKey = "Model"; + + /// + /// Registers a singleton in the services provided by the . + /// + /// Additionally, registers the underlying and as singleton services. + /// + /// The to read config from and add services to. + /// A name used to retrieve the connection string from the ConnectionStrings configuration section. + /// An optional method that can be used for customizing the pipeline. + /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. + /// An optional method that can be used for customizing the . + /// Optionally specifies the deployment name. If not specified, a value will be taken from the connection string. + /// Reads the configuration from "Aspire.Azure.AI.OpenAI" section. + public static void AddAzureOpenAIChatClient( + this IHostApplicationBuilder builder, + string connectionName, + Func? configurePipeline = null, + Action? configureSettings = null, + Action>? configureClientBuilder = null, + string? deploymentName = null) + { + builder.AddAzureOpenAIClient(connectionName, configureSettings, configureClientBuilder); + + builder.Services.AddSingleton(services => + { + var chatClientBuilder = new ChatClientBuilder(services); + configurePipeline?.Invoke(chatClientBuilder); + + deploymentName ??= GetRequiredDeploymentName(builder.Configuration, connectionName); + + var innerClient = chatClientBuilder.Services + .GetRequiredService() + .AsChatClient(deploymentName); + + return chatClientBuilder.Use(innerClient); + }); + } + + private static string GetRequiredDeploymentName(IConfiguration configuration, string connectionName) + { + string? deploymentName = null; + + if (configuration.GetConnectionString(connectionName) is string connectionString) + { + var connectionBuilder = new DbConnectionStringBuilder { ConnectionString = connectionString }; + deploymentName = (connectionBuilder[DeployentKey] ?? connectionBuilder[ModelKey]).ToString(); + } + + var configurationSectionName = AspireAzureOpenAIExtensions.DefaultConfigSectionName; + if (string.IsNullOrEmpty(deploymentName)) + { + var configSection = configuration.GetSection(configurationSectionName); + deploymentName = configSection[DeployentKey]; + } + + if (string.IsNullOrEmpty(deploymentName)) + { + throw new InvalidOperationException($"An {nameof(IChatClient)} could not be configured. Ensure a '{DeployentKey}' or '{ModelKey}' value is provided in 'ConnectionStrings:{connectionName}', or specify a '{DeployentKey}' in the '{configurationSectionName}' configuration section, or specify a '{nameof(deploymentName)}' in the call to {nameof(AddAzureOpenAIChatClient)}."); + } + + return deploymentName; + } +} diff --git a/src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIExtensions.cs b/src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIExtensions.cs index 32e05e062a..35ddf55ef8 100644 --- a/src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIExtensions.cs +++ b/src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIExtensions.cs @@ -22,7 +22,7 @@ namespace Microsoft.Extensions.Hosting; /// public static class AspireAzureOpenAIExtensions { - private const string DefaultConfigSectionName = "Aspire:Azure:AI:OpenAI"; + internal const string DefaultConfigSectionName = "Aspire:Azure:AI:OpenAI"; /// /// Registers as a singleton in the services provided by the . diff --git a/src/Components/Aspire.Azure.AI.OpenAI/PublicAPI.Unshipped.txt b/src/Components/Aspire.Azure.AI.OpenAI/PublicAPI.Unshipped.txt index 1f2eb92b6e..a956c0122a 100644 --- a/src/Components/Aspire.Azure.AI.OpenAI/PublicAPI.Unshipped.txt +++ b/src/Components/Aspire.Azure.AI.OpenAI/PublicAPI.Unshipped.txt @@ -11,8 +11,10 @@ Aspire.Azure.AI.OpenAI.AzureOpenAISettings.Endpoint.get -> System.Uri? Aspire.Azure.AI.OpenAI.AzureOpenAISettings.Endpoint.set -> void Aspire.Azure.AI.OpenAI.AzureOpenAISettings.Key.get -> string? Aspire.Azure.AI.OpenAI.AzureOpenAISettings.Key.set -> void +Microsoft.Extensions.Hosting.AspireAzureOpenAIChatClientExtensions Microsoft.Extensions.Hosting.AspireAzureOpenAIExtensions Microsoft.Extensions.Hosting.AspireConfigurableOpenAIExtensions +static Microsoft.Extensions.Hosting.AspireAzureOpenAIChatClientExtensions.AddAzureOpenAIChatClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Func? configurePipeline = null, System.Action? configureSettings = null, System.Action!>? configureClientBuilder = null, string? deploymentName = null) -> void static Microsoft.Extensions.Hosting.AspireAzureOpenAIExtensions.AddAzureOpenAIClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Action? configureSettings = null, System.Action!>? configureClientBuilder = null) -> void static Microsoft.Extensions.Hosting.AspireAzureOpenAIExtensions.AddKeyedAzureOpenAIClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name, System.Action? configureSettings = null, System.Action!>? configureClientBuilder = null) -> void static Microsoft.Extensions.Hosting.AspireConfigurableOpenAIExtensions.AddKeyedOpenAIClientFromConfiguration(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name) -> void diff --git a/src/Components/Aspire.OpenAI/Aspire.OpenAI.csproj b/src/Components/Aspire.OpenAI/Aspire.OpenAI.csproj index fd8747d8ac..228fa521d3 100644 --- a/src/Components/Aspire.OpenAI/Aspire.OpenAI.csproj +++ b/src/Components/Aspire.OpenAI/Aspire.OpenAI.csproj @@ -19,9 +19,12 @@ - + + + + diff --git a/src/Components/Aspire.OpenAI/MEAIPackageOverrides.targets b/src/Components/Aspire.OpenAI/MEAIPackageOverrides.targets new file mode 100644 index 0000000000..268bffe277 --- /dev/null +++ b/src/Components/Aspire.OpenAI/MEAIPackageOverrides.targets @@ -0,0 +1,13 @@ + + + + + + + + +