-
Notifications
You must be signed in to change notification settings - Fork 715
Integration for Azure.AI.Inference
#9103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
cacc720
5f10191
3a2cc94
51ac74c
f7230ed
7f4889d
e08b444
efdc6f8
ede53da
137f02f
9f9c025
1357337
c7cf933
70ad18f
dfbaed1
d7e69de
5e2d407
32c281a
30fb1ec
b19df43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>$(DefaultTargetFramework)</TargetFramework> | ||
| <IsPackable>true</IsPackable> | ||
| <PackageTags>$(ComponentAzurePackageTags) ai</PackageTags> | ||
| <Description>A client for Azure AI Inference SDK that integrates with Aspire, including logging and telemetry.</Description> | ||
| <PackageIconFullPath>$(SharedDir)Azure_256x.png</PackageIconFullPath> | ||
| <NoWarn>$(NoWarn);SYSLIB1100;SYSLIB1101</NoWarn> | ||
| <!-- In preview until the public API is validated and the Microsoft.Extensions.AI integration is designed.. --> | ||
aaronpowell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| <SuppressFinalPackageVersion>true</SuppressFinalPackageVersion> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <Compile Include="..\Common\AzureComponent.cs" Link="AzureComponent.cs" /> | ||
| <Compile Include="..\Common\ConfigurationSchemaAttributes.cs" Link="ConfigurationSchemaAttributes.cs" /> | ||
sebastienros marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <Compile Include="..\Common\HealthChecksExtensions.cs" Link="HealthChecksExtensions.cs" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.AI.Inference" /> | ||
| <PackageReference Include="Microsoft.Extensions.AI" /> | ||
| <PackageReference Include="Microsoft.Extensions.AI.AzureAIInference" /> | ||
| <PackageReference Include="Microsoft.Extensions.Azure" /> | ||
| <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" /> | ||
| <PackageReference Include="Microsoft.Extensions.Configuration.Binder" /> | ||
| <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" /> | ||
| <PackageReference Include="OpenTelemetry.Extensions.Hosting" /> | ||
| </ItemGroup> | ||
| </Project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Azure.Common; | ||
| using Azure; | ||
| using Azure.AI.Inference; | ||
| using Azure.Core; | ||
| using Azure.Core.Extensions; | ||
| using Azure.Identity; | ||
| using Microsoft.Extensions.AI; | ||
| using Microsoft.Extensions.Azure; | ||
| using Microsoft.Extensions.Configuration; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
|
|
||
| namespace Microsoft.Extensions.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Extension methods for adding Azure AI Inference services to an Aspire application. | ||
| /// </summary> | ||
| public static class AspireAzureAIInferenceExtensions | ||
| { | ||
| private const string DefaultConfigSectionName = "Aspire:Azure:AI:Inference"; | ||
|
|
||
| /// <summary> | ||
| /// Adds a <see cref="ChatCompletionsClient"/> to the application and configures it with the specified settings. | ||
| /// </summary> | ||
| /// <param name="builder">The <see cref="IHostApplicationBuilder"/> to add the client to.</param> | ||
| /// <param name="connectionName">The name of the client. This is used to retrieve the connection string from configuration.</param> | ||
| /// <param name="configureClient">An optional callback to configure the <see cref="ChatCompletionsClientSettings"/>.</param> | ||
| /// <param name="configureClientBuilder">An optional callback to configure the <see cref="IAzureClientBuilder{TClient, TOptions}"/> for the client.</param> | ||
| /// <returns>An <see cref="AspireChatCompletionsClientBuilder"/> that can be used to further configure the client.</returns> | ||
| /// <exception cref="InvalidOperationException">Thrown when endpoint is missing from settings.</exception> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// The client is registered as a singleton with a keyed service. | ||
| /// </para> | ||
| /// <para> | ||
| /// Configuration is loaded from the "Aspire:Azure:AI:Inference" section, and can be supplemented with a connection string named after the <paramref name="connectionName"/> parameter. | ||
| /// </para> | ||
| /// </remarks> | ||
| public static AspireChatCompletionsClientBuilder AddChatCompletionsClient( | ||
| this IHostApplicationBuilder builder, | ||
| string connectionName, | ||
| Action<ChatCompletionsClientSettings>? configureClient = null, | ||
|
||
| Action<IAzureClientBuilder<ChatCompletionsClient, AzureAIInferenceClientOptions>>? configureClientBuilder = null) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
| ArgumentException.ThrowIfNullOrEmpty(connectionName); | ||
|
|
||
| var settings = new ChatCompletionsClientServiceComponent().AddClient( | ||
| builder, | ||
| DefaultConfigSectionName, | ||
| configureClient, | ||
| configureClientBuilder, | ||
| connectionName, | ||
| serviceKey: null); | ||
|
|
||
| return new AspireChatCompletionsClientBuilder(builder, serviceKey: null, settings.ModelId, settings.DisableTracing); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds a <see cref="ChatCompletionsClient"/> to the application and configures it with the specified settings. | ||
| /// </summary> | ||
| /// <param name="builder">The <see cref="IHostApplicationBuilder"/> to add the client to.</param> | ||
| /// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection string from the ConnectionStrings configuration section.</param> | ||
| /// <param name="configureClient">An optional callback to configure the <see cref="ChatCompletionsClientSettings"/>.</param> | ||
| /// <param name="configureClientBuilder">An optional callback to configure the <see cref="IAzureClientBuilder{TClient, TOptions}"/> for the client.</param> | ||
| /// <returns>An <see cref="AspireChatCompletionsClientBuilder"/> that can be used to further configure the client.</returns> | ||
| /// <exception cref="InvalidOperationException">Thrown when endpoint is missing from settings.</exception> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// The client is registered as a singleton with a keyed service. | ||
| /// </para> | ||
| /// <para> | ||
| /// Configuration is loaded from the "Aspire:Azure:AI:Inference" section, and can be supplemented with a connection string named after the <paramref name="name"/> parameter. | ||
| /// </para> | ||
| /// </remarks> | ||
| public static AspireChatCompletionsClientBuilder AddKeyedChatCompletionsClient( | ||
| this IHostApplicationBuilder builder, | ||
| string name, | ||
| Action<ChatCompletionsClientSettings>? configureClient = null, | ||
| Action<IAzureClientBuilder<ChatCompletionsClient, AzureAIInferenceClientOptions>>? configureClientBuilder = null) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
| ArgumentException.ThrowIfNullOrEmpty(name); | ||
|
|
||
| var settings = new ChatCompletionsClientServiceComponent().AddClient( | ||
| builder, | ||
| DefaultConfigSectionName, | ||
| configureClient, | ||
| configureClientBuilder, | ||
| name, | ||
| serviceKey: name); | ||
|
|
||
| return new AspireChatCompletionsClientBuilder(builder, serviceKey: name, settings.ModelId, settings.DisableTracing); | ||
| } | ||
|
|
||
| private sealed class ChatCompletionsClientServiceComponent : AzureComponent<ChatCompletionsClientSettings, ChatCompletionsClient, AzureAIInferenceClientOptions> | ||
RussKie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| protected override IAzureClientBuilder<ChatCompletionsClient, AzureAIInferenceClientOptions> AddClient( | ||
| AzureClientFactoryBuilder azureFactoryBuilder, | ||
| ChatCompletionsClientSettings settings, | ||
| string connectionName, string | ||
| configurationSectionName) | ||
| { | ||
| return azureFactoryBuilder.AddClient<ChatCompletionsClient, AzureAIInferenceClientOptions>((options, _, _) => | ||
| { | ||
| if (settings.Endpoint is null) | ||
| { | ||
| throw new InvalidOperationException($"An ChatCompletionsClient could not be configured. Ensure valid connection information was provided in 'ConnectionStrings:{connectionName}' or specify a '{nameof(ChatCompletionsClientSettings.Endpoint)}' or '{nameof(ChatCompletionsClientSettings.Key)}' in the '{configurationSectionName}' configuration section."); | ||
| } | ||
| else | ||
| { | ||
| // Connect to Azure AI Foundry using key auth | ||
| if (!string.IsNullOrEmpty(settings.Key)) | ||
| { | ||
| var credential = new AzureKeyCredential(settings.Key); | ||
| return new ChatCompletionsClient(settings.Endpoint, credential, options); | ||
| } | ||
| else | ||
| { | ||
| return new ChatCompletionsClient(settings.Endpoint, settings.TokenCredential ?? new DefaultAzureCredential(), options); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| protected override void BindClientOptionsToConfiguration(IAzureClientBuilder<ChatCompletionsClient, AzureAIInferenceClientOptions> clientBuilder, IConfiguration configuration) | ||
| { | ||
| #pragma warning disable IDE0200 // Remove unnecessary lambda expression - needed so the ConfigBinder Source Generator works | ||
| clientBuilder.ConfigureOptions(options => configuration.Bind(options)); | ||
| #pragma warning restore IDE0200 | ||
| } | ||
|
|
||
| protected override void BindSettingsToConfiguration(ChatCompletionsClientSettings settings, IConfiguration configuration) | ||
| => configuration.Bind(settings); | ||
|
|
||
| protected override IHealthCheck CreateHealthCheck(ChatCompletionsClient client, ChatCompletionsClientSettings settings) | ||
| => throw new NotImplementedException(); | ||
|
|
||
| protected override bool GetHealthCheckEnabled(ChatCompletionsClientSettings settings) | ||
| => false; | ||
|
|
||
| protected override bool GetMetricsEnabled(ChatCompletionsClientSettings settings) | ||
| => !settings.DisableMetrics; | ||
|
|
||
| protected override TokenCredential? GetTokenCredential(ChatCompletionsClientSettings settings) | ||
| => settings.TokenCredential; | ||
|
|
||
| protected override bool GetTracingEnabled(ChatCompletionsClientSettings settings) | ||
| => !settings.DisableTracing; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a <see cref="IChatClient"/> from the <see cref="ChatCompletionsClient"/> registered in the service collection. | ||
| /// </summary> | ||
| /// <param name="builder"></param> | ||
| /// <returns></returns> | ||
| public static ChatClientBuilder AddChatClient(this AspireChatCompletionsClientBuilder builder) => | ||
danmoseley marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| builder.HostBuilder.Services.AddChatClient(services => | ||
| { | ||
| var chatCompletionsClient = !string.IsNullOrEmpty(builder.ServiceKey) ? | ||
| services.GetRequiredService<ChatCompletionsClient>() : | ||
| services.GetRequiredKeyedService<ChatCompletionsClient>(builder.ServiceKey); | ||
|
|
||
| var result = chatCompletionsClient.AsIChatClient(); | ||
|
|
||
| if (builder.DisableTracing) | ||
| { | ||
| return result; | ||
| } | ||
|
|
||
| var loggerFactory = services.GetService<ILoggerFactory>(); | ||
|
Check failure on line 174 in src/Components/Aspire.Azure.AI.Inference/AspireAzureAIInferenceExtensions.cs
|
||
| return new OpenTelemetryChatClient(result, loggerFactory?.CreateLogger(typeof(OpenTelemetryChatClient))); | ||
|
||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Azure.AI.Inference; | ||
|
|
||
| namespace Microsoft.Extensions.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Provides a builder for configuring and integrating an Aspire Chat Completions client into a host application. | ||
| /// </summary> | ||
| /// <remarks>This class is used to configure the necessary parameters for creating an Aspire Chat Completions | ||
| /// client, such as the host application builder, service key, and optional model ID. It is intended for internal use | ||
| /// within the application setup process.</remarks> | ||
| /// <param name="hostBuilder">The <see cref="IHostApplicationBuilder"/> with which services are being registered.</param> | ||
| /// <param name="serviceKey">The service key used to register the <see cref="ChatCompletionsClient"/> service, if any.</param> | ||
| /// <param name="deploymentId">The id of the deployment in Azure AI Foundry.</param> | ||
| /// <param name="disableTracing">A flag to indicate whether tracing should be disabled.</param> | ||
| public class AspireChatCompletionsClientBuilder( | ||
aaronpowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| IHostApplicationBuilder hostBuilder, | ||
| string? serviceKey, | ||
| string? deploymentId, | ||
| bool disableTracing) | ||
| { | ||
| /// <summary> | ||
| /// Gets a flag indicating whether tracing should be disabled. | ||
| /// </summary> | ||
| public bool DisableTracing { get; } = disableTracing; | ||
|
|
||
| /// <summary> | ||
| /// Gets the <see cref="IHostApplicationBuilder"/> with which services are being registered. | ||
| /// </summary> | ||
| public IHostApplicationBuilder HostBuilder { get; } = hostBuilder ?? throw new ArgumentNullException(nameof(hostBuilder)); | ||
|
|
||
| /// <summary> | ||
| /// Gets the service key used to register the <see cref="ChatCompletionsClient"/> service, if any. | ||
| /// </summary> | ||
| public string? ServiceKey { get; } = serviceKey; | ||
|
|
||
| /// <summary> | ||
| /// The ID of the deployment in Azure AI Foundry. | ||
| /// </summary> | ||
| public string? DeploymentId { get; } = deploymentId; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where does this get used? |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Azure.Core; | ||
| using System.Data.Common; | ||
|
|
||
| namespace Microsoft.Extensions.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Represents configuration settings for Azure AI Chat Completions client. | ||
| /// </summary> | ||
| public sealed class ChatCompletionsClientSettings | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the name of the AI model to use for chat completions. | ||
| /// </summary> | ||
| public string? ModelName { get; set; } | ||
|
||
|
|
||
| /// <summary> | ||
| /// Gets or sets the ID of the AI model to use for chat completions. | ||
| /// </summary> | ||
| public string? ModelId { get; set; } | ||
|
||
|
|
||
| /// <summary> | ||
| /// Gets or sets the endpoint URI for the Azure AI service. | ||
| /// </summary> | ||
| public Uri? Endpoint { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the token credential used for Azure authentication. | ||
| /// </summary> | ||
| public TokenCredential? TokenCredential { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the API key used for authentication with the Azure AI service. | ||
| /// </summary> | ||
| public string? Key { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a boolean value that indicates whether the OpenTelemetry metrics are enabled or not. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// /// Azure AI Inference telemetry follows the pattern of Azure SDKs Diagnostics. | ||
| /// </remarks> | ||
| public bool DisableMetrics { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is disabled or not. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Azure AI Inference telemetry follows the pattern of Azure SDKs Diagnostics. | ||
| /// </remarks> | ||
| public bool DisableTracing { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Parses a connection string and populates the settings properties. | ||
| /// </summary> | ||
| /// <param name="connectionString">The connection string containing configuration values.</param> | ||
| /// <remarks> | ||
| /// The connection string can contain the following keys: | ||
| /// - ModelName: The name of the AI model | ||
| /// - ModelId: The ID of the AI model | ||
| /// - Endpoint: The service endpoint URI | ||
| /// - Key: The API key for authentication | ||
| /// </remarks> | ||
| internal void ParseConnectionString(string connectionString) | ||
| { | ||
| var connectionBuilder = new DbConnectionStringBuilder | ||
| { | ||
| ConnectionString = connectionString | ||
| }; | ||
|
|
||
| if (connectionBuilder.TryGetValue(nameof(ModelName), out var model)) | ||
| { | ||
| ModelName = model.ToString(); | ||
| } | ||
|
|
||
| if (connectionBuilder.TryGetValue(nameof(ModelId), out var modelId)) | ||
| { | ||
| ModelId = modelId.ToString(); | ||
| } | ||
|
|
||
| if (connectionBuilder.TryGetValue(nameof(Endpoint), out var endpoint) && Uri.TryCreate(endpoint.ToString(), UriKind.Absolute, out var serviceUri)) | ||
| { | ||
| Endpoint = serviceUri; | ||
| } | ||
|
|
||
| if (connectionBuilder.TryGetValue(nameof(Key), out var key) && !string.IsNullOrWhiteSpace(key.ToString())) | ||
| { | ||
| Key = key.ToString(); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.