Compartir a través de


Uso de la IChatClient interfaz

La interfaz IChatClient define una abstracción de cliente responsable de interactuar con los servicios de INTELIGENCIA ARTIFICIAL que proporcionan funcionalidades de chat. Incluye métodos para enviar y recibir mensajes con contenido multi modal (como texto, imágenes y audio), ya sea como un conjunto completo o transmitido incrementalmente. Además, permite recuperar servicios fuertemente tipados proporcionados por el cliente o sus servicios subyacentes.

Las bibliotecas de .NET que proporcionan clientes para los modelos de lenguaje y los servicios pueden proporcionar una implementación de la IChatClient interfaz. Los consumidores de la interfaz pueden interoperar sin problemas con estos modelos y servicios a través de las abstracciones. Puede encontrar ejemplos en la sección Ejemplos de implementación .

Solicitud de una respuesta de chat

Con una instancia de IChatClient, puede llamar al IChatClient.GetResponseAsync método para enviar una solicitud y obtener una respuesta. La solicitud se compone de uno o varios mensajes, cada uno de los cuales se compone de una o varias partes de contenido. Existen métodos de acelerador para simplificar casos comunes, como la construcción de una solicitud para un solo fragmento de contenido de texto.

using Microsoft.Extensions.AI;
using OllamaSharp;

IChatClient client = new OllamaApiClient(
    new Uri("http://localhost:11434/"), "phi3:mini");

Console.WriteLine(await client.GetResponseAsync("What is AI?"));

El método principal IChatClient.GetResponseAsync acepta una lista de mensajes. Esta lista representa el historial de todos los mensajes que forman parte de la conversación.

Console.WriteLine(await client.GetResponseAsync(
[
    new(ChatRole.System, "You are a helpful AI assistant"),
    new(ChatRole.User, "What is AI?"),
]));

El ChatResponse que se devuelve desde GetResponseAsync presenta una lista de instancias de ChatMessage que representan uno o más mensajes generados como parte de la operación. En casos comunes, solo hay un mensaje de respuesta, pero en algunas situaciones puede haber varios mensajes. La lista de mensajes está ordenada, de modo que el último mensaje de la lista representa el mensaje final a la solicitud. Para devolver todos esos mensajes de respuesta al servicio en una solicitud posterior, puede volver a agregar los mensajes de la respuesta a la lista de mensajes.

List<ChatMessage> history = [];
while (true)
{
    Console.Write("Q: ");
    history.Add(new(ChatRole.User, Console.ReadLine()));

    ChatResponse response = await client.GetResponseAsync(history);
    Console.WriteLine(response);

    history.AddMessages(response);
}

Solicitud de una respuesta de chat en streaming

Las entradas de IChatClient.GetStreamingResponseAsync son idénticas a las de GetResponseAsync. Sin embargo, en lugar de devolver la respuesta completa como parte de un objeto ChatResponse, el método devuelve un IAsyncEnumerable<T> donde T es ChatResponseUpdate, proporcionando una secuencia de actualizaciones que forman colectivamente la respuesta única.

await foreach (ChatResponseUpdate update in client.GetStreamingResponseAsync("What is AI?"))
{
    Console.Write(update);
}

Sugerencia

Las API de streaming son casi sinónimos de experiencias de usuario de IA. C# permite escenarios atractivos con su compatibilidad con IAsyncEnumerable<T>, lo que permite una forma natural y eficaz de transmitir datos.

Al igual que con GetResponseAsync, puede agregar las actualizaciones de IChatClient.GetStreamingResponseAsync de nuevo a la lista de mensajes. Dado que las actualizaciones son partes individuales de una respuesta, puede usar asistentes como ToChatResponse(IEnumerable<ChatResponseUpdate>) para componer una o varias actualizaciones de nuevo en una sola ChatResponse instancia.

Los asistentes como AddMessages componen un ChatResponse y luego extraen los mensajes compuestos de la respuesta y los agregan a una lista.

List<ChatMessage> chatHistory = [];
while (true)
{
    Console.Write("Q: ");
    chatHistory.Add(new(ChatRole.User, Console.ReadLine()));

    List<ChatResponseUpdate> updates = [];
    await foreach (ChatResponseUpdate update in
        client.GetStreamingResponseAsync(chatHistory))
    {
        Console.Write(update);
        updates.Add(update);
    }
    Console.WriteLine();

    chatHistory.AddMessages(updates);
}

