Dela via


Microsoft.Extensions.AI-bibliotek

.NET-utvecklare måste integrera och interagera med en växande mängd ai-tjänster (artificiell intelligens) i sina appar. Biblioteken Microsoft.Extensions.AI ger en enhetlig metod för att representera generativa AI-komponenter och möjliggöra sömlös integrering och samverkan med olika AI-tjänster. Den här artikeln introducerar biblioteken och innehåller djupgående användningsexempel som hjälper dig att komma igång.

Paketen

📦 Microsoft.Extensions.AI.Abstractions-paketet innehåller kärnutbytestyperna, inklusive IChatClient och IEmbeddingGenerator<TInput,TEmbedding>. Alla .NET-bibliotek som tillhandahåller en LLM-klient kan implementera IChatClient gränssnittet för sömlös integration med konsumerande kod.

📦 Microsoft.Extensions.AI-paketet har ett implicit beroende av Microsoft.Extensions.AI.Abstractions-paketet. Med det här paketet kan du enkelt integrera komponenter som automatisk funktionsverktygsanrop, telemetri och cachelagring i dina program med hjälp av välbekanta beroendeinmatnings- och mellanprogramsmönster. Den tillhandahåller till exempel UseOpenTelemetry(ChatClientBuilder, ILoggerFactory, String, Action<OpenTelemetryChatClient>)-tilläggsmetoden, som lägger till OpenTelemetry-stöd till chattklientpipelinen.

Vilket paket som ska refereras

Bibliotek som erbjuder implementeringar av abstraktionerna refererar vanligtvis bara till Microsoft.Extensions.AI.Abstractions.

För att också få åtkomst till verktyg på högre nivå för att arbeta med generativa AI-komponenter, referera istället till Microsoft.Extensions.AI-paketet (som i sin tur refererar till Microsoft.Extensions.AI.Abstractions). De mest använda programmen och tjänsterna bör referera till Microsoft.Extensions.AI paketet tillsammans med ett eller flera bibliotek som tillhandahåller konkreta implementeringar av abstraktionerna.

Installera programvarupaketen

Information om hur du installerar NuGet-paket finns i dotnet package add or Manage package dependencies in .NET applications (Lägga till eller Hantera paketberoenden i .NET-program).

Exempel på API-användning

Följande underavsnitt visar specifika IChatClient-användningsexempel :

Följande avsnitt visar specifika användningsexempel för IEmbeddingGenerator :

Gränssnittet IChatClient

Gränssnittet IChatClient definierar en klientabstraktion som ansvarar för att interagera med AI-tjänster som tillhandahåller chattfunktioner. Den innehåller metoder för att skicka och ta emot meddelanden med multimodalt innehåll (till exempel text, bilder och ljud), antingen som en fullständig uppsättning eller strömmas stegvis. Dessutom möjliggör det hämtning av starkt typade tjänster som tillhandahålls av klienten eller dess underliggande tjänster.

.NET-bibliotek som tillhandahåller klienter för språkmodeller och tjänster kan tillhandahålla en implementering av IChatClient gränssnittet. Alla användare av gränssnittet kan sedan samverka sömlöst med dessa modeller och tjänster via abstraktionerna. Du kan se en enkel implementering i Exempelimplementeringar av IChatClient och IEmbeddingGenerator.

Begära ett chattsvar

Med en instans av IChatClientkan du anropa IChatClient.GetResponseAsync metoden för att skicka en begäran och få ett svar. Begäran består av ett eller flera meddelanden som var och en består av ett eller flera innehåll. Acceleratormetoder finns för att förenkla vanliga fall, till exempel att skapa en begäran om ett enda textinnehåll.

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

Kärnmetoden IChatClient.GetResponseAsync accepterar en lista med meddelanden. Den här listan representerar historiken för alla meddelanden som ingår i konversationen.

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

Den ChatResponse som returneras från GetResponseAsync visar en lista över ChatMessage instanser som representerar ett eller flera meddelanden som genereras som en del av åtgärden. I vanliga fall finns det bara ett svarsmeddelande, men i vissa situationer kan det finnas flera meddelanden. Meddelandelistan sorteras så att det sista meddelandet i listan representerar det slutliga meddelandet till begäran. Om du vill skicka tillbaka alla dessa svarsmeddelanden till tjänsten i en efterföljande begäran kan du lägga till meddelandena från svaret i meddelandelistan igen.

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

Begär ett svar i direktchatt

