mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
.Net: fix: fall back to ToString() when logging function results with unregistered types (#13884)
Fixes #13681 ## Problem When a `KernelFunction` returns a value whose runtime type is not registered in `AbstractionsJsonContext` (e.g. `Microsoft.Extensions.AI.TextContent` returned by an MCP tool via `.AsKernelFunction()`), the function result logging in AOT/source-generation mode throws `NotSupportedException` during JSON serialization. The exception is caught but produces an unhelpful log entry: ``` Function SomePlugin-SomeTool result: Failed to log function result value System.NotSupportedException: JsonTypeInfo metadata for type 'Microsoft.Extensions.AI.TextContent' was not provided by TypeInfoResolver of type 'Microsoft.SemanticKernel.AbstractionsJsonContext'. ``` ## Solution Add a `ToString()` fallback in the `NotSupportedException` catch block of `LogFunctionResultValueInternal`. When JSON serialization fails because the runtime type is absent from the source-generated context, the logger now calls `resultValue?.Value?.ToString()` to produce useful output instead of the generic error message. ## Testing - Added `ItShouldFallBackToToStringWhenJsonSerializationIsNotSupported` unit test that uses a restricted source-generated `JsonSerializerContext` (containing only `object` and `IDictionary<string, object?>`) to simulate the AOT scenario, verifying that `ToString()` output appears in the log instead of the failure message. - All existing `ItShouldLogFunctionResultOfAnyType` test cases are unchanged. --------- Co-authored-by: octo-patch <[email protected]> Co-authored-by: SergeyMenshykh <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: SergeyMenshykh <[email protected]> Co-authored-by: Copilot <[email protected]>
This commit is contained in:
co-authored by
octo-patch
SergeyMenshykh
Copilot
SergeyMenshykh
Copilot
parent
6e1ab9dee5
commit
006a5d9a9b
@@ -161,7 +161,22 @@ internal static partial class KernelFunctionLogMessages
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
s_logFunctionResultValue(logger, pluginName, functionName, "Failed to log function result value", ex);
|
||||
// Fall back to ToString() when JSON serialization isn't supported for this type
|
||||
// (e.g. Microsoft.Extensions.AI.TextContent is not registered in AbstractionsJsonContext)
|
||||
try
|
||||
{
|
||||
var toStringValue = resultValue?.Value?.ToString() ?? string.Empty;
|
||||
s_logFunctionResultValue(logger, pluginName, functionName, toStringValue, null);
|
||||
}
|
||||
catch (Exception toStringEx)
|
||||
{
|
||||
s_logFunctionResultValue(
|
||||
logger,
|
||||
pluginName,
|
||||
functionName,
|
||||
"Failed to log function result value",
|
||||
new AggregateException(ex, toStringEx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.SemanticKernel;
|
||||
@@ -10,7 +11,7 @@ using Xunit;
|
||||
|
||||
namespace SemanticKernel.UnitTests.Functions;
|
||||
|
||||
public class KernelFunctionLogMessagesTests
|
||||
public partial class KernelFunctionLogMessagesTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(string))]
|
||||
@@ -48,9 +49,46 @@ public class KernelFunctionLogMessagesTests
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItShouldFallBackToToStringWhenJsonSerializationIsNotSupported()
|
||||
{
|
||||
// Arrange
|
||||
var logger = new Mock<ILogger>();
|
||||
logger.Setup(l => l.IsEnabled(It.IsAny<LogLevel>())).Returns(true);
|
||||
|
||||
// TypeNotInJsonContext cannot be cast to string and is not registered in the restricted JSON context
|
||||
var unserializableValue = new TypeNotInJsonContext();
|
||||
var functionResult = new FunctionResult(KernelFunctionFactory.CreateFromMethod(() => { }), unserializableValue);
|
||||
|
||||
// Use a restricted JsonSerializerOptions that knows about object but not TypeNotInJsonContext,
|
||||
// simulating the AOT scenario where AbstractionsJsonContext is used and an unregistered
|
||||
// MEAI type (e.g. Microsoft.Extensions.AI.TextContent) is returned from an MCP tool.
|
||||
var restrictedOptions = RestrictedJsonContext.Default.Options;
|
||||
|
||||
// Act
|
||||
logger.Object.LogFunctionResultValue("p1", "f1", functionResult, restrictedOptions);
|
||||
|
||||
// Assert - ToString() fallback should have been used, not the error message
|
||||
logger.Verify(l => l.Log(
|
||||
LogLevel.Trace,
|
||||
0,
|
||||
It.Is<It.IsAnyType>((o, _) => o.ToString() == "Function p1-f1 result: TypeNotInJsonContext()"),
|
||||
null,
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()));
|
||||
}
|
||||
|
||||
private sealed class User
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
|
||||
private sealed class TypeNotInJsonContext
|
||||
{
|
||||
public override string ToString() => "TypeNotInJsonContext()";
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(IDictionary<string, object?>))]
|
||||
[JsonSerializable(typeof(object))]
|
||||
private sealed partial class RestrictedJsonContext : JsonSerializerContext { }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user