Skip to content

Commit 8f6b731

Browse files
authored
Add a .NET ChatApp example for AI configuration (#1055)
This PR adds a .NET 8.0 console example illustrating how to load AI model settings from Azure App Configuration, refresh them at runtime, and drive an Azure OpenAI chat loop.
1 parent 5ac0054 commit 8f6b731

6 files changed

Lines changed: 166 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.14.36119.2 d17.14
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatApp", "ChatApp\ChatApp.csproj", "{E7CB6AEC-C73D-4C9F-9A28-D728B47CF9EE}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{E7CB6AEC-C73D-4C9F-9A28-D728B47CF9EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{E7CB6AEC-C73D-4C9F-9A28-D728B47CF9EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{E7CB6AEC-C73D-4C9F-9A28-D728B47CF9EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{E7CB6AEC-C73D-4C9F-9A28-D728B47CF9EE}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {4228A6BE-DF14-40A7-8447-85FAF6249828}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
12+
<PackageReference Include="Azure.Identity" Version="1.14.0" />
13+
<PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="8.2.0" />
14+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.5" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using Microsoft.Extensions.Configuration;
5+
6+
namespace ChatApp
7+
{
8+
internal class Message
9+
{
10+
[ConfigurationKeyName("role")]
11+
public required string Role { get; set; }
12+
13+
[ConfigurationKeyName("content")]
14+
public string? Content { get; set; }
15+
}
16+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using Microsoft.Extensions.Configuration;
5+
6+
namespace ChatApp
7+
{
8+
internal class ModelConfiguration
9+
{
10+
[ConfigurationKeyName("model")]
11+
public string? Model { get; set; }
12+
13+
[ConfigurationKeyName("messages")]
14+
public List<Message>? Messages { get; set; }
15+
16+
[ConfigurationKeyName("max_tokens")]
17+
public int MaxTokens { get; set; }
18+
19+
[ConfigurationKeyName("temperature")]
20+
public float Temperature { get; set; }
21+
22+
[ConfigurationKeyName("top_p")]
23+
public float TopP { get; set; }
24+
}
25+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using Azure.AI.OpenAI;
5+
using Azure.Core;
6+
using Azure.Identity;
7+
using ChatApp;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
10+
using OpenAI.Chat;
11+
12+
TokenCredential credential = new DefaultAzureCredential();
13+
IConfigurationRefresher refresher = null;
14+
15+
// Load configuration from Azure App Configuration
16+
IConfiguration configuration = new ConfigurationBuilder()
17+
.AddAzureAppConfiguration(options =>
18+
{
19+
Uri endpoint = new(Environment.GetEnvironmentVariable("AZURE_APPCONFIG_ENDPOINT") ??
20+
throw new InvalidOperationException("The environment variable 'AZURE_APPCONFIG_ENDPOINT' is not set or is empty."));
21+
options.Connect(endpoint, credential)
22+
// Load all keys that start with "ChatApp:" and have no label.
23+
.Select("ChatApp:*")
24+
// Reload configuration if any selected key-values have changed.
25+
// Use the default refresh interval of 30 seconds. It can be overridden via refreshOptions.SetRefreshInterval.
26+
.ConfigureRefresh(refreshOptions =>
27+
{
28+
refreshOptions.RegisterAll();
29+
});
30+
31+
refresher = options.GetRefresher();
32+
})
33+
.Build();
34+
35+
// Retrieve the OpenAI connection information from the configuration
36+
Uri openaiEndpoint = new (configuration["ChatApp:AzureOpenAI:Endpoint"]);
37+
string deploymentName = configuration["ChatApp:AzureOpenAI:DeploymentName"];
38+
39+
// Create a chat client
40+
AzureOpenAIClient azureClient = new(openaiEndpoint, credential);
41+
ChatClient chatClient = azureClient.GetChatClient(deploymentName);
42+
43+
while (true)
44+
{
45+
// Refresh the configuration from Azure App Configuration
46+
await refresher.TryRefreshAsync();
47+
48+
// Configure chat completion with AI configuration
49+
var modelConfiguration = configuration.GetSection("ChatApp:Model").Get<ModelConfiguration>();
50+
var requestOptions = new ChatCompletionOptions()
51+
{
52+
MaxOutputTokenCount = modelConfiguration.MaxTokens,
53+
Temperature = modelConfiguration.Temperature,
54+
TopP = modelConfiguration.TopP
55+
};
56+
57+
foreach (var message in modelConfiguration.Messages)
58+
{
59+
Console.WriteLine($"{message.Role}: {message.Content}");
60+
}
61+
62+
// Get chat response from AI
63+
var response = await chatClient.CompleteChatAsync(GetChatMessages(modelConfiguration), requestOptions);
64+
System.Console.WriteLine($"AI response: {response.Value.Content[0].Text}");
65+
66+
Console.WriteLine("Press Enter to continue...");
67+
Console.ReadLine();
68+
}
69+
70+
static IEnumerable<ChatMessage> GetChatMessages(ModelConfiguration modelConfiguration)
71+
{
72+
return modelConfiguration.Messages.Select<Message, ChatMessage>(message => message.Role switch
73+
{
74+
"system" => ChatMessage.CreateSystemMessage(message.Content),
75+
"user" => ChatMessage.CreateUserMessage(message.Content),
76+
"assistant" => ChatMessage.CreateAssistantMessage(message.Content),
77+
_ => throw new ArgumentException($"Unknown role: {message.Role}", nameof(message.Role))
78+
});
79+
}

examples/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## .NET Samples
44

5+
### [AI Chat App](./DotNetCore/ChatApp)
6+
7+
This example showcases a .NET console application that retrieves chat responses from Azure OpenAI. It demonstrates how to configure chat completion using AI Configuration from Azure App Configuration, enabling rapid prompt iteration and frequent tuning of model parameters—without requiring application restarts, rebuilds, or redeployments.
8+
59
### [Azure Functions (Isolated worker model)](./DotNetCore/AzureFunctions/FunctionAppIsolated)
610

711
This example showcases a .NET isolated worker model Function App, which operates out-of-process in Azure Functions. It demonstrates how to enable dynamic configuration and utilize feature flags from App Configuration. Additionally, it illustrates how to leverage the App Configuration references feature in Azure Functions to manage trigger parameters within App Configuration.

0 commit comments

Comments
 (0)