Indata till IChatClient.GetStreamingResponseAsync är identiska med indata för GetResponseAsync. Men i stället för att returnera det fullständiga svaret som en del av ett ChatResponse objekt returnerar metoden en IAsyncEnumerable<T> där T är ChatResponseUpdate, vilket ger en ström med uppdateringar som tillsammans utgör det enskilda svaret.

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

Tips/Råd

API:er för direktuppspelning är nästan synonyma med AI-användarupplevelser. C# möjliggör övertygande scenarier med stöd för IAsyncEnumerable<T>, vilket möjliggör ett naturligt och effektivt sätt att strömma data.

Precis som med GetResponseAsynckan du lägga till uppdateringarna från IChatClient.GetStreamingResponseAsync tillbaka till meddelandelistan. Eftersom uppdateringarna är enskilda delar av ett svar kan du använda hjälpverktyg som ToChatResponse(IEnumerable<ChatResponseUpdate>) att skapa en eller flera uppdateringar i en enda ChatResponse instans.

Hjälpare som AddMessages skriver en ChatResponse och extraherar sedan de sammansatta meddelandena från svaret och lägger till dem i en 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);
}

Verktygsanrop

Vissa modeller och tjänster stöder verktygssamtal. Om du vill samla in ytterligare information kan du konfigurera ChatOptions med information om verktyg (vanligtvis .NET-metoder) som modellen kan begära att klienten anropar. I stället för att skicka ett slutligt svar begär modellen ett funktionsanrop med specifika argument. Klienten anropar sedan funktionen och skickar resultatet tillbaka till modellen med konversationshistoriken. Microsoft.Extensions.AI.Abstractions-biblioteket innehåller abstraktioner för olika typer av meddelandeinnehåll, inklusive begäranden om funktionsanrop och resultat. Konsumenterna IChatClient kan interagera direkt med det här innehållet, och Microsoft.Extensions.AI tillhandahåller hjälpverktyg som kan möjliggöra automatisk aktivering av verktygen som svarar på motsvarande förfrågningar. Biblioteken Microsoft.Extensions.AI.Abstractions och Microsoft.Extensions.AI innehåller följande typer:

  • AIFunction: Representerar en funktion som kan beskrivas för en AI-modell och anropas.
  • AIFunctionFactory: Tillhandahåller fabriksmetoder för att skapa AIFunction instanser som representerar .NET-metoder.
  • FunctionInvokingChatClient: Omsluter en IChatClient som en annan IChatClient som lägger till möjligheter för automatisk funktionsanrop.

I följande exempel visas ett slumpmässigt funktionsanrop (det här exemplet beror på 📦 NuGet-paketet 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);
}

Föregående kod:

  • Definierar en funktion med namnet GetCurrentWeather som returnerar en slumpmässig väderprognos.
  • Instansierar en ChatClientBuilder med en OllamaSharp.OllamaApiClient och konfigurerar den att använda funktionsanrop.
  • Anropar GetStreamingResponseAsync på klienten, skickar en prompt och en lista över verktyg som innehåller en funktion som skapats med Create.
  • Itererar över svaret och skriver ut varje uppdatering till konsolen.

Du kan också använda MCP-verktyg (Model Context Protocol) med din IChatClient. Mer information finns i Skapa en minimal MCP-klient.

Cache-svar

Om du är bekant med cachelagring i .NETär det bra att veta att Microsoft.Extensions.AI tillhandahåller andra sådana delegerande IChatClient implementeringar. DistributedCachingChatClient är en IChatClient som lager cache runt en godtycklig annan IChatClient instans. När en ny chatthistorik skickas till DistributedCachingChatClientvidarebefordras den till den underliggande klienten och cachelagrar sedan svaret innan det skickas tillbaka till konsumenten. Nästa gång samma historik skickas, så att ett cachelagrat svar kan hittas i cacheminnet, DistributedCachingChatClient returnerar det cachelagrade svaret i stället för att vidarebefordra begäran längs pipelinen.

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

Det här exemplet beror på 📦 NuGet-paketet Microsoft.Extensions.Caching.Memory . För mer information, se Cachelagring i .NET.

Använda telemetri

Ett annat exempel på en delegerande chattklient är OpenTelemetryChatClient. Den här implementeringen följer OpenTelemetry semantiska konventioner för generativa AI-system. Precis som andra IChatClient delegeringsmän lagrar den mått och sträcker sig över andra godtyckliga IChatClient implementeringar.

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