Llamada a herramientas

Algunos modelos y servicios admiten llamadas a herramientas. Para recopilar información adicional, puede configurar con ChatOptions información sobre herramientas (normalmente métodos .NET) que el modelo puede solicitar al cliente que invoque. En lugar de enviar una respuesta final, el modelo solicita una invocación de función con argumentos específicos. A continuación, el cliente invoca la función y devuelve los resultados al modelo con el historial de conversaciones. La biblioteca de Microsoft.Extensions.AI.Abstractions incluye abstracciones para varios tipos de contenido de mensajes, incluidas las solicitudes y los resultados de las llamadas de función. Aunque IChatClient los consumidores pueden interactuar directamente con este contenido, Microsoft.Extensions.AI proporciona asistentes que pueden habilitar la invocación automática de las herramientas en respuesta a las solicitudes correspondientes. Las Microsoft.Extensions.AI.Abstractions bibliotecas y Microsoft.Extensions.AI proporcionan los siguientes tipos:

  • AIFunction: representa una función que se puede describir en un modelo de IA e invocarla.
  • AIFunctionFactory: proporciona métodos de fábrica para crear AIFunction instancias que representan métodos de .NET.
  • FunctionInvokingChatClient: Envuelve un IChatClient como otro IChatClient adicional que agrega capacidades de invocación automática de funciones.

En el ejemplo siguiente se muestra una invocación de función aleatoria (este ejemplo depende del 📦 paquete NuGet OllamaSharp ):

using Microsoft.Extensions.AI;
using OllamaSharp;

string GetCurrentWeather() => Random.Shared.NextDouble() > 0.5 ? "It's sunny" : "It's raining";

IChatClient client = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1");

client = ChatClientBuilderChatClientExtensions
    .AsBuilder(client)
    .UseFunctionInvocation()
    .Build();

ChatOptions options = new() { Tools = [AIFunctionFactory.Create(GetCurrentWeather)] };

var response = client.GetStreamingResponseAsync("Should I wear a rain coat?", options);
await foreach (var update in response)
{
    Console.Write(update);
}

El código anterior:

  • Define una función denominada GetCurrentWeather que devuelve una previsión meteorológica aleatoria.
  • Crea una instancia de un ChatClientBuilder con un OllamaSharp.OllamaApiClient y lo configura para usar la invocación de función.
  • Llama a GetStreamingResponseAsync en el cliente, pasando una solicitud y una lista de herramientas que incluye una función creada con Create.
  • Recorre en iteración la respuesta, imprimiendo cada actualización en la consola.

Para obtener más información sobre la creación de funciones de INTELIGENCIA ARTIFICIAL, consulte Acceso a datos en funciones de IA.

También puede utilizar herramientas del protocolo de contexto de modelo (MCP) con IChatClient. Para obtener más información, consulte Compilación de un cliente MCP mínimo.

Reducción de herramientas (experimental)

Importante

Esta característica es experimental y está sujeta a cambios.

La reducción de herramientas ayuda a administrar catálogos de herramientas grandes recortandolos en función de la relevancia del contexto de conversación actual. La IToolReductionStrategy interfaz define estrategias para reducir el número de herramientas enviadas al modelo. La biblioteca proporciona implementaciones como EmbeddingToolReductionStrategy que clasifica las herramientas mediante la inserción de similitudes con la conversación. Usa el método de extensión UseToolReduction para agregar la reducción de herramientas a la canalización del cliente de chat.

Respuestas de caché

Si está familiarizado con el almacenamiento en caché en .NET, es bueno saber que Microsoft.Extensions.AI proporciona implementaciones de delegación IChatClient para el almacenamiento en caché. El DistributedCachingChatClient es un IChatClient que aplica almacenamiento en caché sobre otra instancia arbitraria de IChatClient. Cuando se envía un historial de chat nuevo a DistributedCachingChatClient, lo reenvía al cliente subyacente y, a continuación, almacena en caché la respuesta antes de devolverla al consumidor. La próxima vez que se envíe el mismo historial, de modo que se pueda encontrar una respuesta almacenada en caché en la memoria caché, DistributedCachingChatClient devuelve la respuesta almacenada en caché en lugar de reenviar la solicitud a lo largo de la canalización.

