Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,17 @@ private static void LogFunctionResultValueInternal(this ILogger logger, string?
}
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
{
s_logFunctionResultValue(logger, pluginName, functionName, "Failed to log function result value", ex);
Comment thread
SergeyMenshykh marked this conversation as resolved.
Outdated
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -48,9 +49,46 @@ public void ItShouldLogFunctionResultOfAnyType(Type resultType)
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
// MEAItype (e.g. Microsoft.Extensions.AI.TextContent) is returned from an MCP tool.
Comment thread
SergeyMenshykh marked this conversation as resolved.
Outdated
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 { }
Comment thread
SergeyMenshykh marked this conversation as resolved.
}
Loading