Partilhar via


Usar a IChatClient interface

A interface IChatClient define uma abstração de cliente responsável por interagir com serviços de IA que fornecem recursos de bate-papo. Inclui métodos para enviar e receber mensagens com conteúdo multimodal (como texto, imagens e áudio), como um conjunto completo ou transmitido incrementalmente. Além disso, permite recuperar serviços de tipos fortes disponibilizados pelo cliente ou pelos seus serviços subjacentes.

As bibliotecas .NET que fornecem clientes para modelos de linguagem e serviços podem fornecer uma implementação da IChatClient interface. Qualquer consumidor da interface é então capaz de interoperar perfeitamente com esses modelos e serviços através das abstrações. Pode encontrar exemplos na secção de Exemplos de Implementação .

Solicite uma resposta de bate-papo

Com uma instância do IChatClient, você pode chamar o IChatClient.GetResponseAsync método para enviar uma solicitação e obter uma resposta. O pedido é composto por uma ou mais mensagens, cada uma das quais é composta por um ou mais conteúdos. Os métodos de aceleração existem para simplificar casos comuns, como a construção de uma solicitação para uma única parte do conteúdo 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?"));

O método IChatClient.GetResponseAsync principal aceita uma lista de mensagens. Esta lista representa o histórico de todas as mensagens que fazem parte da conversa.

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

O ChatResponse que é retornado de GetResponseAsync expõe uma lista de ChatMessage instâncias que representam uma ou mais mensagens geradas como parte da operação. Em casos comuns, há apenas uma mensagem de resposta, mas em algumas situações, pode haver várias mensagens. A lista de mensagens é ordenada, de modo que a última mensagem na lista represente a mensagem final para a solicitação. Para fornecer todas essas mensagens de resposta de volta ao serviço em uma solicitação subsequente, você pode adicionar as mensagens da resposta de volta à lista de mensagens.

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);
}

Solicite uma resposta de bate-papo por transmissão contínua

As entradas para IChatClient.GetStreamingResponseAsync são idênticas às de GetResponseAsync. No entanto, em vez de retornar a resposta completa como parte de um objeto ChatResponse, o método retorna um IAsyncEnumerable<T> onde T é ChatResponseUpdate, fornecendo um fluxo de atualizações que formam coletivamente a resposta única.

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

Sugestão

APIs de streaming são quase sinônimo de experiências de usuário de IA. O C# permite cenários atraentes com seu suporte IAsyncEnumerable<T>, permitindo uma maneira natural e eficiente de transmitir dados.

Assim como no GetResponseAsync, pode adicionar novamente as atualizações à lista de mensagens de IChatClient.GetStreamingResponseAsync. Como as atualizações são partes individuais de uma resposta, pode-se usar ferramentas como ToChatResponse(IEnumerable<ChatResponseUpdate>) para compor uma ou mais atualizações numa única instância ChatResponse.

Auxiliares como AddMessages compõem um ChatResponse, em seguida, extraem as mensagens compostas da resposta e adicionam-nas a uma 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);
}

Chamada da ferramenta

Alguns modelos e serviços suportam a chamada de ferramentas. Para coletar informações adicionais, você pode configurar o ChatOptions com informações sobre ferramentas (geralmente métodos .NET) que o modelo pode solicitar que o cliente invoque. Em vez de enviar uma resposta final, o modelo solicita uma invocação de função com argumentos específicos. Em seguida, o cliente invoca a função e envia os resultados de volta para o modelo com o histórico de conversas. A biblioteca de Microsoft.Extensions.AI.Abstractions inclui abstrações para vários tipos de conteúdo de mensagem, incluindo solicitações de chamada de função e resultados. Embora IChatClient os consumidores possam interagir diretamente com esse conteúdo, Microsoft.Extensions.AI fornece auxiliares que podem habilitar a invocação automática das ferramentas em resposta às solicitações correspondentes. As Microsoft.Extensions.AI.Abstractions bibliotecas e Microsoft.Extensions.AI fornecem os seguintes tipos:

  • AIFunction: Representa uma função que pode ser descrita para um modelo de IA e invocada.
  • AIFunctionFactory: Fornece métodos de fábrica para criar AIFunction instâncias que representam métodos .NET.
  • FunctionInvokingChatClient: Encapsula um IChatClient num outro IChatClient que adiciona capacidades automáticas de invocação de funções.

O exemplo a seguir demonstra uma invocação de função aleatória (este exemplo depende do 📦 pacote 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);
}

O código anterior:

  • Define uma função chamada GetCurrentWeather que retorna uma previsão do tempo aleatória.
  • Instancia um ChatClientBuilder com um OllamaSharp.OllamaApiClient e configura-o para utilizar a invocação de função.
  • Chama GetStreamingResponseAsync no cliente, passando um prompt e uma lista de ferramentas que inclui uma função criada com Create.
  • Itera sobre a resposta, imprimindo cada atualização no console.

