Delen via


Kunstmatige intelligentie in .NET (preview)

Met een groeiende verscheidenheid aan ai-services (kunstmatige intelligentie) die beschikbaar zijn, hebben ontwikkelaars een manier nodig om deze services in hun .NET-toepassingen te integreren en ermee te communiceren. De Microsoft.Extensions.AI-bibliotheek biedt een uniforme benadering voor het vertegenwoordigen van generatieve AI-onderdelen, waardoor naadloze integratie en interoperabiliteit met verschillende AI-services mogelijk is. In dit artikel wordt de bibliotheek geïntroduceerd en vindt u installatie-instructies en gebruiksvoorbeelden om u op weg te helpen.

Het pakket installeren

Als u het 📦 Microsoft.Extensions.AI NuGet-pakket wilt installeren, gebruikt u de .NET CLI of voegt u rechtstreeks een pakketreferentie toe aan uw C#-projectbestand:

dotnet add package Microsoft.Extensions.AI --prelease

Zie dotnet-pakket toevoegen of Pakketafhankelijkheden beheren in .NET-toepassingenvoor meer informatie.

Gebruiksvoorbeelden

De IChatClient-interface definieert een clientabstractie die verantwoordelijk is voor interactie met AI-services die chatmogelijkheden bieden. Het bevat methoden voor het verzenden en ontvangen van berichten met multimodale inhoud (zoals tekst, afbeeldingen en audio), als een volledige set of incrementeel gestreamd. Daarnaast biedt het metagegevensinformatie over de client en maakt het mogelijk sterk getypte services op te halen.

Belangrijk

Zie AI voor .NET-ontwikkelaarsvoor meer gebruiksvoorbeelden en praktijkscenario's.

In deze sectie

De IChatClient-interface

