Udostępnij przez


Korzystanie z interfejsu IChatClient

Interfejs IChatClient definiuje abstrakcję klienta odpowiedzialną za interakcję z usługami sztucznej inteligencji, które zapewniają możliwości czatu. Zawiera metody wysyłania i odbierania wiadomości z treściami multimodalnymi (takimi jak tekst, obrazy i dźwięk) jako kompletne zestawy lub strumieniowo w sposób przyrostowy. Ponadto umożliwia pobieranie silnie typowanych usług udostępnianych przez klienta lub jego bazowe usługi.

Biblioteki platformy .NET, które udostępniają klientom modele językowe i usługi, mogą zapewnić implementację interfejsu IChatClient . Wszyscy użytkownicy interfejsu mogą bezproblemowo współpracować z tymi modelami i usługami za pośrednictwem abstrakcji. Przykłady można znaleźć w sekcji Przykłady implementacji .

Żądanie odpowiedzi na czat

Za pomocą wystąpienia IChatClient można wywołać metodę IChatClient.GetResponseAsync, aby wysłać żądanie i uzyskać odpowiedź. Żądanie składa się z co najmniej jednego komunikatu, z których każda składa się z co najmniej jednego fragmentu zawartości. Istnieją metody akceleratora, aby uprościć typowe przypadki, takie jak konstruowanie żądania dla pojedynczego fragmentu zawartości tekstowej.

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

Podstawowa metoda IChatClient.GetResponseAsync akceptuje listę komunikatów. Ta lista reprezentuje historię wszystkich wiadomości, które są częścią konwersacji.

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

Element ChatResponse zwracany z GetResponseAsync ujawnia listę wystąpień ChatMessage, które reprezentują jeden lub więcej komunikatów wygenerowanych w ramach operacji. W typowych przypadkach istnieje tylko jeden komunikat odpowiedzi, ale w niektórych sytuacjach może istnieć wiele komunikatów. Lista komunikatów jest uporządkowana, tak że ostatni komunikat na liście reprezentuje finalną odpowiedź na żądanie. Aby przekazać wszystkie te komunikaty odpowiedzi z powrotem do usługi w kolejnym żądaniu, możesz dodać komunikaty z odpowiedzi z powrotem do listy komunikatów.

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

Zamów odpowiedź na czat na żywo

Dane wejściowe IChatClient.GetStreamingResponseAsync są identyczne z danymi GetResponseAsync. Jednak zamiast zwracać pełną odpowiedź w ramach obiektu ChatResponse metoda zwraca IAsyncEnumerable<T>, w której T jest ChatResponseUpdate, zapewniając strumień aktualizacji, które zbiorczo tworzą pojedynczą odpowiedź.

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

Wskazówka

Interfejsy API przesyłania strumieniowego są niemal synonimem doświadczeń użytkownika związanych ze sztuczną inteligencją. Język C# umożliwia interesujące scenariusze dzięki obsłudze IAsyncEnumerable<T>, co pozwala na naturalne i wydajne strumieniowanie danych.

Podobnie jak w przypadku GetResponseAsync programu, możesz dodać aktualizacje z IChatClient.GetStreamingResponseAsync z powrotem do listy komunikatów. Ponieważ aktualizacje są poszczególnymi elementami odpowiedzi, możesz użyć takich pomocników jak ToChatResponse(IEnumerable<ChatResponseUpdate>) do tworzenia jednej lub więcej aktualizacji w jednym wystąpieniu ChatResponse.

Pomocnicy, tacy jak AddMessages, tworzą ChatResponse, a następnie wyodrębniają skomponowane komunikaty z odpowiedzi i dodają je do listy.

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

Uruchamianie narzędzi

