diff --git a/dotnet/src/SemanticKernel.Abstractions/Contents/KernelContent.cs b/dotnet/src/SemanticKernel.Abstractions/Contents/KernelContent.cs
index 183542021705..8dbcc00eb25d 100644
--- a/dotnet/src/SemanticKernel.Abstractions/Contents/KernelContent.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/Contents/KernelContent.cs
@@ -9,13 +9,14 @@ namespace Microsoft.SemanticKernel;
///
/// Base class for all AI non-streaming results
///
-[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
[JsonDerivedType(typeof(TextContent), typeDiscriminator: nameof(TextContent))]
[JsonDerivedType(typeof(ImageContent), typeDiscriminator: nameof(ImageContent))]
[JsonDerivedType(typeof(FunctionCallContent), typeDiscriminator: nameof(FunctionCallContent))]
[JsonDerivedType(typeof(FunctionResultContent), typeDiscriminator: nameof(FunctionResultContent))]
[JsonDerivedType(typeof(BinaryContent), typeDiscriminator: nameof(BinaryContent))]
[JsonDerivedType(typeof(AudioContent), typeDiscriminator: nameof(AudioContent))]
+[JsonDerivedType(typeof(ChatMessageContent), typeDiscriminator: nameof(ChatMessageContent))]
#pragma warning disable SKEXP0110
[JsonDerivedType(typeof(AnnotationContent), typeDiscriminator: nameof(AnnotationContent))]
[JsonDerivedType(typeof(FileReferenceContent), typeDiscriminator: nameof(FileReferenceContent))]
diff --git a/dotnet/src/SemanticKernel.UnitTests/Contents/ChatMessageContentTests.cs b/dotnet/src/SemanticKernel.UnitTests/Contents/ChatMessageContentTests.cs
index 9fe258d8bba8..cd753a15e201 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Contents/ChatMessageContentTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Contents/ChatMessageContentTests.cs
@@ -202,7 +202,7 @@ public void ItCanBeSerializeAndDeserialized()
new FunctionCallContent("function-name", "plugin-name", "function-id", new KernelArguments { ["parameter"] = "argument" }),
new FunctionResultContent(new FunctionCallContent("function-name", "plugin-name", "function-id"), "function-result"),
new FileReferenceContent(fileId: "file-id-1") { ModelId = "model-7", Metadata = new Dictionary() { ["metadata-key-7"] = "metadata-value-7" } },
- new AnnotationContent("quote-8") { ModelId = "model-8", FileId = "file-id-2", StartIndex = 2, EndIndex = 24, Metadata = new Dictionary() { ["metadata-key-8"] = "metadata-value-8" } }
+ new AnnotationContent("quote-8") { ModelId = "model-8", FileId = "file-id-2", StartIndex = 2, EndIndex = 24, Metadata = new Dictionary() { ["metadata-key-8"] = "metadata-value-8" } },
];
// Act
@@ -320,4 +320,92 @@ public void ItCanBeSerializeAndDeserialized()
Assert.Single(annotationContent.Metadata);
Assert.Equal("metadata-value-8", annotationContent.Metadata["metadata-key-8"]?.ToString());
}
+
+ [Fact]
+ public void ItCanBePolymorphicallySerializedAndDeserializedAsKernelContentType()
+ {
+ // Arrange
+ KernelContent sut = new ChatMessageContent(AuthorRole.User, "test-content", "test-model", metadata: new Dictionary()
+ {
+ ["test-metadata-key"] = "test-metadata-value"
+ })
+ {
+ MimeType = "test-mime-type"
+ };
+
+ // Act
+ var json = JsonSerializer.Serialize(sut);
+
+ var deserialized = JsonSerializer.Deserialize(json)!;
+
+ // Assert
+ Assert.IsType(deserialized);
+ Assert.Equal("test-content", ((ChatMessageContent)deserialized).Content);
+ Assert.Equal("test-model", deserialized.ModelId);
+ Assert.Equal("test-mime-type", deserialized.MimeType);
+ Assert.NotNull(deserialized.Metadata);
+ Assert.Single(deserialized.Metadata);
+ Assert.Equal("test-metadata-value", deserialized.Metadata["test-metadata-key"]?.ToString());
+ }
+
+ [Fact]
+ public void UnknownDerivativeCanBePolymorphicallySerializedAndDeserializedAsChatMessageContentType()
+ {
+ // Arrange
+ KernelContent sut = new UnknownExternalChatMessageContent(AuthorRole.User, "test-content")
+ {
+ MimeType = "test-mime-type",
+ };
+
+ // Act
+ var json = JsonSerializer.Serialize(sut);
+
+ var deserialized = JsonSerializer.Deserialize(json)!;
+
+ // Assert
+ Assert.IsType(deserialized);
+ Assert.Equal("test-content", ((ChatMessageContent)deserialized).Content);
+ Assert.Equal("test-mime-type", deserialized.MimeType);
+ }
+
+ [Fact]
+ public void ItCanBeSerializeAndDeserializedWithFunctionResultOfChatMessageType()
+ {
+ // Arrange
+ ChatMessageContentItemCollection items = [
+ new FunctionResultContent(new FunctionCallContent("function-name-1", "plugin-name-1", "function-id-1"), new ChatMessageContent(AuthorRole.User, "test-content-1")),
+ new FunctionResultContent(new FunctionCallContent("function-name-2", "plugin-name-2", "function-id-2"), new UnknownExternalChatMessageContent(AuthorRole.Assistant, "test-content-2")),
+ ];
+
+ // Act
+ var chatMessageJson = JsonSerializer.Serialize(new ChatMessageContent(AuthorRole.User, items: items, "message-model"));
+
+ var deserializedMessage = JsonSerializer.Deserialize(chatMessageJson)!;
+
+ // Assert
+ var functionResultContentWithResultOfChatMessageContentType = deserializedMessage.Items[0] as FunctionResultContent;
+ Assert.NotNull(functionResultContentWithResultOfChatMessageContentType);
+ Assert.Equal("function-name-1", functionResultContentWithResultOfChatMessageContentType.FunctionName);
+ Assert.Equal("function-id-1", functionResultContentWithResultOfChatMessageContentType.CallId);
+ Assert.Equal("plugin-name-1", functionResultContentWithResultOfChatMessageContentType.PluginName);
+ var chatMessageContent = Assert.IsType(functionResultContentWithResultOfChatMessageContentType.Result);
+ Assert.Equal("user", chatMessageContent.GetProperty("Role").GetProperty("Label").GetString());
+ Assert.Equal("test-content-1", chatMessageContent.GetProperty("Items")[0].GetProperty("Text").GetString());
+
+ var functionResultContentWithResultOfUnknownChatMessageContentType = deserializedMessage.Items[1] as FunctionResultContent;
+ Assert.NotNull(functionResultContentWithResultOfUnknownChatMessageContentType);
+ Assert.Equal("function-name-2", functionResultContentWithResultOfUnknownChatMessageContentType.FunctionName);
+ Assert.Equal("function-id-2", functionResultContentWithResultOfUnknownChatMessageContentType.CallId);
+ Assert.Equal("plugin-name-2", functionResultContentWithResultOfUnknownChatMessageContentType.PluginName);
+ var unknownChatMessageContent = Assert.IsType(functionResultContentWithResultOfUnknownChatMessageContentType.Result);
+ Assert.Equal("assistant", unknownChatMessageContent.GetProperty("Role").GetProperty("Label").GetString());
+ Assert.Equal("test-content-2", unknownChatMessageContent.GetProperty("Items")[0].GetProperty("Text").GetString());
+ }
+
+ private sealed class UnknownExternalChatMessageContent : ChatMessageContent
+ {
+ public UnknownExternalChatMessageContent(AuthorRole role, string? content) : base(role, content)
+ {
+ }
+ }
}