using Microsoft.Extensions.AI;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using OllamaSharp;

var sampleChatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1");

IChatClient client = new ChatClientBuilder(sampleChatClient)
    .UseDistributedCache(new MemoryDistributedCache(
        Options.Create(new MemoryDistributedCacheOptions())))
    .Build();

string[] prompts = ["What is AI?", "What is .NET?", "What is AI?"];

foreach (var prompt in prompts)
{
    await foreach (var update in client.GetStreamingResponseAsync(prompt))
    {
        Console.Write(update);
    }
    Console.WriteLine();
}

Este ejemplo depende del 📦 paquete NuGet Microsoft.Extensions.Caching.Memory . Para más información, vea Almacenamiento en caché en .NET.

Uso de telemetría

Otro ejemplo de un cliente de chat de delegación es el OpenTelemetryChatClient. Esta implementación se adhiere a las convenciones semánticas de OpenTelemetry para sistemas de inteligencia artificial generativa. De forma similar a otros delegadores IChatClient, se superponen las métricas y se extienden alrededor de otras implementaciones de IChatClient arbitrarias.

using Microsoft.Extensions.AI;
using OllamaSharp;
using OpenTelemetry.Trace;

// Configure OpenTelemetry exporter.
string sourceName = Guid.NewGuid().ToString();
TracerProvider tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
    .AddSource(sourceName)
    .AddConsoleExporter()
    .Build();

IChatClient ollamaClient = new OllamaApiClient(
    new Uri("http://localhost:11434/"), "phi3:mini");

IChatClient client = new ChatClientBuilder(ollamaClient)
    .UseOpenTelemetry(
        sourceName: sourceName,
        configure: c => c.EnableSensitiveData = true)
    .Build();

Console.WriteLine((await client.GetResponseAsync("What is AI?")).Text);

(El ejemplo anterior depende del 📦 paquete NuGet OpenTelemetry.Exporter.Console ).

Como alternativa, el método LoggingChatClient y el método correspondiente UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) proporcionan una manera sencilla de escribir entradas de registro en un ILogger para cada solicitud y respuesta.

Proporcionar opciones

Cada llamada a GetResponseAsync o GetStreamingResponseAsync puede proporcionar opcionalmente una instancia de ChatOptions que contenga parámetros adicionales para la operación. Los parámetros más comunes entre los modelos y servicios de IA aparecen como propiedades fuertemente tipadas en el tipo, como ChatOptions.Temperature. Otros parámetros se pueden proporcionar por nombre de una manera débilmente tipada, a través del ChatOptions.AdditionalProperties diccionario o a través de una instancia de opciones que el proveedor subyacente entiende, utilizando la ChatOptions.RawRepresentationFactory propiedad .

También puede especificar opciones al construir un IChatClient con la API fluida ChatClientBuilder mediante el encadenamiento de una llamada al método de extensión ConfigureOptions(ChatClientBuilder, Action<ChatOptions>). Este cliente de delegación encapsula otro cliente e invoca al delegado proporcionado para rellenar una instancia de ChatOptions para cada llamada. Por ejemplo, para asegurarse de que la propiedad ChatOptions.ModelId tiene como valor predeterminado un nombre de modelo determinado, puede usar código como el siguiente:

using Microsoft.Extensions.AI;
using OllamaSharp;

IChatClient client = new OllamaApiClient(new Uri("http://localhost:11434"));

client = ChatClientBuilderChatClientExtensions.AsBuilder(client)
    .ConfigureOptions(options => options.ModelId ??= "phi3")
    .Build();

// Will request "phi3".
Console.WriteLine(await client.GetResponseAsync("What is AI?"));
// Will request "llama3.1".
Console.WriteLine(await client.GetResponseAsync("What is AI?", new() { ModelId = "llama3.1" }));

Pipelines de funcionalidad