In het volgende voorbeeld wordt IChatClient geïmplementeerd om de algemene structuur weer te geven.

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<ChatCompletion> CompleteAsync(
        IList<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()
        {
            Role = ChatRole.Assistant,
            Text = responses[Random.Shared.Next(responses.Length)],
        }]);
    }

    public async IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(
        IList<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 StreamingChatCompletionUpdate
            {
                Role = ChatRole.Assistant,
                Text = 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() { }
}

In de volgende NuGet-pakketten vindt u andere concrete implementaties van IChatClient:

Chatvoltooiing aanvragen

Als u een voltooiing wilt aanvragen, roept u de IChatClient.CompleteAsync-methode aan. De aanvraag bestaat uit een of meer berichten, die elk bestaan uit een of meer inhoudsonderdelen. Er bestaan acceleratormethoden om veelvoorkomende gevallen te vereenvoudigen, zoals het samenstellen van een aanvraag voor één stuk tekstinhoud.

using Microsoft.Extensions.AI;

IChatClient client = new SampleChatClient(
    new Uri("http://coolsite.ai"), "target-ai-model");

var response = await client.CompleteAsync("What is AI?");

Console.WriteLine(response.Message);

De kernmethode IChatClient.CompleteAsync accepteert een lijst met berichten. Deze lijst vertegenwoordigt de geschiedenis van alle berichten die deel uitmaken van het gesprek.

using Microsoft.Extensions.AI;

IChatClient client = new SampleChatClient(
    new Uri("http://coolsite.ai"), "target-ai-model");

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

Elk bericht in de geschiedenis wordt vertegenwoordigd door een ChatMessage-object. De klasse ChatMessage biedt een ChatMessage.Role eigenschap die de rol van het bericht aangeeft. Standaard wordt de ChatRole.User gebruikt. De volgende rollen zijn beschikbaar:

  • ChatRole.Assistant: geeft aan of stelt het gedrag van de assistent in.
  • ChatRole.System: geeft antwoorden op door het systeem geïnstrueerde, door de gebruiker gevraagd invoer.
  • ChatRole.Tool: bevat aanvullende informatie en verwijzingen voor voltooiing van chats.
  • ChatRole.User: biedt invoer voor voltooiing van chats.

Elk chatbericht wordt geïnstantieerd, waarbij aan de eigenschap Contents een nieuwe TextContentwordt toegewezen. Er zijn verschillende typen inhoud die kunnen worden weergegeven, zoals een eenvoudige tekenreeks of een complexer object dat een multimodaal bericht vertegenwoordigt met tekst, afbeeldingen en audio:

Chatvoltooiing aanvragen met streaming

De invoer voor IChatClient.CompleteStreamingAsync is identiek aan die van CompleteAsync. In plaats van het volledige antwoord als onderdeel van een ChatCompletion-object te retourneren, retourneert de methode echter een IAsyncEnumerable<T> waarbij T is StreamingChatCompletionUpdate, waardoor een stroom updates wordt geboden die gezamenlijk het enige antwoord vormen.

using Microsoft.Extensions.AI;

IChatClient client = new SampleChatClient(
    new Uri("http://coolsite.ai"), "target-ai-model");

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

Fooi

Streaming-API's zijn bijna synoniem voor AI-gebruikerservaringen. C# maakt aantrekkelijke scenario's mogelijk met de IAsyncEnumerable<T> ondersteuning, waardoor gegevens op een natuurlijke en efficiënte manier kunnen worden gestreamd.

Aanroepen van hulpprogramma's

Sommige modellen en services ondersteunen -hulpprogramma's waarmee-functies kunnen worden aangeroepen, waarbij aanvragen hulpmiddelen kunnen bevatten voor het model om aanvullende informatie te verzamelen. In plaats van een definitief antwoord te verzenden, vraagt het model een functie-aanroep aan met specifieke argumenten. De client roept vervolgens de functie aan en stuurt de resultaten terug naar het model, samen met de gespreksgeschiedenis. De Microsoft.Extensions.AI-bibliotheek bevat abstracties voor verschillende berichtinhoudstypen, waaronder aanvragen voor functieoproepen en resultaten. Hoewel consumenten rechtstreeks met deze inhoud kunnen communiceren, Microsoft.Extensions.AI deze interacties automatiseren en het volgende bieden:

  • AIFunction: Vertegenwoordigt een functie die kan worden beschreven in een AI-service en die kan worden aangeroepen.
  • AIFunctionFactory: biedt fabrieksmethoden voor het maken van veelgebruikte implementaties van AIFunction.
  • FunctionInvokingChatClient: verpakt een IChatClient om automatische functieaanroepmogelijkheden toe te voegen.

Bekijk het volgende voorbeeld waarin een willekeurige functie-aanroep wordt gedemonstreert:

using System.ComponentModel;
using Microsoft.Extensions.AI;

[Description("Gets the current weather")]
string GetCurrentWeather() => Random.Shared.NextDouble() > 0.5
    ? "It's sunny"
    : "It's raining";

IChatClient client = new ChatClientBuilder(
        new OllamaChatClient(new Uri("http://localhost:11434"), "llama3.1"))
    .UseFunctionInvocation()
    .Build();

var response = client.CompleteStreamingAsync(
    "Should I wear a rain coat?",
    new() { Tools = [AIFunctionFactory.Create(GetCurrentWeather)] });

await foreach (var update in response)
{
    Console.Write(update);
}

Het voorgaande voorbeeld is afhankelijk van het 📦 Microsoft.Extensions.AI.Ollama NuGet-pakket.

De voorgaande code:

  • Definieert een functie met de naam GetCurrentWeather die een willekeurige weersvoorspelling retourneert.
    • Deze functie is versierd met een DescriptionAttribute, die wordt gebruikt om een beschrijving van de functie aan de AI-dienst te geven.
  • Instantieert een ChatClientBuilder met een OllamaChatClient en configureert deze voor het gebruik van functieaanroepen.
  • Roept CompleteStreamingAsync aan op de client, waarbij een prompt en een lijst met hulpprogramma's worden doorgegeven die een functie bevat die is gemaakt met Create.
  • Doorloopt het antwoord en drukt elke update af op de console.

Reacties in cache

Als u bekend bent met caching in .NET, is het goed om te weten dat Microsoft.Extensions.AI andere IChatClient implementaties biedt. De DistributedCachingChatClient is een IChatClient dat caching lagen legt rondom een ander willekeurig IChatClient exemplaar. Wanneer een unieke chatgeschiedenis wordt verzonden naar de DistributedCachingChatClient, wordt deze doorgestuurd naar de onderliggende client en wordt het antwoord vervolgens in de cache opgeslagen voordat deze naar de consument wordt verzonden. De volgende keer dat dezelfde prompt wordt verzonden, zodat een antwoord in de cache kan worden gevonden, retourneert de DistributedCachingChatClient het antwoord in de cache in plaats van de aanvraag door te sturen langs de pijplijn.

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

var sampleChatClient = new SampleChatClient(
    new Uri("http://coolsite.ai"), "target-ai-model");

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.CompleteStreamingAsync(prompt))
    {
        Console.Write(update);
    }

    Console.WriteLine();
}

