Skip to content

Commit d5eb3d6

Browse files
Change to chaining API
1 parent 1673d92 commit d5eb3d6

File tree

5 files changed

+101
-37
lines changed

5 files changed

+101
-37
lines changed

playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
builder.AddServiceDefaults();
99

10-
builder.AddAzureOpenAIChatClient("openai");
10+
builder.AddAzureOpenAIClient("openai").AddChatClient();
1111

1212
// Add services to the container.
1313
builder.Services.AddRazorComponents()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Azure.AI.OpenAI;
5+
6+
namespace Microsoft.Extensions.Hosting;
7+
8+
/// <summary>
9+
/// A builder for configuring an <see cref="AzureOpenAIClient"/> service registration.
10+
/// </summary>
11+
public class AspireAzureOpenAIClientBuilder
12+
{
13+
/// <summary>
14+
/// Constructs a new instance of <see cref="AspireAzureOpenAIClientBuilder"/>.
15+
/// </summary>
16+
/// <param name="hostBuilder">The <see cref="IHostApplicationBuilder"/> with which services are being registered.</param>
17+
/// <param name="connectionName">The name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
18+
/// <param name="serviceKey">The service key used to register the <see cref="AzureOpenAIClient"/> service, if any.</param>
19+
public AspireAzureOpenAIClientBuilder(IHostApplicationBuilder hostBuilder, string connectionName, string? serviceKey)
20+
{
21+
HostBuilder = hostBuilder;
22+
ConnectionName = connectionName;
23+
ServiceKey = serviceKey;
24+
}
25+
26+
/// <summary>
27+
/// Gets the <see cref="IHostApplicationBuilder"/> with which services are being registered.
28+
/// </summary>
29+
public IHostApplicationBuilder HostBuilder { get; }
30+
31+
/// <summary>
32+
/// Gets the name used to retrieve the connection string from the ConnectionStrings configuration section.
33+
/// </summary>
34+
public string ConnectionName { get; }
35+
36+
/// <summary>
37+
/// Gets the service key used to register the <see cref="AzureOpenAIClient"/> service, if any.
38+
/// </summary>
39+
public string? ServiceKey { get; }
40+
}

src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIChatClientExtensions.cs src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIClientBuilderChatClientExtensions.cs

+44-30
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,73 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Data.Common;
5-
using Aspire.Azure.AI.OpenAI;
65
using Azure.AI.OpenAI;
7-
using Azure.Core.Extensions;
86
using Microsoft.Extensions.AI;
97
using Microsoft.Extensions.Configuration;
108
using Microsoft.Extensions.DependencyInjection;
11-
using OpenAI;
9+
using Microsoft.Extensions.DependencyInjection.Extensions;
1210

1311
namespace Microsoft.Extensions.Hosting;
1412