IChatClient Las instancias se pueden superponer para crear una canalización de componentes que agreguen funcionalidad adicional. Estos componentes pueden provenir de Microsoft.Extensions.AI, otros paquetes NuGet o implementaciones personalizadas. Este enfoque le permite aumentar el comportamiento del IChatClient de varias maneras de satisfacer sus necesidades específicas. Tenga en cuenta el siguiente fragmento de código que añade capas a los almacenamientos en caché distribuidos, la invocación de funciones y el seguimiento de OpenTelemetry en torno a clientes de chat de ejemplo.

// Explore changing the order of the intermediate "Use" calls.
IChatClient client = new ChatClientBuilder(new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1"))
    .UseDistributedCache(new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())))
    .UseFunctionInvocation()
    .UseOpenTelemetry(sourceName: sourceName, configure: c => c.EnableSensitiveData = true)
    .Build();

Middleware IChatClient personalizado

Para agregar funcionalidad adicional, puede implementar IChatClient directamente o usar la clase DelegatingChatClient. Esta clase sirve como base para crear clientes de chat que deleguen operaciones a otra instancia de IChatClient. Simplifica el encadenamiento de varios clientes, lo que permite que las llamadas pasen a un cliente subyacente.

La clase DelegatingChatClient proporciona implementaciones predeterminadas para métodos como GetResponseAsync, GetStreamingResponseAsyncy Dispose, que reenvía llamadas al cliente interno. A continuación, una clase derivada solo puede invalidar los métodos que necesita para aumentar el comportamiento, al tiempo que se delega otras llamadas a la implementación base. Este enfoque es útil para crear clientes de chat flexibles y modulares que son fáciles de ampliar y redactar.

A continuación se muestra una clase de ejemplo derivada de DelegatingChatClient que usa la biblioteca System.Threading.RateLimiting para proporcionar funcionalidad de limitación de velocidad.

using Microsoft.Extensions.AI;
using System.Runtime.CompilerServices;
using System.Threading.RateLimiting;

public sealed class RateLimitingChatClient(
    IChatClient innerClient, RateLimiter rateLimiter)
        : DelegatingChatClient(innerClient)
{
    public override async Task<ChatResponse> GetResponseAsync(
        IEnumerable<ChatMessage> messages,
        ChatOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
            .ConfigureAwait(false);
        if (!lease.IsAcquired)
            throw new InvalidOperationException("Unable to acquire lease.");

        return await base.GetResponseAsync(messages, options, cancellationToken)
            .ConfigureAwait(false);
    }

    public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
        IEnumerable<ChatMessage> messages,
        ChatOptions? options = null,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
            .ConfigureAwait(false);
        if (!lease.IsAcquired)
            throw new InvalidOperationException("Unable to acquire lease.");

        await foreach (var update in base.GetStreamingResponseAsync(messages, options, cancellationToken)
            .ConfigureAwait(false))
        {
            yield return update;
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
            rateLimiter.Dispose();

        base.Dispose(disposing);
    }
}

Al igual que con otras IChatClient implementaciones, RateLimitingChatClient se puede componer:

using Microsoft.Extensions.AI;
using OllamaSharp;
using System.Threading.RateLimiting;

var client = new RateLimitingChatClient(
    new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1"),
    new ConcurrencyLimiter(new() { PermitLimit = 1, QueueLimit = int.MaxValue }));

Console.WriteLine(await client.GetResponseAsync("What color is the sky?"));

Para simplificar la composición de estos componentes con otros, los autores de componentes deben crear un método de extensión Use* para registrar el componente en una canalización. Por ejemplo, considere el siguiente UseRateLimiting método de extensión:

using Microsoft.Extensions.AI;
using System.Threading.RateLimiting;

public static class RateLimitingChatClientExtensions
{
    public static ChatClientBuilder UseRateLimiting(
        this ChatClientBuilder builder,
        RateLimiter rateLimiter) =>
        builder.Use(innerClient =>
            new RateLimitingChatClient(innerClient, rateLimiter)
        );
}

Estas extensiones también pueden consultar los servicios pertinentes del contenedor de inserción de dependencias; el IServiceProvider usado por la pipeline se pasa como parámetro opcional:

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.RateLimiting;