(Föregående exempel beror på 📦 NuGet-paketet OpenTelemetry.Exporter.Console .)

Alternativt ger LoggingChatClient och motsvarande UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) metod ett enkelt sätt att skriva loggposter till en ILogger för varje begäran och svar.

Ange alternativ

Varje anrop till GetResponseAsync eller GetStreamingResponseAsync kan också ange en ChatOptions instans som innehåller ytterligare parametrar för åtgärden. De vanligaste parametrarna bland AI-modeller och -tjänster visas som starkt skrivna egenskaper för typen, till exempel ChatOptions.Temperature. Andra parametrar kan anges med namn på ett svagt skrivet sätt, via ChatOptions.AdditionalProperties ordlistan eller via en alternativinstans som den underliggande providern förstår via ChatOptions.RawRepresentationFactory egenskapen.

Du kan också ange alternativ när du skapar ett IChatClient med api:et fluent ChatClientBuilder genom att länka ett anrop till ConfigureOptions(ChatClientBuilder, Action<ChatOptions>) tilläggsmetoden. Den här delegerande klienten omsluter en annan klient och anropar det angivna ombudet för att fylla i en ChatOptions instans för varje anrop. Om du till exempel vill se till att egenskapen ChatOptions.ModelId är standard för ett visst modellnamn kan du använda kod som följande:

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

Funktionspipelines

IChatClient instanser kan kombineras för att skapa en pipeline med komponenter som var och en lägger till ytterligare funktioner. Dessa komponenter kan komma från Microsoft.Extensions.AI, andra NuGet-paket eller anpassade implementeringar. Med den här metoden kan du utöka beteendet för IChatClient på olika sätt för att uppfylla dina specifika behov. Överväg följande kodfragment som lagrar en distribuerad cache, funktionsanrop och OpenTelemetry-spårning runt en chattklientexempel:

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

Anpassad IChatClient mellanvara

Om du vill lägga till ytterligare funktioner kan du implementera IChatClient direkt eller använda klassen DelegatingChatClient. Den här klassen fungerar som bas för att skapa chattklienter som delegerar åtgärder till en annan IChatClient instans. Det gör det enklare att länka flera klienter så att anrop kan skickas till en underliggande klient.

Klassen DelegatingChatClient tillhandahåller standardimplementeringar för metoder som GetResponseAsync, GetStreamingResponseAsyncoch Dispose, som vidarebefordrar anrop till den inre klienten. En härledd klass kan sedan bara åsidosätta de metoder som behövs för att utöka beteendet, samtidigt som andra anrop delegeras till basimplementeringen. Den här metoden är användbar för att skapa flexibla och modulära chattklienter som är enkla att utöka och skriva.

Följande är en exempelklass som härleds från DelegatingChatClient som använder biblioteket System.Threading.RateLimiting för att tillhandahålla hastighetsbegränsningsfunktioner.

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

Precis som med andra IChatClient implementeringar kan de RateLimitingChatClient bestå:

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

För att förenkla sammansättningen av sådana komponenter med andra bör komponentförfattare skapa en Use* tilläggsmetod för att registrera komponenten i en pipeline. Tänk till exempel på följande UseRateLimiting tilläggsmetod:

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

Sådana tillägg kan också fråga efter relevanta tjänster från DI-containern. den IServiceProvider som används av pipelinen skickas som en valfri parameter:

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

Nu är det enkelt för konsumenten att använda detta i sin pipeline, till exempel:

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

De tidigare tilläggsmetoderna visar hur du använder en Use metod på ChatClientBuilder. ChatClientBuilder ger Use också överlagringar som gör det enklare att skriva sådana delegeringshanterare. I det tidigare exemplet RateLimitingChatClient behöver överlagringarna av GetResponseAsync och GetStreamingResponseAsync bara utföra arbete före och efter delegering till nästa klient i pipelinen. Om du vill uppnå samma sak utan att skriva en anpassad klass kan du använda en överlagring av Use som accepterar en delegat som används för både GetResponseAsync och GetStreamingResponseAsync, vilket minskar den mall som krävs.

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

För scenarier där du behöver en annan implementering för GetResponseAsync och GetStreamingResponseAsync för att hantera deras unika returtyper kan du använda Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken, Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions, IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>)-överlagringen som accepterar en delegering för varje.

Beroendeinsprutning