Het voorgaande voorbeeld is afhankelijk van het nuget-pakket 📦 Microsoft.Extensions.Caching.Memory. Zie Caching in .NETvoor meer informatie.

Telemetrie gebruiken

Een ander voorbeeld van een delegering van een chatclient is de OpenTelemetryChatClient. Deze implementatie voldoet aan de OpenTelemetry Semantic Conventions voor generatieve AI-systemen. Net als bij andere IChatClient-delegeerders, worden metrische gegevens en spans gelaagd rond elke onderliggende IChatClient-implementatie, waardoor de waarneembaarheid wordt verbeterd.

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

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

var sampleChatClient = new SampleChatClient(
    new Uri("http://coolsite.ai"), "target-ai-model");

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

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

Het voorgaande voorbeeld is afhankelijk van het 📦 OpenTelemetry.Exporter.Console NuGet-pakket.

Opties opgeven

Elke aanroep naar CompleteAsync of CompleteStreamingAsync kan eventueel een ChatOptions exemplaar met extra parameters voor de bewerking opgeven. De meest voorkomende parameters onder AI-modellen en -services verschijnen als sterk getypte eigenschappen van het type, zoals ChatOptions.Temperature. Andere parameters kunnen op naam worden opgegeven op een zwak getypte manier via de ChatOptions.AdditionalProperties woordenlijst.

U kunt ook opties opgeven bij het bouwen van een IChatClient met de fluent ChatClientBuilder-API en het koppelen van een aanroep naar de ConfigureOptions-extensiemethode. Deze delegeringsclient verpakt een andere client en roept de opgegeven gemachtigde aan om een ChatOptions exemplaar voor elke aanroep te vullen. Als u er bijvoorbeeld voor wilt zorgen dat de eigenschap ChatOptions.ModelId standaard wordt ingesteld op een bepaalde modelnaam, kunt u code als volgt gebruiken:

using Microsoft.Extensions.AI;

IChatClient client = new ChatClientBuilder(
        new OllamaChatClient(new Uri("http://localhost:11434")))
    .ConfigureOptions(options => options.ModelId ??= "phi3")
    .Build();

// will request "phi3"
Console.WriteLine(await client.CompleteAsync("What is AI?"));

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

Het voorgaande voorbeeld is afhankelijk van het 📦 Microsoft.Extensions.AI.Ollama NuGet-pakket.

Functionaliteitspijplijnen

IChatClient exemplaren kunnen worden gelaagd om een pijplijn met onderdelen te maken, waarbij elke specifieke functionaliteit wordt toegevoegd. Deze onderdelen kunnen afkomstig zijn van Microsoft.Extensions.AI, andere NuGet-pakketten of aangepaste implementaties. Met deze aanpak kunt u het gedrag van de IChatClient op verschillende manieren uitbreiden om te voldoen aan uw specifieke behoeften. Bekijk de volgende voorbeeldcode waarmee een gedistribueerde cache, functieaanroep en OpenTelemetry-tracering rond een voorbeeld-chatclient worden gelaagd:

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

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

// Explore changing the order of the intermediate "Use" calls to see that impact
// that has on what gets cached, traced, etc.
IChatClient client = new ChatClientBuilder(
        new OllamaChatClient(new Uri("http://localhost:11434"), "llama3.1"))
    .UseDistributedCache(new MemoryDistributedCache(
        Options.Create(new MemoryDistributedCacheOptions())))
    .UseFunctionInvocation()
    .UseOpenTelemetry(
        sourceName: sourceName,
        configure: static c => c.EnableSensitiveData = true)
    .Build();

ChatOptions options = new()
{
    Tools =
    [
        AIFunctionFactory.Create(
            () => Random.Shared.NextDouble() > 0.5 ? "It's sunny" : "It's raining",
            name: "GetCurrentWeather",
            description: "Gets the current weather")
    ]
};

for (int i = 0; i < 3; ++i)
{
    List<ChatMessage> history =
    [
        new ChatMessage(ChatRole.System, "You are a helpful AI assistant"),
        new ChatMessage(ChatRole.User, "Do I need an umbrella?")
    ];

    Console.WriteLine(await client.CompleteAsync(history, options));
}