Para mais informações sobre a criação de funções de IA, consulte Aceder a dados em funções de IA.

Você também pode usar ferramentas MCP (Model Context Protocol) com o .IChatClient Para obter mais informações, consulte Criar um cliente MCP mínimo.

Redução de ferramentas (experimental)

Importante

Esta funcionalidade é experimental e sujeita a alterações.

A redução de ferramentas ajuda a gerir grandes catálogos de ferramentas, cortando-os com base na relevância para o contexto atual da conversa. A IToolReductionStrategy interface define estratégias para reduzir o número de ferramentas enviadas ao modelo. A biblioteca fornece implementações, como EmbeddingToolReductionStrategy, que classificam ferramentas com base na similaridade de incorporação à conversa. Use o método de extensão UseToolReduction para adicionar o processo de simplificação de ferramentas ao seu pipeline de cliente de chat.

Respostas de cache

Se estás familiarizado com cache em .NET, é bom saber que isso Microsoft.Extensions.AI fornece implementações de delegação IChatClient para cache. O DistributedCachingChatClient é um IChatClient que organiza o cache em camadas ao redor de outra instância arbitrária IChatClient. Quando um novo histórico de bate-papo é enviado para o DistributedCachingChatClient, ele o encaminha para o cliente subjacente e, em seguida, armazena em cache a resposta antes de enviá-la de volta ao consumidor. Na próxima vez que o mesmo histórico for enviado, de modo que uma resposta em cache possa ser encontrada no cache, o DistributedCachingChatClient retornará a resposta em cache em vez de encaminhar a solicitação ao longo do pipeline.

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 exemplo depende do pacote NuGet Microsoft.Extensions.Caching.Memory📦. Para mais informações, veja Caching no .NET.

Usar telemetria

Outro exemplo de um cliente de chat de delegação é o OpenTelemetryChatClient. Esta implementação adere ao OpenTelemetry Semantic Conventions for Generative AI systems. Semelhante a outros IChatClient delegadores, ele coloca métricas em camadas e se estende em torno de outras implementações arbitrárias IChatClient .

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);

(O exemplo anterior depende do 📦 pacote NuGet OpenTelemetry.Exporter.Console .)

Como alternativa, o LoggingChatClient e o método correspondente UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) fornecem uma maneira simples de gravar entradas de log em um ILogger para cada solicitação e resposta.

Fornecer opções

Cada chamada para GetResponseAsync ou GetStreamingResponseAsync pode, opcionalmente, fornecer uma instância ChatOptions contendo parâmetros adicionais para a operação. Os parâmetros mais comuns entre modelos e serviços de IA aparecem como propriedades fortemente tipadas no tipo, como ChatOptions.Temperature. Outros parâmetros podem ser fornecidos pelo nome de forma menos estrita, através do ChatOptions.AdditionalProperties dicionário, ou através de um objeto de opções que o fornecedor subjacente compreenda, usando a ChatOptions.RawRepresentationFactory propriedade.

Você também pode especificar as opções ao criar uma IChatClient com a API fluente ChatClientBuilder, encadeando uma chamada ao método de extensão ConfigureOptions(ChatClientBuilder, Action<ChatOptions>). Esse cliente de delegação encapsula outro cliente e invoca o delegado fornecido para preencher uma instância ChatOptions para cada chamada. Por exemplo, para garantir que a propriedade ChatOptions.ModelId seja padronizada para um nome de modelo específico, você pode usar um código como o seguinte:

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" }));

Fluxos de funcionalidade

IChatClient As instâncias podem ser colocadas em camadas para criar um pipeline de componentes que adicionam funcionalidade adicional. Esses componentes podem vir de Microsoft.Extensions.AI, outros pacotes NuGet ou implementações personalizadas. Essa abordagem permite que você aumente o comportamento do IChatClient de várias maneiras para atender às suas necessidades específicas. Considere o seguinte trecho de código que organiza em camadas um cache distribuído, uma invocação de função e o rastreamento OpenTelemetry num cliente de chat de exemplo:

// 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 adicionar mais funcionalidades, pode implementar IChatClient diretamente ou usar a classe DelegatingChatClient. Essa classe serve como base para a criação de clientes de chat que delegam operações a outra instância IChatClient. Simplifica o encadeamento de múltiplos clientes, permitindo que as chamadas passem para um cliente subjacente.

A classe DelegatingChatClient fornece implementações padrão para métodos como GetResponseAsync, GetStreamingResponseAsynce Dispose, que encaminham chamadas para o cliente interno. Uma classe derivada pode então substituir apenas os métodos necessários para aumentar o comportamento, enquanto delega outras chamadas para a implementação base. Essa abordagem é útil para criar clientes de bate-papo flexíveis e modulares que são fáceis de estender e compor.