public static class RateLimitingChatClientExtensions
{
    public static ChatClientBuilder UseRateLimiting(
        this ChatClientBuilder builder,
        RateLimiter? rateLimiter = null) =>
        builder.Use((innerClient, services) =>
            new RateLimitingChatClient(
                innerClient,
                services.GetRequiredService<RateLimiter>())
        );
}

Ahora es fácil para el consumidor usarlo en su flujo de trabajo, por ejemplo:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

IChatClient client = new OllamaApiClient(
    new Uri("http://localhost:11434/"),
    "phi3:mini");

builder.Services.AddChatClient(services =>
        client
        .AsBuilder()
        .UseDistributedCache()
        .UseRateLimiting()
        .UseOpenTelemetry()
        .Build(services));

Los métodos de extensión anteriores muestran el uso de un método Use en ChatClientBuilder. El ChatClientBuilder también proporciona sobrecargas de Use que facilitan la escritura de tales controladores de delegación. Por ejemplo, en el ejemplo anterior de RateLimitingChatClient, las sobrescrituras de GetResponseAsync y GetStreamingResponseAsync solo necesitan realizar el trabajo antes y después de delegar al siguiente cliente en el pipeline. Para lograr lo mismo sin escribir una clase personalizada, se puede usar una sobrecarga de Use que acepta un delegado que se utiliza para GetResponseAsync y GetStreamingResponseAsync, reduciendo el código repetitivo necesario:

using Microsoft.Extensions.AI;
using OllamaSharp;
using System.Threading.RateLimiting;

RateLimiter rateLimiter = new ConcurrencyLimiter(new()
{
    PermitLimit = 1,
    QueueLimit = int.MaxValue
});

IChatClient client = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1");

client = ChatClientBuilderChatClientExtensions
    .AsBuilder(client)
    .UseDistributedCache()
    .Use(async (messages, options, nextAsync, cancellationToken) =>
    {
        using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken).ConfigureAwait(false);
        if (!lease.IsAcquired)
            throw new InvalidOperationException("Unable to acquire lease.");

        await nextAsync(messages, options, cancellationToken);
    })
    .UseOpenTelemetry()
    .Build();

En escenarios en los que necesita una implementación diferente para GetResponseAsync y GetStreamingResponseAsync para manejar sus tipos de retorno únicos, puede usar la sobrecarga Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken, Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions, IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>) que acepta un delegado para cada uno.

Inserción de dependencia

IChatClientLas implementaciones a menudo se proporcionan a una aplicación a través de la inserción de dependencias (DI). En el ejemplo siguiente, se agrega un IDistributedCache al contenedor de inyección de dependencias, al igual que un IChatClient. El registro de IChatClient emplea un generador que crea una pipeline que contiene un cliente de caché (que luego usará un IDistributedCache recuperado a través de la DI) y el cliente de ejemplo. El IChatClient insertado se puede recuperar y usar en otra parte de la aplicación.

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OllamaSharp;

