Skip to content

.Net: Make AzureOpenAI service/model deployment configurable #10786

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<PackageVersion Include="Pinecone.NET" Version="2.1.1" />
<PackageVersion Include="Prompty.Core" Version="0.0.23-alpha" />
<PackageVersion Include="PuppeteerSharp" Version="20.0.5" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
<PackageVersion Include="System.Formats.Asn1" Version="8.0.2" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
Expand Down
9 changes: 9 additions & 0 deletions dotnet/SK-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithAgent.ApiService",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithAgent.Web", "samples\Demos\Hosting\Agent\ChatWithAgent.Web\ChatWithAgent.Web.csproj", "{518EBD17-F582-9E69-2716-57C9329DC0BE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithAgent.Configuration", "samples\Demos\Hosting\Agent\ChatWithAgent.Configuration\ChatWithAgent.Configuration.csproj", "{02663844-E371-416A-969A-66B6512BB2E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1296,6 +1298,12 @@ Global
{518EBD17-F582-9E69-2716-57C9329DC0BE}.Publish|Any CPU.Build.0 = Release|Any CPU
{518EBD17-F582-9E69-2716-57C9329DC0BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{518EBD17-F582-9E69-2716-57C9329DC0BE}.Release|Any CPU.Build.0 = Release|Any CPU
{02663844-E371-416A-969A-66B6512BB2E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02663844-E371-416A-969A-66B6512BB2E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02663844-E371-416A-969A-66B6512BB2E1}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{02663844-E371-416A-969A-66B6512BB2E1}.Publish|Any CPU.Build.0 = Release|Any CPU
{02663844-E371-416A-969A-66B6512BB2E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02663844-E371-416A-969A-66B6512BB2E1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1474,6 +1482,7 @@ Global
{FCEB6560-A377-88A0-3FFC-D82ACCCEB211} = {D1FD2171-A467-4F2D-9CF1-4B0DB753A689}
{CB494B28-A90B-C2AA-F7BB-CFD2A88639A0} = {D1FD2171-A467-4F2D-9CF1-4B0DB753A689}
{518EBD17-F582-9E69-2716-57C9329DC0BE} = {D1FD2171-A467-4F2D-9CF1-4B0DB753A689}
{02663844-E371-416A-969A-66B6512BB2E1} = {D1FD2171-A467-4F2D-9CF1-4B0DB753A689}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

<ItemGroup>
<ProjectReference Include="..\ChatWithAgent.ServiceDefaults\ChatWithAgent.ServiceDefaults.csproj" />
<ProjectReference Include="..\ChatWithAgent.Configuration\ChatWithAgent.Configuration.csproj" IsAspireProjectResource="false" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft. All rights reserved.

using Azure.Identity;
using ChatWithAgent.Configuration;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
using Microsoft.SemanticKernel;

namespace ChatWithAgent.ApiService.Extensions;

/// <summary>
/// Web application builder extensions.
/// </summary>
internal static class WebApplicationBuilderExtensions
{
/// <summary>
/// Adds Azure OpenAI services to the web application.
/// </summary>
/// <param name="builder">The web application builder.</param>
/// <param name="hostConfig">The host configuration.</param>
internal static void AddAzureOpenAIServices(this WebApplicationBuilder builder, HostConfig hostConfig)
{
// Add AzureOpenAI client.
builder.AddAzureOpenAIClient(
AzureOpenAIChatConfig.ConnectionStringName,
(settings) => settings.Credential = builder.Environment.IsProduction()
? new DefaultAzureCredential()
: new AzureCliCredential()); // Use credentials from Azure CLI for local development.

// Add AzureOpenAI chat completion service.
builder.Services.AddAzureOpenAIChatCompletion(hostConfig.AzureOpenAIChat.DeploymentName);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.

using Azure.Identity;
using System;
using ChatWithAgent.ApiService.Extensions;
using ChatWithAgent.Configuration;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -30,8 +32,11 @@ public static void Main(string[] args)
// Add services to the container.
builder.Services.AddProblemDetails();

// Add Kernel and required AI services
AddKernelAndServices(builder);
// Load the host configuration.
var hostConfig = new HostConfig(builder.Configuration);

// Add Kernel and required AI services.
AddKernelAndServices(builder, hostConfig);

var app = builder.Build();

Expand All @@ -45,20 +50,22 @@ public static void Main(string[] args)
app.Run();
}

private static void AddKernelAndServices(WebApplicationBuilder builder)
private static void AddKernelAndServices(WebApplicationBuilder builder, HostConfig hostConfig)
{
// Add AzureOpenAI client.
builder.AddAzureOpenAIClient(
"azureOpenAI",
(settings) => settings.Credential = builder.Environment.IsProduction()
? new DefaultAzureCredential()
: new AzureCliCredential()); // Use credentials from Azure CLI for local development.

// Add Kernel.
var kernelBuilder = builder.Services.AddKernel();

// Add AzureOpenAI chat completion service.
kernelBuilder.Services.AddAzureOpenAIChatCompletion("chatModelDeployment");
switch (hostConfig.AIChatService)
{
case AzureOpenAIChatConfig.ConfigSectionName:
{
builder.AddAzureOpenAIServices(hostConfig);
break;
}

default:
throw new NotSupportedException($"AI service '{hostConfig.AIChatService}' is not supported.");
}

// Add chat completion agent.
kernelBuilder.Services.AddTransient<ChatCompletionAgent>((sp) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<ItemGroup>
<ProjectReference Include="..\ChatWithAgent.ApiService\ChatWithAgent.ApiService.csproj" />
<ProjectReference Include="..\ChatWithAgent.Configuration\ChatWithAgent.Configuration.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\ChatWithAgent.Web\ChatWithAgent.Web.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft. All rights reserved.

using ChatWithAgent.Configuration;

namespace ChatWithAgent.AppHost.Extensions;

/// <summary>
/// Distributed application builder extensions.
/// </summary>
internal static class DistributedApplicationBuilderExtensions
{
/// <summary>
/// Adds Azure OpenAI service and OpenAI model(s) to the distributed application.
/// </summary>
/// <param name="builder">The distributed application builder.</param>
/// <param name="config">The host configuration.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{IResourceWithConnectionString}"/>.</returns>
internal static IResourceBuilder<IResourceWithConnectionString> AddAzureOpenAI(this IDistributedApplicationBuilder builder, HostConfig config)
{
if (builder.ExecutionContext.IsPublishMode)
{
// Deploy and provision Azure OpenAI service with AI models
return builder
.AddAzureOpenAI(AzureOpenAIChatConfig.ConnectionStringName)
.AddDeployment(new AzureOpenAIDeployment(
name: config.AzureOpenAIChat.DeploymentName,
modelName: config.AzureOpenAIChat.ModelName,
modelVersion: config.AzureOpenAIChat.ModelVersion)
);
}

// Use an existing Azure OpenAI service via connection string
return builder.AddConnectionString(AzureOpenAIChatConfig.ConnectionStringName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.

using ChatWithAgent.Configuration;

namespace ChatWithAgent.AppHost.Extensions;

/// <summary>
/// Resource builder extensions.
/// </summary>
public static class ResourceBuilderExtensions
{
/// <summary>
/// Adds host configuration as environment variables to the resource.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="config">The host configuration.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithEnvironment<T>(this IResourceBuilder<T> builder, HostConfig config) where T : IResourceWithEnvironment
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(config);

// Add configured AI chat service to the environment variables so that Api Service can access it.
builder.WithEnvironment(nameof(config.AIChatService), config.AIChatService);

switch (config.AIChatService)
{
case AzureOpenAIChatConfig.ConfigSectionName:
{
// Add Azure OpenAI chat model deployment name to environment variables so that Api Service can access it.
builder.WithEnvironment($"{HostConfig.AIServicesSectionName}__{AzureOpenAIChatConfig.ConfigSectionName}__{nameof(config.AzureOpenAIChat.DeploymentName)}", config.AzureOpenAIChat.DeploymentName);
break;
}

default:
throw new NotSupportedException($"AI service '{config.AIChatService}' is not supported.");
}

return builder;
}
}
32 changes: 20 additions & 12 deletions dotnet/samples/Demos/Hosting/Agent/ChatWithAgent.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
// Copyright (c) Microsoft. All rights reserved.

using ChatWithAgent.AppHost.Extensions;
using ChatWithAgent.Configuration;

var builder = DistributedApplication.CreateBuilder(args);

// Deploy and provision Azure OpenAI service with AI models
var azureOpenAI = AddAzureOpenAIDeployments(builder);
// Load host configuration.
var hostConfig = new HostConfig(builder.Configuration);

// Deploy and provision AI Service.
var aiService = AddAIServices(builder, hostConfig);

// Deploy and provision Api Service
// Deploy and provision Api Service with dependencies.
var apiService = builder.AddProject<Projects.ChatWithAgent_ApiService>("apiservice")
.WithReference(azureOpenAI);
.WithReference(aiService)
.WithEnvironment(hostConfig); // Add some host configuration as environment variables so that the Api Service can access them

// Deploy and provision Web Frontend
builder.AddProject<Projects.ChatWithAgent_Web>("webfrontend")
Expand All @@ -17,15 +24,16 @@

builder.Build().Run();

static IResourceBuilder<IResourceWithConnectionString> AddAzureOpenAIDeployments(IDistributedApplicationBuilder appBuilder)
static IResourceBuilder<IResourceWithConnectionString> AddAIServices(IDistributedApplicationBuilder builder, HostConfig config)
{
if (appBuilder.ExecutionContext.IsPublishMode)
switch (config.AIChatService)
{
// Deploy and provision Azure OpenAI service with AI models
return appBuilder.AddAzureOpenAI("azureOpenAI")
.AddDeployment(new AzureOpenAIDeployment("chatModelDeployment", "gpt-4o-mini", "2024-07-18"));
}
case AzureOpenAIChatConfig.ConfigSectionName:
{
return builder.AddAzureOpenAI(config);
}

// Use an existing Azure OpenAI service via connection string
return appBuilder.AddConnectionString("azureOpenAI");
default:
throw new NotSupportedException($"AI service '{config.AIChatService}' is not supported.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,13 @@
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
},
"AIServices": {
"AzureOpenAIChat": {
"DeploymentName": "gpt-4o-mini",
"ModelName": "gpt-4o-mini",
"ModelVersion": "2024-07-18"
}
},
"AIChatService": "AzureOpenAIChat"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.

using System.ComponentModel.DataAnnotations;

namespace ChatWithAgent.Configuration;

/// <summary>
/// Azure OpenAI chat configuration.
/// </summary>
public sealed class AzureOpenAIChatConfig
{
/// <summary>
/// Configuration section name.
/// </summary>
public const string ConfigSectionName = "AzureOpenAIChat";

/// <summary>
/// The name of the connection string of the Azure OpenAI chat service.
/// </summary>
public const string ConnectionStringName = ConfigSectionName;

/// <summary>
/// The name of the chat deployment.
/// </summary>
[Required]
public string DeploymentName { get; set; } = string.Empty;

/// <summary>
/// The name of the chat model.
/// </summary>
public string ModelName { get; set; } = string.Empty;

/// <summary>
/// The chat model version.
/// </summary>
public string ModelVersion { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft. All rights reserved.

using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Configuration;

namespace ChatWithAgent.Configuration;

/// <summary>
/// Helper class for loading host configuration settings.
/// </summary>
public sealed class HostConfig
{
/// <summary>
/// The AI services section name.
/// </summary>
public const string AIServicesSectionName = "AIServices";

private readonly AzureOpenAIChatConfig _azureOpenAIChat = new();

/// <summary>
/// Initializes a new instance of the <see cref="HostConfig"/> class.
/// </summary>
/// <param name="configurationManager">The configuration manager.</param>
public HostConfig(ConfigurationManager configurationManager)
{
configurationManager
.GetSection($"{AIServicesSectionName}:{AzureOpenAIChatConfig.ConfigSectionName}")
.Bind(this._azureOpenAIChat);
configurationManager
.Bind(this);
}

/// <summary>
/// The AI chat service to use.
/// </summary>
[Required]
public string AIChatService { get; set; } = string.Empty;

/// <summary>
/// The Azure OpenAI chat configuration.
/// </summary>
public AzureOpenAIChatConfig AzureOpenAIChat => this._azureOpenAIChat;
}
Loading