Partager via


Utiliser l’interface IEmbeddingGenerator

L’interface IEmbeddingGenerator<TInput,TEmbedding> représente un générateur générique d’incorporations. Pour les paramètres de type générique, TInput est le type de valeurs d’entrée incorporées et TEmbedding est le type d’incorporation généré, qui hérite de la Embedding classe.

La classe Embedding sert de classe de base pour les incorporations générées par un IEmbeddingGenerator. Il est conçu pour stocker et gérer les métadonnées et les données associées aux incorporations. Les types dérivés, tels que Embedding<T>, fournissent les données vectorielles d’incorporation concrètes. Par exemple, un Embedding<float> expose une propriété ReadOnlyMemory<float> Vector { get; } permettant d'accéder à ses données intégrées.

L’interface IEmbeddingGenerator définit une méthode pour générer de manière asynchrone des incorporations pour une collection de valeurs d’entrée, avec prise en charge facultative de la configuration et de l’annulation. Il fournit également des métadonnées décrivant le générateur et permet la récupération de services fortement typés qui peuvent être fournis par le générateur ou ses services sous-jacents.

Créer des embeddings

L’opération principale effectuée avec une IEmbeddingGenerator<TInput,TEmbedding> est la génération d'embeddings, qui est réalisée grâce à sa méthode GenerateAsync.

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

Les méthodes d’extension d'accélérateur existent également pour simplifier les cas courants, tels que la génération d’un vecteur d’incorporation à partir d’une seule entrée.

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

Pipelines de fonctionnalités

Comme avec le IChatClient, les implémentations du IEmbeddingGenerator peuvent être superposées. Microsoft.Extensions.AI fournit une implémentation de délégation pour IEmbeddingGenerator la mise en cache et la télémétrie.

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

Le IEmbeddingGenerator permet de créer un middleware personnalisé qui étend les fonctionnalités d’un IEmbeddingGenerator. La classe DelegatingEmbeddingGenerator<TInput,TEmbedding> est une implémentation de l’interface IEmbeddingGenerator<TInput, TEmbedding> qui sert de classe de base pour créer des générateurs d’incorporation qui délèguent leurs opérations à une autre instance IEmbeddingGenerator<TInput, TEmbedding>. Elle permet de chaîner plusieurs générateurs dans n’importe quel ordre, en passant des appels à un générateur sous-jacent. La classe fournit des implémentations par défaut pour les méthodes telles que GenerateAsync et Dispose, qui transfèrent les appels à l’instance de générateur interne, ce qui permet une génération d’incorporation flexible et modulaire.

Voici un exemple d’implémentation d’un tel générateur d’incorporation par délégation qui limite les demandes de génération d’incorporation :

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

On peut ensuite le superposer à un IEmbeddingGenerator<string, Embedding<float>> arbitraire pour limiter le débit de toutes les opérations de génération d'incorporations.

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

De cette façon, le RateLimitingEmbeddingGenerator peut être composé avec d'autres instances IEmbeddingGenerator<string, Embedding<float>> pour offrir une fonctionnalité de limitation de débit.

Exemples d’implémentation

La plupart des utilisateurs n’ont pas besoin d’implémenter l’interface IEmbeddingGenerator . Toutefois, si vous êtes un auteur de bibliothèque, il peut être utile d’examiner ces exemples d’implémentation.

Le code suivant montre comment la SampleEmbeddingGenerator classe implémente l’interface IEmbeddingGenerator<TInput,TEmbedding> . Il a un constructeur principal qui accepte un point de terminaison et un ID de modèle, qui sont utilisés pour identifier le générateur. Il implémente également la GenerateAsync(IEnumerable<TInput>, EmbeddingGenerationOptions, CancellationToken) méthode pour générer des incorporations pour une collection de valeurs d’entrée.

using Microsoft.Extensions.AI;

public sealed class SampleEmbeddingGenerator(
    Uri endpoint, string modelId)
        : IEmbeddingGenerator<string, Embedding<float>>
{
    private readonly EmbeddingGeneratorMetadata _metadata =
        new("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) =>
        serviceKey is not null
        ? null
        : serviceType == typeof(EmbeddingGeneratorMetadata)
            ? _metadata
            : serviceType?.IsInstanceOfType(this) is true
                ? this
                : null;

    void IDisposable.Dispose() { }
}

Cet exemple d’implémentation génère simplement des vecteurs d’incorporation aléatoires. Pour une implémentation plus réaliste et concrète, consultez OpenTelemetryEmbeddingGenerator.cs.