// Copyright (c) Microsoft. All rights reserved. using System; #if NET using System.Buffers; #endif using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; #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; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI; /// /// Represents the response to an run request, containing messages and metadata about the interaction. /// /// /// /// provides one or more response messages and metadata about the response. /// A typical response will contain a single message, however a response may contain multiple messages /// in a variety of scenarios. For example, if the agent internally invokes functions or tools, performs /// RAG retrievals or has other complex logic, a single run by the agent may produce many messages showing /// the intermediate progress that the agent made towards producing the agent result. /// /// /// To get the text result of the response, use the property or simply call on the . /// /// public class AgentRunResponse { /// The response messages. private IList? _messages; /// Initializes a new instance of the class. public AgentRunResponse() { } /// Initializes a new instance of the class. /// The response message to include in this response. /// is . public AgentRunResponse(ChatMessage message) { _ = Throw.IfNull(message); this.Messages.Add(message); } /// /// Initializes a new instance of the class from an existing . /// /// The from which to populate this . /// is . /// /// This constructor creates an agent response that wraps an existing , preserving all /// metadata and storing the original response in for access to /// the underlying implementation details. /// public AgentRunResponse(ChatResponse response) { _ = Throw.IfNull(response); this.AdditionalProperties = response.AdditionalProperties; this.CreatedAt = response.CreatedAt; this.Messages = response.Messages; this.RawRepresentation = response; this.ResponseId = response.ResponseId; this.Usage = response.Usage; this.ContinuationToken = response.ContinuationToken; } /// /// Initializes a new instance of the class with the specified collection of messages. /// /// The collection of response messages, or to create an empty response. public AgentRunResponse(IList? messages) { this._messages = messages; } /// /// Gets or sets the collection of messages to be represented by this response. /// /// /// A collection of instances representing the agent's response. /// If the backing collection is , accessing this property will create an empty list. /// /// /// /// This property provides access to all messages generated during the agent's execution. While most /// responses contain a single assistant message, complex agent behaviors may produce multiple messages /// showing intermediate steps, function calls, or different types of content. /// /// /// The collection is mutable and can be modified after creation. Setting this property to /// will cause subsequent access to return an empty list. /// /// [AllowNull] public IList Messages { get => this._messages ??= new List(1); set => this._messages = value; } /// /// Gets the concatenated text content of all messages in this response. /// /// /// A string containing the combined text from all instances /// across all messages in , or an empty string if no text content is present. /// /// /// This property provides a convenient way to access the textual response without needing to /// iterate through individual messages and content items. Non-text content is ignored. /// [JsonIgnore] public string Text => this._messages?.ConcatText() ?? string.Empty; /// /// Gets all user input requests present in the response messages. /// /// /// An enumerable collection of instances found /// across all messages in the response. /// /// /// User input requests indicate that the agent is asking for additional information /// from the user before it can continue processing. This property aggregates all such /// requests across all messages in the response. /// [JsonIgnore] public IEnumerable UserInputRequests => this._messages?.SelectMany(x => x.Contents).OfType() ?? []; /// /// Gets or sets the identifier of the agent that generated this response. /// /// /// A unique string identifier for the agent, or if not specified. /// /// /// This identifier helps track which agent generated the response in multi-agent scenarios /// or for debugging and telemetry purposes. /// public string? AgentId { get; set; } /// /// Gets or sets the unique identifier for this specific response. /// /// /// A unique string identifier for this response instance, or if not assigned. /// public string? ResponseId { get; set; } /// /// Gets or sets the continuation token for getting the result of a background agent response. /// /// /// implementations that support background responses will return /// a continuation token if background responses are allowed in /// and the result of the response has not been obtained yet. If the response has completed and the result has been obtained, /// the token will be . /// /// This property should be used in conjunction with to /// continue to poll for the completion of the response. Pass this token to /// on subsequent calls to /// to poll for completion. /// /// public ResponseContinuationToken? ContinuationToken { get; set; } /// /// Gets or sets the timestamp indicating when this response was created. /// /// /// A representing when the response was generated, /// or if not specified. /// /// /// The creation timestamp is useful for auditing, logging, and understanding /// the chronology of agentic interactions. /// public DateTimeOffset? CreatedAt { get; set; } /// /// Gets or sets the resource usage information for generating this response. /// /// /// A instance containing token counts and other usage metrics, /// or if usage information is not available. /// public UsageDetails? Usage { get; set; } /// Gets or sets the raw representation of the run response from an underlying implementation. /// /// If a is created to represent some underlying object from another object /// model, this property can be used to store that original object. This can be useful for debugging or /// for enabling a consumer to access the underlying object model if needed. /// [JsonIgnore] public object? RawRepresentation { get; set; } /// /// Gets or sets additional properties associated with this response. /// /// /// An containing custom properties, /// or if no additional properties are present. /// /// /// Additional properties provide a way to include custom metadata or provider-specific /// information that doesn't fit into the standard response schema. This is useful for /// preserving implementation-specific details or extending the response with custom data. /// public AdditionalPropertiesDictionary? AdditionalProperties { get; set; } /// public override string ToString() => this.Text; /// /// Converts this into a collection of instances /// suitable for streaming scenarios. /// /// /// An array of instances that collectively represent /// the same information as this response. /// /// /// /// This method is useful for converting complete responses back into streaming format, /// which may be needed for scenarios that require uniform handling of both streaming /// and non-streaming agent responses. /// /// /// Each message in becomes a separate update, and usage information /// is included as an additional update if present. The order of updates preserves the /// original message sequence. /// /// public AgentRunResponseUpdate[] ToAgentRunResponseUpdates() { AgentRunResponseUpdate? extra = null; if (this.AdditionalProperties is not null || this.Usage is not null) { extra = new AgentRunResponseUpdate { AdditionalProperties = this.AdditionalProperties, }; if (this.Usage is { } usage) { extra.Contents.Add(new UsageContent(usage)); } } int messageCount = this._messages?.Count ?? 0; var updates = new AgentRunResponseUpdate[messageCount + (extra is not null ? 1 : 0)]; int i; for (i = 0; i < messageCount; i++) { ChatMessage message = this._messages![i]; updates[i] = new AgentRunResponseUpdate { AdditionalProperties = message.AdditionalProperties, AuthorName = message.AuthorName, Contents = message.Contents, RawRepresentation = message.RawRepresentation, Role = message.Role, AgentId = this.AgentId, ResponseId = this.ResponseId, MessageId = message.MessageId, CreatedAt = this.CreatedAt, }; } if (extra is not null) { updates[i] = extra; } return updates; } /// /// Deserializes the response text into the given type. /// /// The output type to deserialize into. /// The result as the requested type. /// The result is not parsable into the requested type. public T Deserialize() => this.Deserialize(AgentAbstractionsJsonUtilities.DefaultOptions); /// /// Deserializes the response text into the given type using the specified serializer options. /// /// The output type to deserialize into. /// The JSON serialization options to use. /// The result as the requested type. /// The result is not parsable into the requested type. public T Deserialize(JsonSerializerOptions serializerOptions) { _ = Throw.IfNull(serializerOptions); var structuredOutput = this.GetResultCore(serializerOptions, out var failureReason); return failureReason switch { FailureReason.ResultDidNotContainJson => throw new InvalidOperationException("The response did not contain JSON to be deserialized."), FailureReason.DeserializationProducedNull => throw new InvalidOperationException("The deserialized response is null."), _ => structuredOutput!, }; } /// /// Tries to deserialize response text into the given type. /// /// The output type to deserialize into. /// The parsed structured output. /// if parsing was successful; otherwise, . public bool TryDeserialize([NotNullWhen(true)] out T? structuredOutput) => this.TryDeserialize(AgentAbstractionsJsonUtilities.DefaultOptions, out structuredOutput); /// /// Tries to deserialize response text into the given type using the specified serializer options. /// /// The output type to deserialize into. /// The JSON serialization options to use. /// The parsed structured output. /// if parsing was successful; otherwise, . public bool TryDeserialize(JsonSerializerOptions serializerOptions, [NotNullWhen(true)] out T? structuredOutput) { _ = Throw.IfNull(serializerOptions); try { structuredOutput = this.GetResultCore(serializerOptions, out var failureReason); return failureReason is null; } catch { structuredOutput = default; return false; } } 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 } private T? GetResultCore(JsonSerializerOptions serializerOptions, out FailureReason? failureReason) { var json = this.Text; if (string.IsNullOrEmpty(json)) { failureReason = FailureReason.ResultDidNotContainJson; return default; } // If there's an exception here, we want it to propagate, since the Result property is meant to throw directly T? deserialized = DeserializeFirstTopLevelObject(json!, (JsonTypeInfo)serializerOptions.GetTypeInfo(typeof(T))); if (deserialized is null) { failureReason = FailureReason.DeserializationProducedNull; return default; } failureReason = default; return deserialized; } private enum FailureReason { ResultDidNotContainJson, DeserializationProducedNull } }