Niektóre modele i usługi obsługują wywoływanie narzędzi. Aby zebrać dodatkowe informacje, można skonfigurować ChatOptions informacjami o narzędziach (zwykle metodach .NET), które model może poprosić klienta o wywołanie. Zamiast wysyłać ostateczną odpowiedź, model żąda wywołania funkcji z określonymi argumentami. Następnie klient wywołuje funkcję i wysyła wyniki z powrotem do modelu z historią konwersacji. Biblioteka Microsoft.Extensions.AI.Abstractions zawiera abstrakcje dla różnych typów zawartości komunikatów, w tym żądania wywołań funkcji i wyniki. Podczas gdy IChatClient konsumenci mogą bezpośrednio korzystać z tej zawartości, Microsoft.Extensions.AI udostępnia pomocników, którzy mogą automatycznie uruchamiać narzędzia w odpowiedzi na odpowiednie żądania. Biblioteki Microsoft.Extensions.AI.Abstractions i Microsoft.Extensions.AI udostępniają następujące typy:

  • AIFunction: reprezentuje funkcję, którą można opisać w modelu sztucznej inteligencji i wywołać.
  • AIFunctionFactory: Udostępnia metody fabryczne do tworzenia wystąpień AIFunction reprezentujących metody .NET.
  • FunctionInvokingChatClient: opakowuje IChatClient element jako inny IChatClient , który dodaje funkcje automatycznego wywołania funkcji.

W poniższym przykładzie pokazano wywołanie funkcji losowej (ten przykład zależy od 📦 pakietu 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);
}

Poprzedni kod:

  • Definiuje funkcję o nazwie GetCurrentWeather, która zwraca losową prognozę pogody.
  • Tworzy wystąpienie ChatClientBuilder za pomocą OllamaSharp.OllamaApiClient i konfiguruje je do wywoływania funkcji.
  • Wywołuje GetStreamingResponseAsync na kliencie, przekazując monit oraz listę narzędzi, w której znajduje się funkcja utworzona za pomocą Create.
  • Iteruje przez odpowiedź, wyświetlając każdą aktualizację na konsoli.

Aby uzyskać więcej informacji na temat tworzenia funkcji sztucznej inteligencji, zobacz Access data in AI functions (Uzyskiwanie dostępu do danych w funkcjach sztucznej inteligencji).

Możesz również użyć narzędzi protokołu MCP (Model Context Protocol) za pomocą polecenia IChatClient. Aby uzyskać więcej informacji, zobacz Tworzenie minimalnego klienta MCP.

Redukcja narzędzi (eksperymentalna)

Ważne

Ta funkcja jest eksperymentalna i może ulec zmianie.

Redukcja narzędzi ułatwia zarządzanie dużymi katalogami narzędzi przez przycinanie ich na podstawie istotności bieżącego kontekstu konwersacji. Interfejs IToolReductionStrategy definiuje strategie zmniejszania liczby narzędzi wysyłanych do modelu. Biblioteka udostępnia implementacje takie jak EmbeddingToolReductionStrategy te klasyfikuje narzędzia, osadzając podobieństwo do konwersacji. Użyj metody rozszerzenia UseToolReduction, aby dodać redukcję narzędzi do potoku czatu klienta.

Odpowiedzi z pamięci podręcznej

Jeśli znasz buforowanie na platformie .NET, dobrze jest wiedzieć, że Microsoft.Extensions.AI zapewnia delegowanie IChatClient implementacji buforowania. DistributedCachingChatClient to IChatClient, który nakłada warstwę buforowania na inne dowolne wystąpienie IChatClient. Po przesłaniu nowej historii czatu do DistributedCachingChatClient, przekazuje ją do bazowego klienta, a następnie buforuje odpowiedź przed wysłaniem jej z powrotem do użytkownika. Przy następnym nadesłaniu tego samego zapytania, jeśli w pamięci podręcznej znajduje się buforowana odpowiedź, DistributedCachingChatClient zwraca buforowaną odpowiedź zamiast przesyłać dalej żądanie wzdłuż potoku.

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

Ten przykład zależy 📦 od pakietu NuGet Microsoft.Extensions.Caching.Memory . Aby uzyskać więcej informacji, zobacz pamięć podręczną w .NET.

Korzystanie z telemetrii

Innym przykładem delegowania klienta czatu jest OpenTelemetryChatClient. Ta implementacja jest zgodna z konwencjami semantycznymi OpenTelemetry dla systemów generowania sztucznej inteligencji. Podobnie jak w przypadku innych delegatów IChatClient, nakłada metryki i rozciąga się wokół innych dowolnych implementacji 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);

(Powyższy przykład zależy 📦 od pakietu NuGet OpenTelemetry.Exporter.Console ).