1513
/// <summary>
1614
/// Provides extension methods for registering <see cref="IChatClient"/> as a singleton in the services provided by the <see cref="IHostApplicationBuilder"/>.
1715
/// </summary>
18-
public static class AspireAzureOpenAIChatClientExtensions
16+
public static class AspireAzureOpenAIClientBuilderChatClientExtensions
1917
{
2018
private const string DeploymentKey = "Deployment";
2119
private const string ModelKey = "Model";
2220

2321
/// <summary>
2422
/// Registers a singleton <see cref="IChatClient"/> in the services provided by the <paramref name="builder"/>.
25-
///
26-
/// Additionally, registers the underlying <see cref="AzureOpenAIClient"/> and <see cref="OpenAIClient"/> as singleton services.
2723
/// </summary>
28-
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
29-
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
24+
/// <param name="builder">An <see cref="AspireAzureOpenAIClientBuilder" />.</param>
25+
/// <param name="deploymentName">Optionally specifies which model deployment to use. If not specified, a value will be taken from the connection string.</param>
3026
/// <param name="configurePipeline">An optional method that can be used for customizing the <see cref="IChatClient"/> pipeline.</param>
31-
/// <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>
32-
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{AzureOpenAIClient, AzureOpenAIClientOptions}"/>.</param>
33-
/// <param name="deploymentName">Optionally specifies the deployment name. If not specified, a value will be taken from the connection string.</param>
3427
/// <remarks>Reads the configuration from "Aspire.Azure.AI.OpenAI" section.</remarks>
35-
public static void AddAzureOpenAIChatClient(
36-
this IHostApplicationBuilder builder,
37-
string connectionName,
38-
Func<ChatClientBuilder, ChatClientBuilder>? configurePipeline = null,
39-
Action<AzureOpenAISettings>? configureSettings = null,
40-
Action<IAzureClientBuilder<AzureOpenAIClient, AzureOpenAIClientOptions>>? configureClientBuilder = null,
41-
string? deploymentName = null)
28+
public static void AddChatClient(
29+
this AspireAzureOpenAIClientBuilder builder,
30+
string? deploymentName = null,
31+
Func<ChatClientBuilder, ChatClientBuilder>? configurePipeline = null)
4232
{
43-
builder.AddAzureOpenAIClient(connectionName, configureSettings, configureClientBuilder);
33+
builder.HostBuilder.Services.AddSingleton(
34+
services => CreateChatClient(services, builder, deploymentName, configurePipeline));
35+
}
4436

45-
builder.Services.AddSingleton(services =>
46-
{
47-
var chatClientBuilder = new ChatClientBuilder(services);
48-
configurePipeline?.Invoke(chatClientBuilder);
37+
/// <summary>
38+
/// Registers a keyed singleton <see cref="IChatClient"/> in the services provided by the <paramref name="builder"/>.
39+
/// </summary>
40+
/// <param name="builder">An <see cref="AspireAzureOpenAIClientBuilder" />.</param>
41+
/// <param name="serviceKey">The service key with which the <see cref="IChatClient"/> will be registered.</param>
42+
/// <param name="deploymentName">Optionally specifies which model deployment to use. If not specified, a value will be taken from the connection string.</param>
43+
/// <param name="configurePipeline">An optional method that can be used for customizing the <see cref="IChatClient"/> pipeline.</param>
44+
/// <remarks>Reads the configuration from "Aspire.Azure.AI.OpenAI" section.</remarks>
45+
public static void AddKeyedChatClient(
46+
this AspireAzureOpenAIClientBuilder builder,
47+
string serviceKey,
48+
string? deploymentName = null,
49+
Func<ChatClientBuilder, ChatClientBuilder>? configurePipeline = null)
50+
{
51+
builder.HostBuilder.Services.TryAddKeyedSingleton(
52+
serviceKey,
53+
(services, _) => CreateChatClient(services, builder, deploymentName, configurePipeline));
54+
}
55+
56+
private static IChatClient CreateChatClient(
57+
IServiceProvider services,
58+
AspireAzureOpenAIClientBuilder builder,
59+
string? deploymentName,
60+
Func<ChatClientBuilder, ChatClientBuilder>? configurePipeline)
61+
{
62+
var openAiClient = builder.ServiceKey is null
63+
? services.GetRequiredService<AzureOpenAIClient>()
64+
: services.GetRequiredKeyedService<AzureOpenAIClient>(builder.ServiceKey);
4965

50-
deploymentName ??= GetRequiredDeploymentName(builder.Configuration, connectionName);
66+
var chatClientBuilder = new ChatClientBuilder(services);
67+
configurePipeline?.Invoke(chatClientBuilder);
5168

52-
var innerClient = chatClientBuilder.Services
53-
.GetRequiredService<AzureOpenAIClient>()
54-
.AsChatClient(deploymentName);
69+
deploymentName ??= GetRequiredDeploymentName(builder.HostBuilder.Configuration, builder.ConnectionName);
5570

56-
return chatClientBuilder.Use(innerClient);
57-
});
71+
return chatClientBuilder.Use(openAiClient.AsChatClient(deploymentName));
5872
}
5973

6074
private static string GetRequiredDeploymentName(IConfiguration configuration, string connectionName)
@@ -84,7 +98,7 @@ private static string GetRequiredDeploymentName(IConfiguration configuration, st
8498

8599
if (string.IsNullOrEmpty(deploymentName))
86100
{
87-
throw new InvalidOperationException($"An {nameof(IChatClient)} could not be configured. Ensure a '{DeploymentKey}' or '{ModelKey}' value is provided in 'ConnectionStrings:{connectionName}', or specify a '{DeploymentKey}' in the '{configurationSectionName}' configuration section, or specify a '{nameof(deploymentName)}' in the call to {nameof(AddAzureOpenAIChatClient)}.");
101+
throw new InvalidOperationException($"An {nameof(IChatClient)} could not be configured. Ensure a '{DeploymentKey}' or '{ModelKey}' value is provided in 'ConnectionStrings:{connectionName}', or specify a '{DeploymentKey}' in the '{configurationSectionName}' configuration section, or specify a '{nameof(deploymentName)}' in the call to {nameof(AddChatClient)}.");
88102
}
89103

90104
return deploymentName;

src/Components/Aspire.Azure.AI.OpenAI/AspireAzureOpenAIExtensions.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static class AspireAzureOpenAIExtensions
3434
/// <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>
3535
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{AzureOpenAIClient, AzureOpenAIClientOptions}"/>.</param>
3636
/// <remarks>Reads the configuration from "Aspire.Azure.AI.OpenAI" section.</remarks>
37-
public static void AddAzureOpenAIClient(
37+
public static AspireAzureOpenAIClientBuilder AddAzureOpenAIClient(
3838
this IHostApplicationBuilder builder,
3939
string connectionName,
4040
Action<AzureOpenAISettings>? configureSettings = null,
@@ -44,6 +44,8 @@ public static void AddAzureOpenAIClient(
4444

4545
// Add the AzureOpenAIClient service as OpenAIClient. That way the service can be resolved by both service Types.
4646
builder.Services.TryAddSingleton(typeof(OpenAIClient), static provider => provider.GetRequiredService<AzureOpenAIClient>());
47+
48+
return new AspireAzureOpenAIClientBuilder(builder, connectionName, serviceKey: null);
4749
}
4850

4951
/// <summary>
@@ -56,7 +58,7 @@ public static void AddAzureOpenAIClient(
5658
/// <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>
5759
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{AzureOpenAIClient, OpenAIClientOptions}"/>.</param>
5860
/// <remarks>Reads the configuration from "Aspire.Azure.AI.OpenAI:{name}" section.</remarks>
59-
public static void AddKeyedAzureOpenAIClient(
61+
public static AspireAzureOpenAIClientBuilder AddKeyedAzureOpenAIClient(
6062
this IHostApplicationBuilder builder,
6163
string name,
6264
Action<AzureOpenAISettings>? configureSettings = null,
@@ -68,6 +70,8 @@ public static void AddKeyedAzureOpenAIClient(
6870

6971
// Add the AzureOpenAIClient service as OpenAIClient. That way the service can be resolved by both service Types.
7072
builder.Services.TryAddKeyedSingleton(typeof(OpenAIClient), serviceKey: name, static (provider, key) => provider.GetRequiredKeyedService<AzureOpenAIClient>(key));
73+
74+
return new AspireAzureOpenAIClientBuilder(builder, name, name);
7175
}
7276

7377
private sealed class OpenAIComponent : AzureComponent<AzureOpenAISettings, AzureOpenAIClient, AzureOpenAIClientOptions>

src/Components/Aspire.Azure.AI.OpenAI/PublicAPI.Unshipped.txt

+10-4
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@ Aspire.Azure.AI.OpenAI.AzureOpenAISettings.Endpoint.get -> System.Uri?
1111
Aspire.Azure.AI.OpenAI.AzureOpenAISettings.Endpoint.set -> void
1212
Aspire.Azure.AI.OpenAI.AzureOpenAISettings.Key.get -> string?
1313
Aspire.Azure.AI.OpenAI.AzureOpenAISettings.Key.set -> void
14-
Microsoft.Extensions.Hosting.AspireAzureOpenAIChatClientExtensions
14+
Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilder
15+
Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilder.AspireAzureOpenAIClientBuilder(Microsoft.Extensions.Hosting.IHostApplicationBuilder! hostBuilder, string! connectionName, string? serviceKey) -> void
16+
Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilder.ConnectionName.get -> string!
17+
Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilder.HostBuilder.get -> Microsoft.Extensions.Hosting.IHostApplicationBuilder!
18+
Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilder.ServiceKey.get -> string?
19+
Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilderChatClientExtensions
1520
Microsoft.Extensions.Hosting.AspireAzureOpenAIExtensions
1621
Microsoft.Extensions.Hosting.AspireConfigurableOpenAIExtensions
17-
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
18-
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
19-
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
22+
static Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilderChatClientExtensions.AddChatClient(this Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilder! builder, string? deploymentName = null, System.Func<Microsoft.Extensions.AI.ChatClientBuilder!, Microsoft.Extensions.AI.ChatClientBuilder!>? configurePipeline = null) -> void
23+
static Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilderChatClientExtensions.AddKeyedChatClient(this Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilder! builder, string! serviceKey, string? deploymentName = null, System.Func<Microsoft.Extensions.AI.ChatClientBuilder!, Microsoft.Extensions.AI.ChatClientBuilder!>? configurePipeline = null) -> void
24+
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) -> Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilder!
25+
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) -> Microsoft.Extensions.Hosting.AspireAzureOpenAIClientBuilder!
2026
static Microsoft.Extensions.Hosting.AspireConfigurableOpenAIExtensions.AddKeyedOpenAIClientFromConfiguration(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name) -> void
2127
static Microsoft.Extensions.Hosting.AspireConfigurableOpenAIExtensions.AddOpenAIClientFromConfiguration(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName) -> void

0 commit comments

Comments
 (0)