// App setup.
var builder = Host.CreateApplicationBuilder();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddChatClient(new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1"))
    .UseDistributedCache();
var host = builder.Build();

// Elsewhere in the app.
var chatClient = host.Services.GetRequiredService<IChatClient>();
Console.WriteLine(await chatClient.GetResponseAsync("What is AI?"));

La instancia y la configuración insertadas pueden diferir en función de las necesidades actuales de la aplicación y se pueden insertar varias canalizaciones con claves diferentes.

Clientes sin estado frente a clientes con estado

Los servicios sin estado requieren que se devuelva todo el historial de conversaciones pertinente en cada solicitud. En cambio, los servicios con estado realizan un seguimiento del historial y requieren que solo se envíen mensajes adicionales con una solicitud. La interfaz IChatClient está diseñada para gestionar servicios de IA con y sin estado.

Al trabajar con un servicio sin estado, los autores de llamada mantienen una lista de todos los mensajes. Agregan todos los mensajes de respuesta recibidos y proporcionan la lista de nuevo en las interacciones posteriores.

List<ChatMessage> history = [];
while (true)
{
    Console.Write("Q: ");
    history.Add(new(ChatRole.User, Console.ReadLine()));

    var response = await client.GetResponseAsync(history);
    Console.WriteLine(response);

    history.AddMessages(response);
}

En el caso de los servicios con estado, es posible que ya conozca el identificador usado para la conversación pertinente. Puede colocar ese identificador en ChatOptions.ConversationId. Después, el uso sigue el mismo patrón, salvo que no es necesario mantener manualmente un historial.

ChatOptions statefulOptions = new() { ConversationId = "my-conversation-id" };
while (true)
{
    Console.Write("Q: ");
    ChatMessage message = new(ChatRole.User, Console.ReadLine());

    Console.WriteLine(await client.GetResponseAsync(message, statefulOptions));
}

Algunos servicios pueden admitir la creación automática de un identificador de conversación para una solicitud que no tiene uno o la creación de un nuevo identificador de conversación que represente el estado actual de la conversación después de incorporar la última ronda de mensajes. En tales casos, puede transferir el ChatResponse.ConversationId al ChatOptions.ConversationId para las solicitudes posteriores. Por ejemplo:

ChatOptions options = new();
while (true)
{
    Console.Write("Q: ");
    ChatMessage message = new(ChatRole.User, Console.ReadLine());

    ChatResponse response = await client.GetResponseAsync(message, options);
    Console.WriteLine(response);

    options.ConversationId = response.ConversationId;
}

Si no sabe con antelación si el servicio es con estado o sin estado, puede comprobar la respuesta ConversationId y actuar según su valor. Si se establece, ese valor se propaga a las opciones y el historial se borra para no volver a enviar el mismo historial de nuevo. Si no se establece la respuesta ConversationId , el mensaje de respuesta se agrega al historial para que se devuelva al servicio en el siguiente turno.

List<ChatMessage> chatHistory = [];
ChatOptions chatOptions = new();
while (true)
{
    Console.Write("Q: ");
    chatHistory.Add(new(ChatRole.User, Console.ReadLine()));

    ChatResponse response = await client.GetResponseAsync(chatHistory);
    Console.WriteLine(response);

    chatOptions.ConversationId = response.ConversationId;
    if (response.ConversationId is not null)
    {
        chatHistory.Clear();
    }
    else
    {
        chatHistory.AddMessages(response);
    }
}

Ejemplos de implementación

En el ejemplo siguiente se implementa IChatClient para mostrar la estructura general.

using System.Runtime.CompilerServices;
using Microsoft.Extensions.AI;

public sealed class SampleChatClient(Uri endpoint, string modelId)
    : IChatClient
{
    public ChatClientMetadata Metadata { get; } =
        new(nameof(SampleChatClient), endpoint, modelId);

    public async Task<ChatResponse> GetResponseAsync(
        IEnumerable<ChatMessage> chatMessages,
        ChatOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        // Simulate some operation.
        await Task.Delay(300, cancellationToken);

        // Return a sample chat completion response randomly.
        string[] responses =
        [
            "This is the first sample response.",
            "Here is another example of a response message.",
            "This is yet another response message."
        ];

        return new(new ChatMessage(
            ChatRole.Assistant,
            responses[Random.Shared.Next(responses.Length)]
            ));
    }

    public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
        IEnumerable<ChatMessage> chatMessages,
        ChatOptions? options = null,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        // Simulate streaming by yielding messages one by one.
        string[] words = ["This ", "is ", "the ", "response ", "for ", "the ", "request."];
        foreach (string word in words)
        {
            // Simulate some operation.
            await Task.Delay(100, cancellationToken);

            // Yield the next message in the response.
            yield return new ChatResponseUpdate(ChatRole.Assistant, word);
        }
    }

    public object? GetService(Type serviceType, object? serviceKey) => this;

    public TService? GetService<TService>(object? key = null)
        where TService : class => this as TService;

    void IDisposable.Dispose() { }
}

Para obtener implementaciones más realistas y concretas de IChatClient, consulte:

Reducción del chat (experimental)

Importante

Esta característica es experimental y está sujeta a cambios.

La reducción del chat ayuda a administrar el historial de conversaciones limitando el número de mensajes o resumiendo mensajes más antiguos cuando la conversación supera una longitud especificada. La Microsoft.Extensions.AI biblioteca proporciona reductores como MessageCountingChatReducer que limitan el número de mensajes que no son del sistema y SummarizingChatReducer que resume automáticamente los mensajes más antiguos al tiempo que conserva el contexto.