Het voorgaande voorbeeld is afhankelijk van de volgende NuGet-pakketten:

Aangepaste IChatClient middleware

Als u extra functionaliteit wilt toevoegen, kunt u IChatClient rechtstreeks implementeren of de DelegatingChatClient-klasse gebruiken. Deze klasse fungeert als basis voor het maken van chatclients die bewerkingen delegeren aan een ander IChatClient exemplaar. Het vereenvoudigt het koppelen van meerdere clients, waardoor aanroepen naar een onderliggende client kunnen worden doorgegeven.

De DelegatingChatClient-klasse biedt standaard implementaties voor methoden zoals CompleteAsync, CompleteStreamingAsyncen Dispose, waarmee aanroepen naar de interne client worden doorgestuurd. U kunt deze klasse afleiden en alleen de methoden negeren die u nodig hebt om het gedrag te verbeteren, terwijl u andere aanroepen naar de basis-implementatie delegeert. Met deze aanpak kunt u flexibele en modulaire chatclients maken die eenvoudig kunnen worden uitgebreid en samengesteld.

Hier volgt een voorbeeldklasse die is afgeleid van DelegatingChatClient om functionaliteit voor snelheidsbeperking te bieden, waarbij gebruik wordt gemaakt van de RateLimiter:

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<ChatCompletion> CompleteAsync(
        IList<ChatMessage> chatMessages,
        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.CompleteAsync(chatMessages, options, cancellationToken)
            .ConfigureAwait(false);
    }

    public override async IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(
        IList<ChatMessage> chatMessages,
        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.CompleteStreamingAsync(chatMessages, options, cancellationToken)
            .ConfigureAwait(false))
        {
            yield return update;
        }
    }

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

        base.Dispose(disposing);
    }
}

Het voorgaande voorbeeld is afhankelijk van het 📦 System.Threading.RateLimiting NuGet-pakket. Samenstelling van de RateLimitingChatClient met een andere client is eenvoudig:

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

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

await client.CompleteAsync("What color is the sky?");

Om de samenstelling van dergelijke onderdelen met anderen te vereenvoudigen, moeten auteurs van onderdelen een Use* uitbreidingsmethode maken voor het registreren van het onderdeel in een pijplijn. Denk bijvoorbeeld aan de volgende extensiemethode:

namespace Example.One;

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

Dergelijke extensies kunnen ook query's uitvoeren op relevante services uit de DI-container; de IServiceProvider die door de pijplijn worden gebruikt, worden doorgegeven als een optionele parameter:

namespace Example.Two;

// <two>
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,
                rateLimiter ?? services.GetRequiredService<RateLimiter>()));
}
// </two>

De consument kan dit vervolgens eenvoudig gebruiken in hun pijplijn, bijvoorbeeld:

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

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddChatClient(services =>
    new SampleChatClient(new Uri("http://localhost"), "test")
        .AsBuilder()
        .UseDistributedCache()
        .UseRateLimiting()
        .UseOpenTelemetry()
        .Build(services));

using var app = builder.Build();

// Elsewhere in the app
var chatClient = app.Services.GetRequiredService<IChatClient>();

Console.WriteLine(await chatClient.CompleteAsync("What is AI?"));

app.Run();

In dit voorbeeld ziet u gehost scenario, waarbij de consument afhankelijk is van afhankelijkheidsinjectie om het RateLimiter exemplaar te leveren. De voorgaande uitbreidingsmethoden laten zien hoe je een Use methode op ChatClientBuildertoepast. De ChatClientBuilder biedt ook Use overbelastingen die het gemakkelijker maken om dergelijke delegerende handlers te schrijven.

In het vorige RateLimitingChatClient-voorbeeld hoeven de overschrijvingen van CompleteAsync en CompleteStreamingAsync slechts taken uit te voeren voordat en nadat ze aan de volgende cliënt in de pijplijn delegeren. Om hetzelfde te bereiken zonder een aangepaste klasse te schrijven, kunt u een overload van Use gebruiken die een gedelegeerde accepteert die wordt gebruikt voor zowel CompleteAsync als CompleteStreamingAsync, waardoor de overbodige opmaak vermindert die nodig is.

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

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

var client = new SampleChatClient(new Uri("http://localhost"), "test")
    .AsBuilder()
    .UseDistributedCache()
    .Use(async (chatMessages, 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(chatMessages, options, cancellationToken);
    })
    .UseOpenTelemetry()
    .Build();