IChatClient implementeringar tillhandahålls ofta till ett program via beroendeinmatning (DI). I det här exemplet läggs en IDistributedCache till i DI-containern, liksom en IChatClient. Registreringen för IChatClient använder en konfiguration som skapar en pipeline innehållande en cacheklient (som sedan använder en IDistributedCache hämtad från DI) och en exempelklient. Det injekterade IChatClient kan hämtas och användas någon annanstans i appen.

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

Vilken instans och konfiguration som matas in kan skilja sig beroende på programmets aktuella behov, och flera pipelines kan matas in med olika nycklar.

Tillståndsfria kontra tillståndsfulla klienter

Tillståndslösa tjänster kräver att all relevant konversationshistorik skickas tillbaka på varje begäran. Tillståndskänsliga tjänster håller däremot reda på historiken och kräver att endast ytterligare meddelanden skickas med en begäran. Gränssnittet IChatClient är utformat för att hantera både tillståndslösa och tillståndskänsliga AI-tjänster.

När du arbetar med en tillståndslös tjänst behåller anroparna en lista över alla meddelanden. De lägger till alla mottagna svarsmeddelanden och anger listan igen vid efterföljande interaktioner.

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

För tillståndskänsliga tjänster kanske du redan känner till identifieraren som används för den relevanta konversationen. Du kan placera identifieraren i ChatOptions.ConversationId. Användningen följer sedan samma mönster, förutom att det inte finns något behov av att underhålla en historik manuellt.

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

Vissa tjänster kan ha stöd för att automatiskt skapa ett konversations-ID för en begäran som inte har något, eller skapa ett nytt konversations-ID som representerar konversationens aktuella tillstånd när den sista meddelanderundan har införlivats. I sådana fall kan du överföra ChatResponse.ConversationId över till ChatOptions.ConversationId för efterföljande begäranden. Till exempel:

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

Om du inte vet i förväg om tjänsten är tillståndslös eller tillståndsberoende kan du kontrollera svaret ConversationId och vidta åtgärder baserat på dess värde. Om det anges sprids det värdet till alternativen och historiken rensas för att inte skicka samma historik igen. Om svaret ConversationId inte har angetts läggs svarsmeddelandet till i historiken så att det skickas tillbaka till tjänsten vid nästa tur.

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

Gränssnittet IEmbeddingGenerator

Gränssnittet IEmbeddingGenerator<TInput,TEmbedding> representerar en allmän generator av inbäddningar. För parametrar av allmän typ TInput är typen av indatavärden som bäddas in och TEmbedding är den typ av genererad inbäddning som ärver från Embedding klassen.

Klassen Embedding fungerar som en basklass för inbäddningar som genereras av en IEmbeddingGenerator. Den är utformad för att lagra och hantera metadata och data som är associerade med inbäddningar. Härledda typer, till exempel Embedding<T>, tillhandahåller konkreta inbäddningsvektordata. Till exempel exponerar en Embedding<float>ReadOnlyMemory<float> Vector { get; } egenskap för åtkomst till dess inbäddningsdata.

Gränssnittet IEmbeddingGenerator definierar en metod för att asynkront generera inbäddningar för en samling indatavärden, med valfri konfiguration och stöd för annullering. Den innehåller även metadata som beskriver generatorn och möjliggör hämtning av starkt typade tjänster som kan tillhandahållas av generatorn eller dess underliggande tjänster.

De flesta användare behöver inte implementera IEmbeddingGenerator gränssnittet. Men om du är biblioteksförfattare kan du se en enkel implementering i Exempelimplementeringar av IChatClient och IEmbeddingGenerator.

Skapa inbäddningar

Den primära åtgärden som utförs med en IEmbeddingGenerator<TInput,TEmbedding> är inbäddningsgenerering, vilket utförs med dess GenerateAsync-metod.

using Microsoft.Extensions.AI;
using OllamaSharp;

IEmbeddingGenerator<string, Embedding<float>> generator =
    new OllamaApiClient(new Uri("http://localhost:11434/"), "phi3:mini");

foreach (Embedding<float> embedding in
    await generator.GenerateAsync(["What is AI?", "What is .NET?"]))
{
    Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}

Acceleratortilläggsmetoder finns också för att förenkla vanliga fall, till exempel att generera en inbäddningsvektor från en enda indata.

ReadOnlyMemory<float> vector = await generator.GenerateVectorAsync("What is AI?");

Funktionalitets-pipelines

Precis som med IChatClientkan implementeringar av IEmbeddingGenerator läggas i lager. Microsoft.Extensions.AI tillhandahåller en delegeringsimplementering för IEmbeddingGenerator cachelagring och telemetri.

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

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