A seguir está uma classe de exemplo derivada de que usa a biblioteca DelegatingChatClient para fornecer funcionalidade de limitação de taxa.

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);
    }
}

Tal como acontece com outras IChatClient implementações, o RateLimitingChatClient pode ser composto.

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 a composição de tais componentes com outros, os autores de componentes devem criar um método de extensão Use* para registrar o componente em um pipeline. Por exemplo, considere o seguinte UseRateLimiting método de extensão:

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)
        );
}

Tais extensões também podem consultar serviços relevantes a partir do contêiner DI; O IServiceProvider usado pelo pipeline é passado como um 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>())
        );
}

Agora é fácil para o consumidor usar isto no seu processo, por exemplo:

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));

Os métodos de extensão anteriores demonstram como usar um método Use em ChatClientBuilder. ChatClientBuilder também fornece Use sobrecargas que facilitam a escrita desses manipuladores delegados. Por exemplo, no exemplo anterior RateLimitingChatClient, as sobreposições de GetResponseAsync e GetStreamingResponseAsync precisam apenas realizar trabalho antes e depois de delegar para o próximo cliente na cadeia de execução. Para conseguir a mesma coisa sem escrever uma classe personalizada, pode usar uma sobrecarga de Use que aceita um delegado que é utilizado tanto para GetResponseAsync como para GetStreamingResponseAsync, reduzindo o código repetitivo necessário.

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();

Para cenários em que precisas de uma implementação diferente para GetResponseAsync e GetStreamingResponseAsync para gerir os seus tipos únicos de retorno, podes usar o Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken, Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions, IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>) overload que aceita um delegado para cada um.

Injeção de dependência

IChatClientas implementações geralmente são fornecidas a um aplicativo por meio de injeção de dependência (DI). No exemplo seguinte, um IDistributedCache é adicionado ao contentor DI, tal como é um IChatClient. O registro para o IChatClient usa um construtor que cria um pipeline contendo um cliente de cache (que usa um IDistributedCache recuperado de DI) e o cliente de exemplo. O IChatClient injetado pode ser recuperado e utilizado noutra parte da aplicação.

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?"));

A instância e a configuração injetadas podem diferir com base nas necessidades atuais do aplicativo, e vários pipelines podem ser injetados com chaves diferentes.

Clientes sem estado vs. clientes com estado

Os serviços sem estado exigem que todo o histórico de conversas relevante seja enviado de volta em cada solicitação. Por outro lado, os serviços com estado mantêm um registo do histórico e requerem apenas o envio de mensagens adicionais junto a uma solicitação. A IChatClient interface foi projetada para lidar com serviços de IA sem estado e com monitoração de estado.

Ao trabalhar com um serviço sem monitoração de estado, os chamadores mantêm uma lista de todas as mensagens. Eles adicionam todas as mensagens de resposta recebidas e fornecem a lista de volta nas interações subsequentes.

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);
}

Para serviços com estado, talvez já conheça o identificador usado para a conversa relevante. Você pode colocar esse identificador em ChatOptions.ConversationId. O uso segue o mesmo padrão, exceto que não há necessidade de manter um histórico manualmente.

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));
}

Alguns serviços podem oferecer suporte à criação automática de uma ID de conversa para uma solicitação que não tenha uma ou à criação de uma nova ID de conversa que represente o estado atual da conversa após a incorporação da última rodada de mensagens. Nesses casos, pode transferir o ChatResponse.ConversationId para o ChatOptions.ConversationId para solicitações subsequentes. Por exemplo:

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;
}

Se não souberes com antecedência se o serviço é stateless ou stateful, podes verificar a resposta ConversationId e agir com base no seu valor. Se estiver definido, esse valor será propagado para as opções e o histórico será limpo para não reenviar o mesmo histórico novamente. Se a resposta ConversationId não estiver definida, a mensagem de resposta será adicionada ao histórico para que seja enviada de volta ao serviço no próximo 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);
    }
}

Exemplo de Implementação

O exemplo seguinte implementa IChatClient para mostrar a estrutura geral.

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 implementações mais realistas e concretas de IChatClient, veja:

Redução do chat (experimental)

Importante

Esta funcionalidade é experimental e sujeita a alterações.

A redução das conversas ajuda a gerir o histórico de conversas ao limitar o número de mensagens ou ao resumir mensagens antigas quando a conversa ultrapassa um determinado comprimento. A biblioteca Microsoft.Extensions.AI fornece redutores como MessageCountingChatReducer que limitam o número de mensagens não-sistemáticas, e SummarizingChatReducer que resumem automaticamente mensagens mais antigas preservando o contexto.