// Use client

De voorgaande overbelasting maakt intern gebruik van een AnonymousDelegatingChatClient, waardoor complexere patronen mogelijk zijn met slechts een beetje extra code. Bijvoorbeeld, om hetzelfde resultaat te bereiken maar dan met RateLimiter uit DI opgehaald:

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

var client = new SampleChatClient(new Uri("http://localhost"), "test")
    .AsBuilder()
    .UseDistributedCache()
    .Use(static (innerClient, services) =>
    {
        var rateLimiter = services.GetRequiredService<RateLimiter>();

        return new AnonymousDelegatingChatClient(
            innerClient, async (chatMessages, 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(chatMessages, options, cancellationToken);
            });
    })
    .UseOpenTelemetry()
    .Build();

Voor scenario's waarin de ontwikkelaar implementaties van CompleteAsync en CompleteStreamingAsync inline wil delegeren en waar het belangrijk is om voor elke implementatie een andere implementatie te kunnen schrijven om hun unieke retourtypen speciaal te kunnen verwerken, bestaat er nog een overbelasting van Use die een gemachtigde voor elke gebruiker accepteert.

Afhankelijkheidsinjectie

IChatClient implementaties worden doorgaans aan een toepassing verstrekt via afhankelijkheidsinjectie (DI). In dit voorbeeld wordt een IDistributedCache toegevoegd aan de DI-container, net als een IChatClient. De registratie voor de IChatClient maakt gebruik van een bouwer die een pijplijn creëert met daarin een cacheclient (die vervolgens gebruik zal maken van een IDistributedCache die uit DI is opgehaald) en de voorbeeldclient. De geïnjecteerde IChatClient kan elders in de app worden opgehaald en gebruikt.

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

// App setup
var builder = Host.CreateApplicationBuilder();

builder.Services.AddDistributedMemoryCache();
builder.Services.AddChatClient(new SampleChatClient(
        new Uri("http://coolsite.ai"), "target-ai-model"))
    .UseDistributedCache();

using var app = builder.Build();

// Elsewhere in the app
var chatClient = app.Services.GetRequiredService<IChatClient>();

Console.WriteLine(await chatClient.CompleteAsync("What is AI?"));

app.Run();

Het voorgaande voorbeeld is afhankelijk van de volgende NuGet-pakketten:

Welke instantie en configuratie wordt geïnjecteerd, kunnen verschillen op basis van de huidige behoeften van de toepassing en meerdere pijplijnen kunnen worden geïnjecteerd met verschillende sleutels.

De IEmbeddingGenerator-interface

De IEmbeddingGenerator<TInput,TEmbedding>-interface vertegenwoordigt een algemene generator van insluitingen. Hier is TInput het type invoerwaarden dat wordt ingesloten en TEmbedding het type gegenereerde insluiting is dat wordt overgenomen van de Embedding-klasse.

De Embedding-klasse fungeert als basisklasse voor insluitingen die worden gegenereerd door een IEmbeddingGenerator. Het is ontworpen voor het opslaan en beheren van de metagegevens en gegevens die zijn gekoppeld aan insluitingen. Afgeleide typen zoals Embedding<T> leveren de concrete embeddingvectorgegevens. Een insluiting maakt bijvoorbeeld een Embedding<T>.Vector eigenschap beschikbaar voor toegang tot de ingesloten gegevens.

De IEmbeddingGenerator-interface definieert een methode voor het asynchroon genereren van insluitingen voor een verzameling invoerwaarden, met optionele configuratie- en annuleringsondersteuning. Het biedt ook metagegevens die de generator beschrijven en het ophalen van sterk getypte services die door de generator of de onderliggende services kunnen worden geleverd.

Voorbeeld van implementatie

Bekijk de volgende voorbeeld-implementatie van een IEmbeddingGenerator om de algemene structuur weer te geven, maar die alleen willekeurige insluitingsvectoren genereert.

using Microsoft.Extensions.AI;

