Skip to content

Commit

Permalink
Add AddAzureOpenAIChatClient
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSandersonMS committed Oct 25, 2024
1 parent 11e9f58 commit c565c53
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="$(MicrosoftEntityFrameworkCoreToolsPackageVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(MicrosoftEntityFrameworkCoreDesignPackageVersion)" />
<!-- runtime dependencies-->
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="$(MicrosoftExtensionsAIPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="$(MicrosoftExtensionsConfigurationAbstractionsPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsConfigurationBinderPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion)" />
Expand Down
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<MicrosoftDotNetXUnitExtensionsPackageVersion>9.0.0-beta.24516.2</MicrosoftDotNetXUnitExtensionsPackageVersion>
<MicrosoftDotNetBuildTasksInstallersPackageVersion>9.0.0-beta.24516.2</MicrosoftDotNetBuildTasksInstallersPackageVersion>
<MicrosoftDotNetBuildTasksWorkloadsPackageVersion>9.0.0-beta.24516.2</MicrosoftDotNetBuildTasksWorkloadsPackageVersion>
<MicrosoftExtensionsAIPackageVersion>9.0.0-preview.9.24507.7</MicrosoftExtensionsAIPackageVersion>
<MicrosoftExtensionsHttpResiliencePackageVersion>9.0.0-preview.9.24518.1</MicrosoftExtensionsHttpResiliencePackageVersion>
<MicrosoftExtensionsDiagnosticsTestingPackageVersion>8.10.0</MicrosoftExtensionsDiagnosticsTestingPackageVersion>
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>8.0.0</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
<base href="/" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="OpenAIEndToEnd.WebStory.styles.css" />
<HeadOutlet @rendermode="InteractiveServer" />
<HeadOutlet @rendermode="@renderMode" />
</head>

<body>
<Routes @rendermode="InteractiveServer" />
<Routes @rendermode="@renderMode" />
<script src="_framework/blazor.web.js"></script>
</body>

</html>

