-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
.Net: First step to make ChatMessageContent more serialization-friend…
…ly (#5131) ### Motivation and Context Today, it is not possible to deserialize ChatMessageContent that has at least one content item in the Items collection. The reason for this is that the content items in the Items collection are referenced polymorphically through the KernelContent abstract class. As a result, the deserialization process fails because it cannot create an instance of the abstract class. To solve the problem, the serialization process should save type information when serializing the Items collection so that the deserialization process can use this information to find the type that was serialized and create its instance. Therefore, this PR leverages [type discriminators](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-8-0) to save the type information and use it during deserialization. ### Description This PR registers the type discriminator for the KernelContent class and whitelists its subclasses to participate in polymorphic deserialization. On top of that, a few content types are modified to be serializable/deserializable. ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 --------- Co-authored-by: Dmytro Struk <[email protected]>
- Loading branch information
1 parent
8b16af6
commit f40ea59
Showing
9 changed files
with
278 additions
and
19 deletions.
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
dotnet/samples/KernelSyntaxExamples/Example85_ChatHistorySerialization.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System; | ||
using System.Linq; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization.Metadata; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.ChatCompletion; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace Examples; | ||
|
||
public class Example85_ChatHistorySerialization : BaseTest | ||
{ | ||
/// <summary> | ||
/// Demonstrates how to serialize and deserialize <see cref="ChatHistory"/> class | ||
/// with <see cref="ChatMessageContent"/> having SK various content types as items. | ||
/// </summary> | ||
[Fact] | ||
public void SerializeChatHistoryWithSKContentTypes() | ||
{ | ||
var data = new[] { 1, 2, 3 }; | ||
|
||
var message = new ChatMessageContent(AuthorRole.User, "Describe the factors contributing to climate change."); | ||
message.Items = new ChatMessageContentItemCollection | ||
{ | ||
new TextContent("Discuss the potential long-term consequences for the Earth's ecosystem as well."), | ||
new ImageContent(new Uri("https://fake-random-test-host:123")), | ||
new BinaryContent(new BinaryData(data)), | ||
#pragma warning disable SKEXP0005 | ||
new AudioContent(new BinaryData(data)) | ||
#pragma warning restore SKEXP0005 | ||
}; | ||
|
||
var chatHistory = new ChatHistory(new[] { message }); | ||
|
||
var chatHistoryJson = JsonSerializer.Serialize(chatHistory); | ||
|
||
var deserializedHistory = JsonSerializer.Deserialize<ChatHistory>(chatHistoryJson); | ||
|
||
var deserializedMessage = deserializedHistory!.Single(); | ||
|
||
WriteLine($"Content: {deserializedMessage.Content}"); | ||
WriteLine($"Role: {deserializedMessage.Role.Label}"); | ||
|
||
WriteLine($"Text content: {(deserializedMessage.Items![0]! as TextContent)!.Text}"); | ||
|
||
WriteLine($"Image content: {(deserializedMessage.Items![1]! as ImageContent)!.Uri}"); | ||
|
||
WriteLine($"Binary content: {(deserializedMessage.Items![2]! as BinaryContent)!.Content}"); | ||
|
||
WriteLine($"Audio content: {(deserializedMessage.Items![3]! as AudioContent)!.Data}"); | ||
} | ||
|
||
/// <summary> | ||
/// Shows how to serialize and deserialize <see cref="ChatHistory"/> class with <see cref="ChatMessageContent"/> having custom content type as item. | ||
/// </summary> | ||
[Fact] | ||
public void SerializeChatWithHistoryWithCustomContentType() | ||
{ | ||
var message = new ChatMessageContent(AuthorRole.User, "Describe the factors contributing to climate change."); | ||
message.Items = new ChatMessageContentItemCollection | ||
{ | ||
new TextContent("Discuss the potential long-term consequences for the Earth's ecosystem as well."), | ||
new CustomContent("Some custom content"), | ||
}; | ||
|
||
var chatHistory = new ChatHistory(new[] { message }); | ||
|
||
// The custom resolver should be used to serialize and deserialize the chat history with custom . | ||
var options = new JsonSerializerOptions | ||
{ | ||
TypeInfoResolver = new CustomResolver() | ||
}; | ||
|
||
var chatHistoryJson = JsonSerializer.Serialize(chatHistory, options); | ||
|
||
var deserializedHistory = JsonSerializer.Deserialize<ChatHistory>(chatHistoryJson, options); | ||
|
||
var deserializedMessage = deserializedHistory!.Single(); | ||
|
||
WriteLine($"Content: {deserializedMessage.Content}"); | ||
WriteLine($"Role: {deserializedMessage.Role.Label}"); | ||
|
||
WriteLine($"Text content: {(deserializedMessage.Items![0]! as TextContent)!.Text}"); | ||
|
||
WriteLine($"Custom content: {(deserializedMessage.Items![1]! as CustomContent)!.Content}"); | ||
} | ||
|
||
public Example85_ChatHistorySerialization(ITestOutputHelper output) : base(output) | ||
{ | ||
} | ||
|
||
private sealed class CustomContent : KernelContent | ||
{ | ||
public CustomContent(string content) : base(content) | ||
{ | ||
Content = content; | ||
} | ||
|
||
public string Content { get; } | ||
} | ||
|
||
/// <summary> | ||
/// The TypeResolver is used to serialize and deserialize custom content types polymorphically. | ||
/// For more details, refer to the <see href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-8-0"/> article. | ||
/// </summary> | ||
private sealed class CustomResolver : DefaultJsonTypeInfoResolver | ||
{ | ||
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) | ||
{ | ||
var jsonTypeInfo = base.GetTypeInfo(type, options); | ||
|
||
if (jsonTypeInfo.Type != typeof(KernelContent)) | ||
{ | ||
return jsonTypeInfo; | ||
} | ||
|
||
// It's possible to completely override the polymorphic configuration specified in the KernelContent class | ||
// by using the '=' assignment operator instead of the ??= compound assignment one in the line below. | ||
jsonTypeInfo.PolymorphismOptions ??= new JsonPolymorphismOptions(); | ||
|
||
// Add custom content type to the list of derived types declared on KernelContent class. | ||
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(typeof(CustomContent), "customContent")); | ||
|
||
// Override type discriminator declared on KernelContent class as "$type", if needed. | ||
jsonTypeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName = "name"; | ||
|
||
return jsonTypeInfo; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.