public sealed class SampleEmbeddingGenerator(
    Uri endpoint, string modelId)
        : IEmbeddingGenerator<string, Embedding<float>>
{
    public EmbeddingGeneratorMetadata Metadata { get; } =
        new(nameof(SampleEmbeddingGenerator), endpoint, modelId);

    public async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
        IEnumerable<string> values,
        EmbeddingGenerationOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        // Simulate some async operation
        await Task.Delay(100, cancellationToken);

        // Create random embeddings
        return
        [
            .. from value in values
            select new Embedding<float>(
                Enumerable.Range(0, 384)
                          .Select(_ => Random.Shared.NextSingle())
                          .ToArray())
        ];
    }

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

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

    void IDisposable.Dispose() { }
}

De voorgaande code:

  • Definieert een klasse met de naam SampleEmbeddingGenerator waarmee de IEmbeddingGenerator<string, Embedding<float>>-interface wordt geïmplementeerd.
  • Heeft een primaire constructor die een eindpunt en model-id accepteert, die worden gebruikt om de generator te identificeren.
  • Geeft een Metadata eigenschap weer die metagegevens over de generator biedt.
  • Implementeert de GenerateAsync methode voor het genereren van insluitingen voor een verzameling invoerwaarden:
    • Simuleert een asynchrone bewerking door 100 milliseconden te vertragen.
    • Retourneert willekeurige insluitingen voor elke invoerwaarde.

U vindt concrete implementaties in de volgende pakketten:

Insluitingen maken

De primaire bewerking die wordt uitgevoerd met een IEmbeddingGenerator<TInput,TEmbedding> is het genereren van embeddings, wat wordt bereikt met de methode GenerateAsync.

using Microsoft.Extensions.AI;

IEmbeddingGenerator<string, Embedding<float>> generator =
    new SampleEmbeddingGenerator(
        new Uri("http://coolsite.ai"), "target-ai-model");

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

Aangepaste IEmbeddingGenerator middleware

Net als bij IChatClientkunnen IEmbeddingGenerator implementaties gelaagd worden. Net zoals Microsoft.Extensions.AI implementaties van IChatClient voor caching en telemetrie biedt, levert het ook een implementatie voor IEmbeddingGenerator.

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

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

// Explore changing the order of the intermediate "Use" calls to see that impact
// that has on what gets cached, traced, etc.
var generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(
        new SampleEmbeddingGenerator(new Uri("http://coolsite.ai"), "target-ai-model"))
    .UseDistributedCache(
        new MemoryDistributedCache(
            Options.Create(new MemoryDistributedCacheOptions())))
    .UseOpenTelemetry(sourceName: sourceName)
    .Build();

var embeddings = await generator.GenerateAsync(
[
    "What is AI?",
    "What is .NET?",
    "What is AI?"
]);

foreach (var embedding in embeddings)
{
    Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}

De IEmbeddingGenerator maakt het mogelijk om aangepaste middleware te bouwen die de functionaliteit van een IEmbeddingGeneratoruitbreidt. De DelegatingEmbeddingGenerator<TInput,TEmbedding>-klasse is een implementatie van de IEmbeddingGenerator<TInput, TEmbedding>-interface die fungeert als basisklasse voor het maken van insluitgeneratoren die hun bewerkingen delegeren aan een ander IEmbeddingGenerator<TInput, TEmbedding> exemplaar. Het maakt het mogelijk om meerdere generatoren in elke volgorde te koppelen, waarbij aanroepen worden doorgegeven aan een onderliggende generator. De klasse biedt standaard implementaties voor methoden zoals GenerateAsync en Dispose, waarmee de aanroepen naar het binnenste generatorexemplaren worden doorgestuurd, waardoor flexibele en modulaire insluitingsgeneratie mogelijk is.

Hier volgt een voorbeeld van een implementatie van een dergelijke delegerende embeddinggenerator die het aantal aanvragen voor embedding-generatie beperkt.

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

Dit kan vervolgens worden gelaagd rond een willekeurige IEmbeddingGenerator<string, Embedding<float>> om de snelheid van alle bewerkingen voor het genereren van insluitingen te beperken.

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

IEmbeddingGenerator<string, Embedding<float>> generator =
    new RateLimitingEmbeddingGenerator(
        new SampleEmbeddingGenerator(new Uri("http://coolsite.ai"), "target-ai-model"),
        new ConcurrencyLimiter(new()
        {
            PermitLimit = 1,
            QueueLimit = int.MaxValue
        }));

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

Op deze manier kan de RateLimitingEmbeddingGenerator worden samengesteld met andere IEmbeddingGenerator<string, Embedding<float>> exemplaren om functionaliteit voor snelheidsbeperking te bieden.

Zie ook