Alternatywnie, metoda LoggingChatClient i odpowiadająca jej metoda UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) umożliwiają łatwe zapisywanie logów ILogger dla każdego żądania i odpowiedzi.

Podaj opcje

Każde wywołanie GetResponseAsync lub GetStreamingResponseAsync może opcjonalnie dostarczyć wystąpienie ChatOptions zawierające dodatkowe parametry dla operacji. Najbardziej powszechne parametry modeli i usług sztucznej inteligencji są wyświetlane jako silnie typizowane właściwości typu, takie jak ChatOptions.Temperature. Inne parametry mogą być dostarczane po nazwie w sposób słabo typizowany, za pośrednictwem słownika ChatOptions.AdditionalProperties, lub przy użyciu wystąpienia opcji, które rozumie dostawca bazowy, za pośrednictwem właściwości ChatOptions.RawRepresentationFactory.

Opcje można również określić podczas budowania IChatClient przy użyciu płynnego interfejsu API ChatClientBuilder, łącząc wywołanie metody rozszerzeń ConfigureOptions(ChatClientBuilder, Action<ChatOptions>). Ten delegujący klient opakowuje innego klienta i wywołuje dostarczonego delegata, aby wypełniał wystąpienie ChatOptions przy każdym wywołaniu. Aby na przykład upewnić się, że właściwość ChatOptions.ModelId jest domyślnie ustawiona na określoną nazwę modelu, możesz użyć kodu podobnego do następującego:

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

Linie funkcjonalności

IChatClient instancje mogą być układane warstwowo, aby stworzyć ciąg składników, z których każdy dodaje dodatkowe funkcjonalności. Te składniki mogą pochodzić z Microsoft.Extensions.AI, innych pakietów NuGet lub niestandardowych implementacji. Takie podejście pozwala rozszerzyć zachowanie IChatClient na różne sposoby, aby spełnić określone potrzeby. Rozważmy następujący fragment kodu, który organizuje rozproszoną pamięć podręczną, wywołanie funkcji i śledzenie narzędzia OpenTelemetry w ramach przykładowego klienta czatu.

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

Niestandardowe middleware IChatClient

Aby dodać dodatkowe funkcje, możesz zaimplementować IChatClient bezpośrednio lub użyć klasy DelegatingChatClient. Ta klasa służy jako podstawa do tworzenia klientów czatu, którzy delegują operacje do innego wystąpienia IChatClient. Upraszcza łączenie wielu klientów w łańcuch, co pozwala na przekazywanie wywołań do podstawowego klienta.

Klasa DelegatingChatClient udostępnia domyślne implementacje metod, takich jak GetResponseAsync, GetStreamingResponseAsynci Dispose, które przekazują wywołania do klienta wewnętrznego. Klasa pochodna może następnie zastąpić tylko metody, których potrzebuje, aby rozszerzyć zachowanie, jednocześnie delegując inne wywołania do implementacji podstawowej. Takie podejście jest przydatne w przypadku tworzenia elastycznych i modułowych klientów czatów, które można łatwo rozszerzać i tworzyć.

Poniżej przedstawiono przykładową klasę pochodną DelegatingChatClient , która używa biblioteki System.Threading.RateLimiting w celu zapewnienia funkcji ograniczania szybkości.

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

Podobnie jak w przypadku innych implementacji IChatClient, RateLimitingChatClient może być złożony.

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

Aby uprościć kompozycję takich składników z innymi, autorzy składników powinni utworzyć metodę rozszerzającą Use* w celu zarejestrowania składnika w potoku. Rozważmy na przykład następującą UseRateLimiting metodę rozszerzenia:

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

Takie rozszerzenia mogą również wysyłać zapytania o odpowiednie usługi z kontenera DI; IServiceProvider używany przez pipeline jest przekazywany jako opcjonalny parametr:

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

Teraz konsumentowi łatwo jest użyć tego w swojej linii przetwarzania, na przykład:

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

