Skip to content

Commit 565e95a

Browse files
committed
2024-12-11
1 parent 435d412 commit 565e95a

9 files changed

+329
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "ConsoleApp\ConsoleApp.csproj", "{F6CC1C3C-42BF-42CD-A39C-6F5A5A2BA01F}"
4+
EndProject
5+
Global
6+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7+
Debug|Any CPU = Debug|Any CPU
8+
Release|Any CPU = Release|Any CPU
9+
EndGlobalSection
10+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
11+
{F6CC1C3C-42BF-42CD-A39C-6F5A5A2BA01F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12+
{F6CC1C3C-42BF-42CD-A39C-6F5A5A2BA01F}.Debug|Any CPU.Build.0 = Debug|Any CPU
13+
{F6CC1C3C-42BF-42CD-A39C-6F5A5A2BA01F}.Release|Any CPU.ActiveCfg = Release|Any CPU
14+
{F6CC1C3C-42BF-42CD-A39C-6F5A5A2BA01F}.Release|Any CPU.Build.0 = Release|Any CPU
15+
EndGlobalSection
16+
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace ConsoleApp;
2+
3+
public sealed class AzureOpenAIConfig
4+
{
5+
public required string Endpoint { get; init; }
6+
7+
public string? APIKey { get; init; }
8+
9+
public required string ChatCompletionDeployment { get; init; }
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.ComponentModel;
2+
using System.Text.Json;
3+
using Microsoft.Extensions.AI;
4+
using Microsoft.SemanticKernel;
5+
6+
using ChatResponseFormat = OpenAI.Chat.ChatResponseFormat;
7+
8+
namespace ConsoleApp;
9+
10+
public sealed class CalendarEvent
11+
{
12+
[Description("Name of the event")]
13+
public required string Name { get; init; }
14+
15+
[Description("Day of the event")]
16+
public required string Day { get; init; }
17+
18+
[Description("List of participants of the event")]
19+
public required string[] Participants { get; init; }
20+
21+
public static ChatResponseFormat JsonResponseSchema(JsonSerializerOptions? jsonSerializerOptions = default)
22+
{
23+
var inferenceOptions = new AIJsonSchemaCreateOptions
24+
{
25+
IncludeSchemaKeyword = false,
26+
DisallowAdditionalProperties = true,
27+
};
28+
29+
var jsonElement = AIJsonUtilities.CreateJsonSchema(
30+
typeof(CalendarEvent),
31+
description: "Calendar event result",
32+
serializerOptions: jsonSerializerOptions,
33+
inferenceOptions: inferenceOptions);
34+
35+
var kernelJsonSchema = KernelJsonSchema.Parse(jsonElement.GetRawText());
36+
var jsonSchemaData = BinaryData.FromObjectAsJson(kernelJsonSchema, jsonSerializerOptions);
37+
38+
return ChatResponseFormat.CreateJsonSchemaFormat(
39+
nameof(CalendarEvent).ToLowerInvariant(),
40+
jsonSchemaData,
41+
jsonSchemaIsStrict: true);
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<UserSecretsId>D0CD1C0D-AFB5-47B4-A2A8-75299A3571EF</UserSecretsId>
9+
<NoWarn>SKEXP0001,SKEXP0010,SKEXP0080,SKEXP0110, </NoWarn>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Content Include="appsettings.json">
14+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
15+
</Content>
16+
<Content Include="appsettings.Development.json">
17+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
18+
</Content>
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<PackageReference Include="Azure.Identity" Version="1.13.1"/>
23+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0"/>
24+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0"/>
25+
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0"/>
26+
<PackageReference Include="Microsoft.SemanticKernel" Version="1.32.0" />
27+
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" Version="1.32.0-alpha" />
28+
<PackageReference Include="Microsoft.SemanticKernel.Connectors.AzureOpenAI" Version="1.32.0" />
29+
<PackageReference Include="Microsoft.SemanticKernel.Process.Core" Version="1.32.0-alpha" />
30+
<PackageReference Include="Microsoft.SemanticKernel.Process.LocalRuntime" Version="1.32.0-alpha" />
31+
</ItemGroup>
32+
33+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+

2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using Azure.Identity;
5+
using ConsoleApp;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Hosting;
9+
using Microsoft.SemanticKernel;
10+
using Microsoft.SemanticKernel.ChatCompletion;
11+
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
12+
13+
IHost? host = default;
14+
15+
try
16+
{
17+
host = Host.CreateDefaultBuilder(args)
18+
.ConfigureAppConfiguration((builderContext, builder) =>
19+
{
20+
builder.AddJsonFile("appsettings.json", false);
21+
builder.AddJsonFile($"appsettings.{builderContext.HostingEnvironment.EnvironmentName}.json", true);
22+
23+
if (builderContext.HostingEnvironment.IsDevelopment()) builder.AddUserSecrets<Program>();
24+
25+
builder.AddEnvironmentVariables();
26+
})
27+
.ConfigureServices((builderContext, services) =>
28+
{
29+
var openAIConfig = builderContext.Configuration
30+
.GetSection(nameof(AzureOpenAIConfig))
31+
.Get<AzureOpenAIConfig>()
32+
?? throw new InvalidOperationException("Azure OpenAI configuration required");
33+
34+
services.AddHttpClient(nameof(AzureOpenAIConfig));
35+
36+
services.AddTransient(provider =>
37+
{
38+
var factory = provider.GetRequiredService<IHttpClientFactory>();
39+
var httpClient = factory.CreateClient(nameof(AzureOpenAIConfig));
40+
41+
var builder = Kernel.CreateBuilder();
42+
if (!string.IsNullOrEmpty(openAIConfig.APIKey))
43+
{
44+
builder.AddAzureOpenAIChatCompletion(
45+
deploymentName: openAIConfig.ChatCompletionDeployment,
46+
endpoint: openAIConfig.Endpoint,
47+
openAIConfig.APIKey,
48+
httpClient: httpClient);
49+
}
50+
else
51+
{
52+
builder.AddAzureOpenAIChatCompletion(
53+
deploymentName: openAIConfig.ChatCompletionDeployment,
54+
endpoint: openAIConfig.Endpoint,
55+
new DefaultAzureCredential(),
56+
httpClient: httpClient);
57+
}
58+
59+
return builder.Build();
60+
});
61+
})
62+
.Build();
63+
64+
await host.StartAsync();
65+
66+
var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
67+
await using (var scope = host.Services.CreateAsyncScope())
68+
{
69+
var kernel = scope.ServiceProvider.GetRequiredService<Kernel>();
70+
var chatCompletionService = kernel.Services.GetRequiredService<IChatCompletionService>();
71+
72+
var history = new ChatHistory();
73+
history.AddSystemMessage("Extract the event information.");
74+
history.AddUserMessage("Alice and Bob are going to a science fair on Friday.");
75+
76+
var jsonSerializerOptions = new JsonSerializerOptions(JsonSerializerOptions.Default)
77+
{
78+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
79+
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
80+
};
81+
var responseFormat = CalendarEvent.JsonResponseSchema(jsonSerializerOptions);
82+
83+
var responses = await chatCompletionService.GetChatMessageContentAsync(
84+
history,
85+
new AzureOpenAIPromptExecutionSettings
86+
{
87+
ResponseFormat = responseFormat
88+
},
89+
cancellationToken: lifetime.ApplicationStopping);
90+
91+
var content = responses.ToString();
92+
if (!string.IsNullOrEmpty(content))
93+
{
94+
var result = JsonSerializer.Deserialize<CalendarEvent>(content, jsonSerializerOptions);
95+
Console.WriteLine($"{result?.Name}, {result?.Day}, {string.Join(", ", result?.Participants ?? [])}");
96+
// Prints
97+
// Science Fair, Friday, Alice, Bob
98+
}
99+
}
100+
101+
lifetime.StopApplication();
102+
await host.WaitForShutdownAsync(lifetime.ApplicationStopping);
103+
}
104+
catch (Exception ex)
105+
{
106+
Console.WriteLine($"Host terminated unexpectedly! \n{ex}");
107+
}
108+
finally
109+
{
110+
host?.Dispose();
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "https://json.schemastore.org/launchsettings.json",
3+
"profiles": {
4+
"Console": {
5+
"commandName": "Project",
6+
"environmentVariables": {
7+
"DOTNET_ENVIRONMENT": "Development"
8+
}
9+
}
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Debug",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information"
5+
}
6+
},
7+
"AzureOpenAIConfig": {
8+
"Endpoint": "https://resource-name.openai.azure.com",
9+
"APIKey": "azure open ai api key",
10+
"ChatCompletionDeployment": "gpt-4o"
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
title: OpenAI chat completion with Json output format
3+
description: Auzre/OpenAI chat completion supports json format with schema.
4+
date: 2024-12-11
5+
tags: [ ".NET", "AI", "Semantic Kernel" ]
6+
---
7+
8+
# {{title}}
9+
10+
*{{date | readableDate("LLLL yyyy")}}*
11+
12+
I can't recall how many times I've tried to convince an LLM to return JSON so that I could perform API calls based on natural language inputs from users. Recently, I discovered that this functionality is natively supported by the [Semantic Kernel](https://github.com/microsoft/semantic-kernel) and Microsoft AI Extension Library. It is officially documented by the OpenAI API [here](https://platform.openai.com/docs/guides/structured-outputs). Note that this feature is only available in the latest large language models from GPT-4o/o1 and later. If you are using Azure OpenAI, ensure you have the supported versions when deploying models.
13+
14+
## Chat completion
15+
[Semantic Kernel](https://github.com/microsoft/semantic-kernel) supports JSON output formatting in the ResponseFormat property from PromptExecutionSettings, as shown in the code below:
16+
17+
```csharp
18+
// Configure Azure/OpenAI and semantic kernel first.
19+
20+
var chatCompletionService = kernel.Services.GetRequiredService<IChatCompletionService>();
21+
22+
var history = new ChatHistory();
23+
history.AddSystemMessage("Extract the event information.");
24+
history.AddUserMessage("Alice and Bob are going to a science fair on Friday.");
25+
26+
var jsonSerializerOptions = new JsonSerializerOptions(JsonSerializerOptions.Default)
27+
{
28+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
29+
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
30+
};
31+
var responseFormat = CalendarEvent.JsonResponseSchema(jsonSerializerOptions);
32+
33+
var response = await chatCompletionService.GetChatMessageContentAsync(
34+
history,
35+
new AzureOpenAIPromptExecutionSettings
36+
{
37+
ResponseFormat = responseFormat
38+
});
39+
// Json result
40+
var result = JsonSerializer.Deserialize<CalendarEvent>(response.ToString(), jsonSerializerOptions);
41+
42+
```
43+
44+
## Generate Json schema from types
45+
JSON schema can be automatically generated using Microsoft.Extensions.AI.AIJsonUtilities, which is referenced from [Semantic Kernel](https://github.com/microsoft/semantic-kernel).
46+
47+
```csharp
48+
public sealed class CalendarEvent
49+
{
50+
[Description("Name of the event")]
51+
public required string Name { get; init; }
52+
53+
[Description("Day of the event")]
54+
public required string Day { get; init; }
55+
56+
[Description("List of participants of the event")]
57+
public required string[] Participants { get; init; }
58+
59+
public static ChatResponseFormat JsonResponseSchema(JsonSerializerOptions? jsonSerializerOptions = default)
60+
{
61+
var inferenceOptions = new AIJsonSchemaCreateOptions
62+
{
63+
IncludeSchemaKeyword = false,
64+
DisallowAdditionalProperties = true,
65+
};
66+
67+
var jsonElement = AIJsonUtilities.CreateJsonSchema(
68+
typeof(CalendarEvent),
69+
description: "Calendar event result",
70+
serializerOptions: jsonSerializerOptions,
71+
inferenceOptions: inferenceOptions);
72+
73+
var kernelJsonSchema = KernelJsonSchema.Parse(jsonElement.GetRawText());
74+
var jsonSchemaData = BinaryData.FromObjectAsJson(kernelJsonSchema, jsonSerializerOptions);
75+
76+
return ChatResponseFormat.CreateJsonSchemaFormat(
77+
nameof(CalendarEvent).ToLowerInvariant(),
78+
jsonSchemaData,
79+
jsonSchemaIsStrict: true);
80+
}
81+
}
82+
```
83+
84+
[Sample code here](https://github.com/StormHub/stormhub/tree/main/resources/2024-12-19/ConsoleApp)
85+

0 commit comments

Comments
 (0)