@code {
IComponentRenderMode renderMode = new InteractiveServerRenderMode(prerender: false);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@page "/"
@rendermode @(new InteractiveServerRenderMode(prerender: false))
@using OpenAI
@using OpenAI.Chat
@inject OpenAIClient aiClient
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@page "/useichatclient"
@using Microsoft.Extensions.AI
@inject IChatClient aiClient
@inject ILogger<Home> logger
@inject IConfiguration configuration

<div class="storybox" style="margin: 25%">
@foreach (var message in chatMessages.Where(m => m.Role == ChatRole.Assistant))
{
<p style="font-size: 3em;">@message.Text</p>
}

<button @onclick="GenerateNextParagraph" autofocus>Generate</button>
</div>

@code {
private List<ChatMessage> chatMessages = new List<ChatMessage>
{
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@
<ProjectReference Include="..\..\Playground.ServiceDefaults\Playground.ServiceDefaults.csproj" />
</ItemGroup>

<Import Project="$(RepoRoot)\src\Components\Aspire.OpenAI\MEAIPackageOverrides.targets" />

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

builder.AddServiceDefaults();

builder.AddAzureOpenAIClient("openai");
builder.AddAzureOpenAIChatClient("openai");

// Add services to the container.
builder.Services.AddRazorComponents()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@
<ProjectReference Include="..\Aspire.OpenAI\Aspire.OpenAI.csproj" />
</ItemGroup>

<Import Project="..\Aspire.OpenAI\MEAIPackageOverrides.targets" />

</Project>
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Provides extension methods for registering <see cref="IChatClient"/> as a singleton in the services provided by the <see cref="IHostApplicationBuilder"/>.
/// </summary>
public static class AspireAzureOpenAIChatClientExtensions
{
private const string DeployentKey = "Deployment";
private const string ModelKey = "Model";

/// <summary>
/// Registers a singleton <see cref="IChatClient"/> in the services provided by the <paramref name="builder"/>.
///
/// Additionally, registers the underlying <see cref="AzureOpenAIClient"/> and <see cref="OpenAIClient"/> as singleton services.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
/// <param name="configurePipeline">An optional method that can be used for customizing the <see cref="IChatClient"/> pipeline.</param>
/// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureOpenAISettings"/>. It's invoked after the settings are read from the configuration.</param>
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{AzureOpenAIClient, AzureOpenAIClientOptions}"/>.</param>
/// <param name="deploymentName">Optionally specifies the deployment name. If not specified, a value will be taken from the connection string.</param>
/// <remarks>Reads the configuration from "Aspire.Azure.AI.OpenAI" section.</remarks>
public static void AddAzureOpenAIChatClient(
this IHostApplicationBuilder builder,
string connectionName,
Func<ChatClientBuilder, ChatClientBuilder>? configurePipeline = null,
Action<AzureOpenAISettings>? configureSettings = null,
Action<IAzureClientBuilder<AzureOpenAIClient, AzureOpenAIClientOptions>>? 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<AzureOpenAIClient>()
.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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Microsoft.Extensions.Hosting;
/// </summary>
public static class AspireAzureOpenAIExtensions
{
private const string DefaultConfigSectionName = "Aspire:Azure:AI:OpenAI";
internal const string DefaultConfigSectionName = "Aspire:Azure:AI:OpenAI";

/// <summary>
/// Registers <see cref="AzureOpenAIClient"/> as a singleton in the services provided by the <paramref name="builder"/>.
Expand Down
2 changes: 2 additions & 0 deletions src/Components/Aspire.Azure.AI.OpenAI/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Microsoft.Extensions.AI.ChatClientBuilder!, Microsoft.Extensions.AI.ChatClientBuilder!>? configurePipeline = null, System.Action<Aspire.Azure.AI.OpenAI.AzureOpenAISettings!>? configureSettings = null, System.Action<Azure.Core.Extensions.IAzureClientBuilder<Azure.AI.OpenAI.AzureOpenAIClient!, Azure.AI.OpenAI.AzureOpenAIClientOptions!>!>? configureClientBuilder = null, string? deploymentName = null) -> void
static Microsoft.Extensions.Hosting.AspireAzureOpenAIExtensions.AddAzureOpenAIClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Action<Aspire.Azure.AI.OpenAI.AzureOpenAISettings!>? configureSettings = null, System.Action<Azure.Core.Extensions.IAzureClientBuilder<Azure.AI.OpenAI.AzureOpenAIClient!, Azure.AI.OpenAI.AzureOpenAIClientOptions!>!>? configureClientBuilder = null) -> void
static Microsoft.Extensions.Hosting.AspireAzureOpenAIExtensions.AddKeyedAzureOpenAIClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name, System.Action<Aspire.Azure.AI.OpenAI.AzureOpenAISettings!>? configureSettings = null, System.Action<Azure.Core.Extensions.IAzureClientBuilder<Azure.AI.OpenAI.AzureOpenAIClient!, Azure.AI.OpenAI.AzureOpenAIClientOptions!>!>? configureClientBuilder = null) -> void
static Microsoft.Extensions.Hosting.AspireConfigurableOpenAIExtensions.AddKeyedOpenAIClientFromConfiguration(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name) -> void
Expand Down
5 changes: 4 additions & 1 deletion src/Components/Aspire.OpenAI/Aspire.OpenAI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="OpenAI" />
<PackageReference Include="Microsoft.Extensions.AI" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
</ItemGroup>

<Import Project="MEAIPackageOverrides.targets" />

</Project>
13 changes: 13 additions & 0 deletions src/Components/Aspire.OpenAI/MEAIPackageOverrides.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project>
<ItemGroup>
<!--
Microsoft.Extensions.AI depends on 9.x packages, even on net8.0, so we have to override central package management
to avoid "package downgrade" build errors. This is only used when referencing Aspire.OpenAI and doesn't break
compatibility with net8.0.
-->
<PackageReference Include="Microsoft.Extensions.Primitives" VersionOverride="9.0.0-*" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" VersionOverride="9.0.0-*" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" VersionOverride="9.0.0-*" />
<PackageReference Include="System.Text.Json" VersionOverride="9.0.0-*" />
</ItemGroup>
</Project>

0 comments on commit c565c53

Please sign in to comment.