Poprzednie metody rozszerzenia pokazują używanie metody Use na ChatClientBuilder. ChatClientBuilder Udostępnia Use również przeciążenia, które ułatwiają pisanie takich procedur obsługi delegowania. Na przykład we wcześniejszym przykładzie RateLimitingChatClient, przesłonięcia GetResponseAsync i GetStreamingResponseAsync muszą wykonać swoją pracę tylko przed i po delegowaniu do następnego klienta w linii przetwarzania. Aby osiągnąć to samo bez konieczności pisania klasy niestandardowej, można użyć przeciążenia Use, które akceptuje delegat funkcji używany zarówno do GetResponseAsync, jak i GetStreamingResponseAsync, zmniejszając wymaganą ilość kodu powtarzalnego.

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

W scenariuszach, w których potrzebujesz innej implementacji dla GetResponseAsync i GetStreamingResponseAsync, aby obsługiwać ich różne typy zwracane, można użyć przeciążenia Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken, Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions, IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>), które akceptuje delegata dla każdego z nich.

Wstrzykiwanie zależności

IChatClient implementacje są często dostarczane do aplikacji za pośrednictwem wstrzykiwania zależności (DI). W poniższym przykładzie element IDistributedCache jest dodawany do kontenera DI, podobnie jak element IChatClient. Rejestracja dla IChatClient używa konstruktora, który tworzy potok zawierający klienta buforowania (następnie używa IDistributedCache pobranego z DI) oraz klienta przykładowego. Wstrzyknięty IChatClient można pobrać i wykorzystać w innych miejscach aplikacji.

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

Wprowadzone wystąpienie i konfiguracja mogą się różnić w zależności od bieżących potrzeb aplikacji, a wiele potoków można wstrzykiwać za pomocą różnych kluczy.

Klienci bezstanowi a stanowe

Usługi bezstanowe, wymagają przesłania całej historii konwersacji za każdym razem, gdy wysyłane jest żądanie. Z kolei usługi stanowe śledzą historię i wymagają wysyłania tylko dodatkowych komunikatów z żądaniem. Interfejs IChatClient jest przeznaczony do obsługi bezstanowych i stanowych usług sztucznej inteligencji.

Podczas pracy z usługą bezstanową wywołujący przechowują listę wszystkich komunikatów. Dodają wszystkie odebrane wiadomości odpowiedzi i przekazują listę przy kolejnych interakcjach.

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

W przypadku usług stanowych możesz już znać identyfikator używany do odpowiedniej konwersacji. Możesz umieścić ten identyfikator w pliku ChatOptions.ConversationId. Następnie użycie jest zgodne z tym samym wzorcem, z tym że nie ma potrzeby ręcznego prowadzenia historii.

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

Niektóre usługi mogą obsługiwać automatyczne tworzenie identyfikatora konwersacji dla żądania, które go nie ma, lub utworzenie nowego identyfikatora konwersacji reprezentującego bieżący stan konwersacji po włączeniu ostatniej rundy wiadomości. W takich przypadkach można przenieść ChatResponse.ConversationId do ChatOptions.ConversationId dla kolejnych żądań. Przykład:

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

Jeśli nie wiesz z góry, czy usługa jest bezstanowa czy stanowa, możesz sprawdzić odpowiedź ConversationId i reagować na podstawie jej wartości. Jeśli jest ustawiony, ta wartość jest propagowana do opcji, a historia zostanie wyczyszczona, aby nie wysyłać ponownie tej samej historii. Jeśli odpowiedź ConversationId nie jest ustawiona, komunikat odpowiedzi zostanie dodany do historii, aby był wysyłany z powrotem do usługi w następnym kroku.

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

Przykład wdrożenia

Poniższy przykład implementuje IChatClient , aby pokazać ogólną strukturę.

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() { }
}

Aby uzyskać bardziej realistyczne, konkretne implementacje programu IChatClient, zobacz:

Redukcja czatów (eksperymentalna)

Ważne

Ta funkcja jest eksperymentalna i może ulec zmianie.

Redukcja czatu pomaga zarządzać historią konwersacji, ograniczając liczbę wiadomości lub podsumowywanie starszych wiadomości, gdy konwersacja przekracza określoną długość. Biblioteka Microsoft.Extensions.AI udostępnia takie reduktory, jak MessageCountingChatReducer to ogranicza liczbę komunikatów niesystemowych i SummarizingChatReducer automatycznie podsumowuje starsze komunikaty przy zachowaniu kontekstu.