// Copyright (c) Microsoft. All rights reserved.
using System;
#if NET
using System.Buffers;
#endif
#if NET
using System.Text;
#endif
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
///
/// Represents the response of the specified type to an run request.
///
/// The type of value expected from the agent.
public class AgentResponse : AgentResponse
{
private readonly JsonSerializerOptions _serializerOptions;
///
/// Initializes a new instance of the class.
///
/// The from which to populate this .
/// The to use when deserializing the result.
/// is .
public AgentResponse(AgentResponse response, JsonSerializerOptions serializerOptions) : base(response)
{
_ = Throw.IfNull(serializerOptions);
this._serializerOptions = serializerOptions;
}
///
/// Gets or sets a value indicating whether the JSON schema has an extra object wrapper.
///
///
/// The wrapper is required for any non-JSON-object-typed values such as numbers, enum values, and arrays.
///
public bool IsWrappedInObject { get; init; }
///
/// Gets the result value of the agent response as an instance of .
///
[JsonIgnore]
public virtual T Result
{
get
{
var json = this.Text;
if (string.IsNullOrEmpty(json))
{
throw new InvalidOperationException("The response did not contain JSON to be deserialized.");
}
if (this.IsWrappedInObject)
{
json = StructuredOutputSchemaUtilities.UnwrapResponseData(json!);
}
T? deserialized = DeserializeFirstTopLevelObject(json!, (JsonTypeInfo)this._serializerOptions.GetTypeInfo(typeof(T)));
if (deserialized is null)
{
throw new InvalidOperationException("The deserialized response is null.");
}
return deserialized;
}
}
private static T? DeserializeFirstTopLevelObject(string json, JsonTypeInfo typeInfo)
{
#if NET
// We need to deserialize only the first top-level object as a workaround for a common LLM backend
// issue. GPT 3.5 Turbo commonly returns multiple top-level objects after doing a function call.
// See https://community.openai.com/t/2-json-objects-returned-when-using-function-calling-and-json-mode/574348
var utf8ByteLength = Encoding.UTF8.GetByteCount(json);
var buffer = ArrayPool.Shared.Rent(utf8ByteLength);
try
{
var utf8SpanLength = Encoding.UTF8.GetBytes(json, 0, json.Length, buffer, 0);
var reader = new Utf8JsonReader(new ReadOnlySpan(buffer, 0, utf8SpanLength), new() { AllowMultipleValues = true });
return JsonSerializer.Deserialize(ref reader, typeInfo);
}
finally
{
ArrayPool.Shared.Return(buffer);
}
#else
return JsonSerializer.Deserialize(json, typeInfo);
#endif
}
}