| File: Realtime\FunctionInvokingRealtimeClient.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.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.AI; /// <summary> /// A delegating realtime client that invokes functions defined on <see cref="CreateResponseRealtimeClientMessage"/>. /// Include this in a realtime client pipeline to resolve function calls automatically. /// </summary> /// <remarks> /// <para> /// When sessions created by this client receive a <see cref="FunctionCallContent"/> in a realtime server message from the inner /// <see cref="IRealtimeClientSession"/>, they respond by invoking the corresponding <see cref="AIFunction"/> defined /// in <see cref="CreateResponseRealtimeClientMessage.Tools"/> (or in <see cref="AdditionalTools"/>), producing a <see cref="FunctionResultContent"/> /// that is sent back to the inner session. This loop is repeated until there are no more function calls to make, or until /// another stop condition is met, such as hitting <see cref="MaximumIterationsPerRequest"/>. /// </para> /// </remarks> [Experimental(DiagnosticIds.Experiments.AIRealTime, UrlFormat = DiagnosticIds.UrlFormat)] public sealed class FunctionInvokingRealtimeClient : DelegatingRealtimeClient { private readonly ILoggerFactory? _loggerFactory; private readonly IServiceProvider? _services; /// <summary> /// Initializes a new instance of the <see cref="FunctionInvokingRealtimeClient"/> class. /// </summary> /// <param name="innerClient">The inner <see cref="IRealtimeClient"/>.</param> /// <param name="loggerFactory">An <see cref="ILoggerFactory"/> to use for logging information about function invocation.</param> /// <param name="functionInvocationServices">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param> public FunctionInvokingRealtimeClient(IRealtimeClient innerClient, ILoggerFactory? loggerFactory = null, IServiceProvider? functionInvocationServices = null) : base(innerClient) { _loggerFactory = loggerFactory; _services = functionInvocationServices; } /// <summary> /// Gets the <see cref="FunctionInvocationContext"/> for the current function invocation. /// </summary> /// <remarks> /// This value flows across async calls. /// </remarks> public static FunctionInvocationContext? CurrentContext => FunctionInvokingRealtimeClientSession.CurrentContext; /// <summary> /// Gets or sets a value indicating whether detailed exception information should be included /// in the response when calling the underlying <see cref="IRealtimeClientSession"/>. /// </summary> /// <value> /// <see langword="true"/> if the full exception message is added to the response. /// <see langword="false"/> if a generic error message is included in the response. /// The default value is <see langword="false"/>. /// </value> public bool IncludeDetailedErrors { get; set; } /// <summary> /// Gets or sets a value indicating whether to allow concurrent invocation of functions. /// </summary> /// <value> /// <see langword="true"/> if multiple function calls can execute in parallel. /// <see langword="false"/> if function calls are processed serially. /// The default value is <see langword="false"/>. /// </value> public bool AllowConcurrentInvocation { get; set; } /// <summary> /// Gets or sets the maximum number of iterations per request. /// </summary> /// <value> /// The maximum number of iterations per request. /// The default value is 40. /// </value> public int MaximumIterationsPerRequest { get; set { if (value < 1) { Throw.ArgumentOutOfRangeException(nameof(value)); } field = value; } } = 40; /// <summary> /// Gets or sets the maximum number of consecutive iterations that are allowed to fail with an error. /// </summary> /// <value> /// The maximum number of consecutive iterations that are allowed to fail with an error. /// The default value is 3. /// </value> public int MaximumConsecutiveErrorsPerRequest { get; set => field = Throw.IfLessThan(value, 0); } = 3; /// <summary>Gets or sets a collection of additional tools the session is able to invoke.</summary> public IList<AITool>? AdditionalTools { get; set; } /// <summary>Gets or sets a value indicating whether a request to call an unknown function should terminate the function calling loop.</summary> /// <value> /// <see langword="true"/> to terminate the function calling loop and return the response if a request to call a tool /// that isn't available is received; <see langword="false"/> to create and send a /// function result message stating that the tool couldn't be found. The default is <see langword="false"/>. /// </value> public bool TerminateOnUnknownCalls { get; set; } /// <summary>Gets or sets a delegate used to invoke <see cref="AIFunction"/> instances.</summary> public Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>>? FunctionInvoker { get; set; } /// <inheritdoc /> public override async Task<IRealtimeClientSession> CreateSessionAsync( RealtimeSessionOptions? options = null, CancellationToken cancellationToken = default) { var innerSession = await base.CreateSessionAsync(options, cancellationToken).ConfigureAwait(false); return new FunctionInvokingRealtimeClientSession(innerSession, this, _loggerFactory, _services); } }