| File: ChatRouting\FailoverChatClientAttempt.cs | |
| Project: ..\..\..\src\Libraries\Microsoft.Extensions.AI\Microsoft.Extensions.AI.csproj (Microsoft.Extensions.AI) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.AI; /// <summary>Represents one client invocation attempt performed by a <see cref="FailoverChatClient"/>.</summary> /// <remarks> /// <para> /// A completed response has <see cref="ResponseCompleted"/> set to <see langword="true"/> and /// <see cref="Exception"/> set to <see langword="null"/>. /// </para> /// <para> /// A failed invocation has <see cref="ResponseCompleted"/> set to <see langword="false"/> and /// <see cref="Exception"/> set to the observed exception. If a streaming caller stops enumerating before the /// response ends and disposal completes successfully, both <see cref="ResponseCompleted"/> and /// <see cref="Exception"/> are unset. /// </para> /// <para> /// <see cref="OutputCommitted"/> is independent of the outcome and indicates whether any streaming update was /// exposed to the caller. /// </para> /// </remarks> [Experimental(DiagnosticIds.Experiments.AIRoutingChat, UrlFormat = DiagnosticIds.UrlFormat)] public sealed class FailoverChatClientAttempt { internal FailoverChatClientAttempt( IChatClient client, Exception? exception, TimeSpan duration, TimeSpan? timeToFirstUpdate, bool responseCompleted, bool outputCommitted) { Debug.Assert(duration >= TimeSpan.Zero, "Expected a non-negative duration."); Debug.Assert( timeToFirstUpdate is not { } ttfu || (ttfu >= TimeSpan.Zero && ttfu <= duration), "Expected time to first update to be within the active duration."); Debug.Assert( !responseCompleted || exception is null, "A completed response should not have an exception."); Debug.Assert( outputCommitted == timeToFirstUpdate.HasValue, "Output commitment and time to first update should agree."); Client = Throw.IfNull(client); Exception = exception; Duration = duration; TimeToFirstUpdate = timeToFirstUpdate; ResponseCompleted = responseCompleted; OutputCommitted = outputCommitted; } /// <summary>Gets the client that was invoked.</summary> public IChatClient Client { get; } /// <summary>Gets the time spent actively invoking the client.</summary> /// <remarks>For streaming, time spent by the caller processing yielded updates is excluded.</remarks> public TimeSpan Duration { get; } /// <summary>Gets the exception observed while invoking or disposing the client, if any.</summary> /// <remarks> /// <para>If invocation and disposal both throw, this contains the disposal exception.</para> /// <para> /// A <see langword="null"/> value does not necessarily indicate success; inspect <see cref="ResponseCompleted"/> /// to distinguish a completed response from a streaming response that the caller stopped consuming. /// </para> /// </remarks> public Exception? Exception { get; } /// <summary>Gets a value indicating whether any streaming update was exposed to the caller.</summary> /// <remarks>This is always <see langword="false"/> for non-streaming invocations.</remarks> public bool OutputCommitted { get; } /// <summary>Gets a value indicating whether the response completed successfully.</summary> /// <remarks> /// For streaming responses, this is <see langword="false"/> when the caller stops enumeration before the /// response stream ends. /// </remarks> public bool ResponseCompleted { get; } /// <summary>Gets the time until the first streaming update, if this was a non-empty streaming invocation.</summary> public TimeSpan? TimeToFirstUpdate { get; } }