File: Engine\UIAgentOfT.cs
Web Access
Project: src\aspnetcore\src\Components\AI\src\Microsoft.AspNetCore.Components.AI.csproj (Microsoft.AspNetCore.Components.AI)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.Components.AI;
 
/// <summary>
/// Turns an <see cref="IChatClient"/> stream into renderable content blocks and typed observable state.
/// </summary>
/// <typeparam name="TState">The type of state associated with the agent.</typeparam>
public class UIAgent<TState> : UIAgent where TState : class, new()
{
    /// <summary>
    /// Initializes a new instance of the <see cref="UIAgent{TState}"/> class.
    /// </summary>
    /// <param name="chatClient">The chat client that produces model responses.</param>
    /// <param name="initialState">The initial state value.</param>
    public UIAgent(IChatClient chatClient, TState? initialState = null)
        : this(chatClient, new UIAgentOptions<TState>(initialState), configure: null, loggerFactory: null)
    {
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="UIAgent{TState}"/> class.
    /// </summary>
    /// <param name="chatClient">The chat client that produces model responses.</param>
    /// <param name="chatOptions">The options passed to the chat client.</param>
    /// <param name="initialState">The initial state value.</param>
    public UIAgent(IChatClient chatClient, ChatOptions chatOptions, TState? initialState = null)
        : this(
            chatClient,
            new UIAgentOptions<TState>(initialState),
            options => options.ChatOptions = chatOptions,
            loggerFactory: null)
    {
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="UIAgent{TState}"/> class.
    /// </summary>
    /// <param name="chatClient">The chat client that produces model responses.</param>
    /// <param name="configure">A callback that configures the agent.</param>
    /// <param name="initialState">The initial state value.</param>
    public UIAgent(
        IChatClient chatClient,
        Action<UIAgentOptions<TState>> configure,
        TState? initialState = null)
        : this(chatClient, new UIAgentOptions<TState>(initialState), configure, loggerFactory: null)
    {
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="UIAgent{TState}"/> class.
    /// </summary>
    /// <param name="chatClient">The chat client that produces model responses.</param>
    /// <param name="configure">A callback that configures the agent.</param>
    /// <param name="loggerFactory">The logger factory used to trace block mapping.</param>
    /// <param name="initialState">The initial state value.</param>
    public UIAgent(
        IChatClient chatClient,
        Action<UIAgentOptions<TState>> configure,
        ILoggerFactory? loggerFactory,
        TState? initialState = null)
        : this(chatClient, new UIAgentOptions<TState>(initialState), configure, loggerFactory)
    {
    }
 
    private UIAgent(
        IChatClient chatClient,
        UIAgentOptions<TState> options,
        Action<UIAgentOptions<TState>>? configure,
        ILoggerFactory? loggerFactory)
        : base(chatClient, options, loggerFactory)
    {
        State = options.State;
        configure?.Invoke(options);
    }
 
    /// <summary>
    /// Gets the observable state associated with this agent.
    /// </summary>
    public AgentState<TState> State { get; }
 
    internal override ChatResponseUpdate ApplyStateMapper(ChatResponseUpdate update)
    {
        if (Options.StateMapper is null)
        {
            return update;
        }
 
        var context = new StateMapperContext(update);
        Options.StateMapper(context);
 
        if (context.StateValue is not null)
        {
            if (context.StateValue is not TState typedState)
            {
                throw new InvalidOperationException(
                    $"The state mapper returned a value of type '{context.StateValue.GetType()}', " +
                    $"but this agent requires state of type '{typeof(TState)}'.");
            }
 
            State.Value = typedState;
        }
 
        return context.HasHandledContent ? context.GetFilteredUpdate() : update;
    }
}