|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
3 | 3 |
|
4 | 4 | using System.Data.Common;
|
5 |
| -using Aspire.Azure.AI.OpenAI; |
6 | 5 | using Azure.AI.OpenAI;
|
7 |
| -using Azure.Core.Extensions; |
8 | 6 | using Microsoft.Extensions.AI;
|
9 | 7 | using Microsoft.Extensions.Configuration;
|
10 | 8 | using Microsoft.Extensions.DependencyInjection;
|
11 |
| -using OpenAI; |
| 9 | +using Microsoft.Extensions.DependencyInjection.Extensions; |
12 | 10 |
|
13 | 11 | namespace Microsoft.Extensions.Hosting;
|
14 | 12 |
|
15 | 13 | /// <summary>
|
16 | 14 | /// Provides extension methods for registering <see cref="IChatClient"/> as a singleton in the services provided by the <see cref="IHostApplicationBuilder"/>.
|
17 | 15 | /// </summary>
|
18 |
| -public static class AspireAzureOpenAIChatClientExtensions |
| 16 | +public static class AspireAzureOpenAIClientBuilderChatClientExtensions |
19 | 17 | {
|
20 | 18 | private const string DeploymentKey = "Deployment";
|
21 | 19 | private const string ModelKey = "Model";
|
22 | 20 |
|
23 | 21 | /// <summary>
|
24 | 22 | /// 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. |
27 | 23 | /// </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> |
30 | 26 | /// <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> |
34 | 27 | /// <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) |
42 | 32 | {
|
43 |
| - builder.AddAzureOpenAIClient(connectionName, configureSettings, configureClientBuilder); |
| 33 | + builder.HostBuilder.Services.AddSingleton( |
| 34 | + services => CreateChatClient(services, builder, deploymentName, configurePipeline)); |
| 35 | + } |
44 | 36 |
|
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); |
49 | 65 |
|
50 |
| - deploymentName ??= GetRequiredDeploymentName(builder.Configuration, connectionName); |
| 66 | + var chatClientBuilder = new ChatClientBuilder(services); |
| 67 | + configurePipeline?.Invoke(chatClientBuilder); |
51 | 68 |
|
52 |
| - var innerClient = chatClientBuilder.Services |
53 |
| - .GetRequiredService<AzureOpenAIClient>() |
54 |
| - .AsChatClient(deploymentName); |
| 69 | + deploymentName ??= GetRequiredDeploymentName(builder.HostBuilder.Configuration, builder.ConnectionName); |
55 | 70 |
|
56 |
| - return chatClientBuilder.Use(innerClient); |
57 |
| - }); |
| 71 | + return chatClientBuilder.Use(openAiClient.AsChatClient(deploymentName)); |
58 | 72 | }
|
59 | 73 |
|
60 | 74 | private static string GetRequiredDeploymentName(IConfiguration configuration, string connectionName)
|
@@ -84,7 +98,7 @@ private static string GetRequiredDeploymentName(IConfiguration configuration, st
|
84 | 98 |
|
85 | 99 | if (string.IsNullOrEmpty(deploymentName))
|
86 | 100 | {
|
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)}."); |
88 | 102 | }
|
89 | 103 |
|
90 | 104 | return deploymentName;
|
|
0 commit comments