// Explore changing the order of the intermediate "Use" calls to see
// what impact that has on what gets cached and traced.
IEmbeddingGenerator<string, Embedding<float>> generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(
        new OllamaApiClient(new Uri("http://localhost:11434/"), "phi3:mini"))
    .UseDistributedCache(
        new MemoryDistributedCache(
            Options.Create(new MemoryDistributedCacheOptions())))
    .UseOpenTelemetry(sourceName: sourceName)
    .Build();

GeneratedEmbeddings<Embedding<float>> embeddings = await generator.GenerateAsync(
[
    "What is AI?",
    "What is .NET?",
    "What is AI?"
]);

foreach (Embedding<float> embedding in embeddings)
{
    Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}

Med IEmbeddingGenerator kan du skapa anpassade mellanprogram som utökar funktionerna i en IEmbeddingGenerator. Klassen DelegatingEmbeddingGenerator<TInput,TEmbedding> är en implementering av IEmbeddingGenerator<TInput, TEmbedding>-gränssnittet som fungerar som en basklass för att skapa inbäddningsgeneratorer som delegerar sina åtgärder till en annan IEmbeddingGenerator<TInput, TEmbedding> instans. Det gör det möjligt att länka flera generatorer i valfri ordning och skicka anrop till en underliggande generator. Klassen tillhandahåller standardimplementeringar för metoder som GenerateAsync och Dispose, som vidarebefordrar anropen till den inre generatorinstansen, vilket möjliggör flexibel och modulär inbäddningsgenerering.

Följande är ett exempel på en implementering av en sådan delegerande inbäddningsgenerator som begränsar inbäddningsgenereringsbegäranden:

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

public class RateLimitingEmbeddingGenerator(
    IEmbeddingGenerator<string, Embedding<float>> innerGenerator, RateLimiter rateLimiter)
        : DelegatingEmbeddingGenerator<string, Embedding<float>>(innerGenerator)
{
    public override async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
        IEnumerable<string> values,
        EmbeddingGenerationOptions? 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.GenerateAsync(values, options, cancellationToken);
    }

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

        base.Dispose(disposing);
    }
}

Detta kan sedan läggas runt ett godtyckligt IEmbeddingGenerator<string, Embedding<float>> värde för att begränsa alla inbäddningsgenereringsåtgärder.

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

IEmbeddingGenerator<string, Embedding<float>> generator =
    new RateLimitingEmbeddingGenerator(
        new OllamaApiClient(new Uri("http://localhost:11434/"), "phi3:mini"),
        new ConcurrencyLimiter(new()
        {
            PermitLimit = 1,
            QueueLimit = int.MaxValue
        }));

foreach (Embedding<float> embedding in
    await generator.GenerateAsync(["What is AI?", "What is .NET?"]))
{
    Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}

På så sätt kan RateLimitingEmbeddingGenerator kombineras med andra IEmbeddingGenerator<string, Embedding<float>> instanser för att tillhandahålla gränssättningsfunktionalitet.

Skapa med Microsoft.Extensions.AI

Du kan börja skapa med Microsoft.Extensions.AI på följande sätt:

  • Biblioteksutvecklare: Om du äger bibliotek som tillhandahåller klienter för AI-tjänster bör du överväga att implementera gränssnitten i dina bibliotek. På så sätt kan användarna enkelt integrera ditt NuGet-paket via abstraktionerna. Exempelimplementeringar finns i Exempelimplementeringar av IChatClient och IEmbeddingGenerator.
  • Service-konsumenter: Om du utvecklar bibliotek som använder AI-tjänster bör du använda abstraktionerna i stället för att hårdkoda till en specifik AI-tjänst. Den här metoden ger dina konsumenter flexibiliteten att välja önskad leverantör.
  • Programutvecklare: Använd abstraktionerna för att förenkla integreringen i dina appar. Detta möjliggör portabilitet mellan modeller och tjänster, underlättar testning och modellering, utnyttjar mellanprogram som tillhandahålls av ekosystemet och upprätthåller ett konsekvent API i hela appen, även om du använder olika tjänster i olika delar av ditt program.
  • Ecosystem-deltagare: Om du är intresserad av att bidra till ekosystemet kan du skriva anpassade mellanprogramskomponenter.

Fler exempel finns i GitHub-lagringsplatsen dotnet/ai-samples . Ett exempel från slutpunkt till slutpunkt finns i eShopSupport.

Se även