Compartir vía


Inteligencia artificial en .NET (versión preliminar)

Con una variedad creciente de servicios de inteligencia artificial (IA) disponibles, los desarrolladores necesitan una manera de integrar e interactuar con estos servicios en sus aplicaciones .NET. La biblioteca de Microsoft.Extensions.AI proporciona un enfoque unificado para representar componentes de IA generativos, lo que permite una integración e interoperabilidad sin problemas con varios servicios de inteligencia artificial. En este artículo se presenta la biblioteca y se proporcionan instrucciones de instalación y ejemplos de uso para ayudarle a empezar.

Instalación del paquete

Para instalar el paquete NuGet de 📦 Microsoft.Extensions.AI, use la CLI de .NET o agregue una referencia de paquete directamente al archivo de proyecto de C#:

dotnet add package Microsoft.Extensions.AI --prerelease

Para obtener más información, consulte dotnet add package o Administración de dependencias de paquetes en aplicaciones .NET.

Ejemplos de uso

La interfaz IChatClient define una abstracción de cliente responsable de interactuar con los servicios de INTELIGENCIA ARTIFICIAL que proporcionan funcionalidades de chat. Incluye métodos para enviar y recibir mensajes con contenido multi modal (como texto, imágenes y audio), ya sea como un conjunto completo o transmitido incrementalmente. Además, proporciona información de metadatos sobre el cliente y permite recuperar servicios fuertemente tipados.

Importante

Para obtener más ejemplos de uso y escenarios reales, consulte AI para desarrolladores de .NET.

En esta sección

Interfaz IChatClient

En el ejemplo siguiente se implementa IChatClient para mostrar la estructura general.

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

Puede encontrar otras implementaciones concretas de IChatClient en los siguientes paquetes NuGet:

Solicitud de finalización del chat

Para solicitar una finalización, llame al método IChatClient.CompleteAsync. La solicitud se compone de uno o varios mensajes, cada uno de los cuales se compone de una o varias partes de contenido. Existen métodos de acelerador para simplificar casos comunes, como la construcción de una solicitud para un solo fragmento de contenido de texto.

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

El método principal IChatClient.CompleteAsync acepta una lista de mensajes. Esta lista representa el historial de todos los mensajes que forman parte de la conversación.

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

Cada mensaje del historial se representa mediante un objeto ChatMessage. La clase ChatMessage proporciona una propiedad ChatMessage.Role que indica el rol del mensaje. De forma predeterminada, se usa el ChatRole.User. Están disponibles los siguientes roles:

  • ChatRole.Assistant: indica o establece el comportamiento del asistente.
  • ChatRole.System: proporciona respuestas a la entrada indicada por el sistema y solicitada por el usuario.
  • ChatRole.Tool: proporciona información adicional y referencias para finalizaciones de chat.
  • ChatRole.User: proporciona datos para finalizaciones de chat.

Se crea una instancia de cada mensaje de chat y se asigna a su propiedad Contents una nueva TextContent. Hay varios tipos de de contenido que se pueden representar, como una cadena simple o un objeto más complejo que representa un mensaje multi modal con texto, imágenes y audio:

Solicitud de finalización del chat con streaming

Las entradas de IChatClient.CompleteStreamingAsync son idénticas a las de CompleteAsync. Sin embargo, en lugar de devolver la respuesta completa como parte de un objeto ChatCompletion, el método devuelve un IAsyncEnumerable<T> donde T es StreamingChatCompletionUpdate, proporcionando una secuencia de actualizaciones que forman colectivamente la respuesta única.

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

Sugerencia

Las API de streaming son casi sinónimos de experiencias de usuario de IA. C# permite escenarios atractivos con su compatibilidad con IAsyncEnumerable<T>, lo que permite una forma natural y eficaz de transmitir datos.

Llamada a la herramienta

Algunos modelos y servicios admiten la llamada a la herramienta, donde las solicitudes pueden incluir herramientas para que el modelo invoque funciones y recopile información adicional. En lugar de enviar una respuesta final, el modelo solicita una invocación de función con argumentos específicos. A continuación, el cliente invoca la función y devuelve los resultados al modelo junto con el historial de conversaciones. La biblioteca de Microsoft.Extensions.AI incluye abstracciones para varios tipos de contenido de mensajes, incluidas las solicitudes y los resultados de las llamadas de función. Aunque los consumidores pueden interactuar directamente con este contenido, Microsoft.Extensions.AI automatiza estas interacciones y proporciona:

  • AIFunction: representa una función que se puede describir en un servicio de IA e invocarla.
  • AIFunctionFactory: proporciona métodos de fábrica para crear implementaciones de AIFunctionusadas habitualmente.
  • FunctionInvokingChatClient: envuelve un IChatClient para agregar capacidad de invocación automática de funciones.

Considere el ejemplo siguiente que muestra una invocación de función aleatoria:

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

El ejemplo anterior depende del paquete Microsoft.Extensions.AI.Ollama 📦 NuGet.

El código anterior:

  • Define una función denominada GetCurrentWeather que devuelve una previsión meteorológica aleatoria.
    • Esta función está decorada con un DescriptionAttribute, que se usa para proporcionar una descripción de la función al servicio de IA.
  • Crea una instancia de un ChatClientBuilder con un OllamaChatClient y lo configura para usar la invocación de función.
  • Llama a CompleteStreamingAsync en el cliente, pasando una solicitud y una lista de herramientas que incluye una función creada con Create.
  • Recorre en iteración la respuesta, imprimiendo cada actualización en la consola.

Respuestas de caché

Si está familiarizado con el Almacenamiento en caché en .NET, es bueno saber que Microsoft.Extensions.AI ofrece otras implementaciones delegadas como IChatClient. El DistributedCachingChatClient es un IChatClient que aplica almacenamiento en caché sobre otra instancia arbitraria de IChatClient. Cuando se envía un historial de chat único al DistributedCachingChatClient, lo reenvía al cliente subyacente y, a continuación, almacena en caché la respuesta antes de devolverla al consumidor. La próxima vez que se envíe la misma solicitud, de modo que se pueda encontrar una respuesta almacenada en caché en la memoria caché, el DistributedCachingChatClient devuelve la respuesta almacenada en caché en lugar de necesitar reenviar la solicitud a lo largo de la canalización.

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

El ejemplo anterior depende del paquete NuGet 📦 Microsoft.Extensions.Caching.Memory. Para más información, vea Almacenamiento en caché en .NET.

Uso de telemetría

Otro ejemplo de un cliente de chat de delegación es el OpenTelemetryChatClient. Esta implementación se adhiere a las convenciones semánticas de OpenTelemetry para sistemas de inteligencia artificial generativa. De forma similar a otros delegadores IChatClient, se superponen las métricas y se extienden alrededor de cualquier implementación de IChatClient subyacente, lo que proporciona una observabilidad mejorada.

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

El ejemplo anterior depende del paquete NuGet 📦 OpenTelemetry.Exporter.Console.

Proporcionar opciones

Cada llamada a CompleteAsync o CompleteStreamingAsync puede proporcionar opcionalmente una instancia de ChatOptions que contenga parámetros adicionales para la operación. Los parámetros más comunes entre los modelos y servicios de IA aparecen como propiedades fuertemente tipadas en el tipo, como ChatOptions.Temperature. Otros parámetros se pueden proporcionar por nombre de manera poco estricta a través del diccionario ChatOptions.AdditionalProperties.

También puede especificar opciones al compilar un IChatClient con la fluida API de ChatClientBuilder y encadenar una llamada al método de extensión ConfigureOptions. Este cliente de delegación encapsula otro cliente e invoca al delegado proporcionado para rellenar una instancia de ChatOptions para cada llamada. Por ejemplo, para asegurarse de que la propiedad ChatOptions.ModelId tiene como valor predeterminado un nombre de modelo determinado, puede usar código como el siguiente:

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

El ejemplo anterior depende del paquete NuGet de Microsoft.Extensions.AI.Ollama 📦.

Pipelines de funcionalidad

IChatClient instancias se pueden superponer para crear una canalización de componentes, cada una de las cuales agrega funcionalidad específica. Estos componentes pueden provenir de Microsoft.Extensions.AI, otros paquetes NuGet o implementaciones personalizadas. Este enfoque le permite aumentar el comportamiento del IChatClient de varias maneras de satisfacer sus necesidades específicas. Tenga en cuenta el código de ejemplo siguiente que superpone una caché distribuida, la invocación de funciones y el seguimiento de OpenTelemetry en torno a un cliente de chat de ejemplo:

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

El ejemplo anterior depende de los siguientes paquetes NuGet:

Middleware IChatClient personalizado

Para agregar funcionalidad adicional, puede implementar IChatClient directamente o usar la clase DelegatingChatClient. Esta clase sirve como base para crear clientes de chat que deleguen operaciones a otra instancia de IChatClient. Simplifica el encadenamiento de varios clientes, lo que permite que las llamadas pasen a un cliente subyacente.

La clase DelegatingChatClient proporciona implementaciones predeterminadas para métodos como CompleteAsync, CompleteStreamingAsyncy Dispose, que reenvía llamadas al cliente interno. Puede derivar de esta clase e invalidar solo los métodos necesarios para mejorar el comportamiento, al mismo tiempo que delegar otras llamadas a la implementación base. Este enfoque ayuda a crear clientes de chat flexibles y modulares que son fáciles de ampliar y redactar.

A continuación se muestra una clase de ejemplo derivada de DelegatingChatClient para proporcionar funcionalidad de limitación de velocidad, utilizando el 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);
    }
}

El ejemplo anterior depende del paquete NuGet System.Threading.RateLimiting 📦. La composición del RateLimitingChatClient con otro cliente es sencilla:

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

Para simplificar la composición de estos componentes con otros, los autores de componentes deben crear un método de extensión Use* para registrar el componente en una canalización. Por ejemplo, considere el siguiente método de extensión:

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>

Estas extensiones también pueden consultar los servicios pertinentes del contenedor de inserción de dependencias; el IServiceProvider usado por la pipeline se pasa como parámetro opcional:

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>

Después, el consumidor puede usarlo fácilmente en su canalización, por ejemplo:

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

En este ejemplo se muestra el escenario hospedado , donde el consumidor se basa en la inyección de dependencias para proporcionar la instancia de RateLimiter. Los métodos de extensión anteriores muestran el uso de un método Use en ChatClientBuilder. El ChatClientBuilder también proporciona sobrecargas de Use que facilitan la escritura de tales controladores de delegación.

Por ejemplo, en el ejemplo anterior de RateLimitingChatClient, las sobrescrituras de CompleteAsync y CompleteStreamingAsync solo necesitan realizar el trabajo antes y después de delegar al siguiente cliente en el pipeline. Para lograr lo mismo sin escribir una clase personalizada, se puede usar una sobrecarga de Use que acepta un delegado que se utiliza para CompleteAsync y CompleteStreamingAsync, reduciendo el código repetitivo necesario:

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

La sobrecarga anterior usa internamente un AnonymousDelegatingChatClient, que permite patrones más complicados con solo un poco de código adicional. Por ejemplo, para lograr el mismo resultado, pero con el RateLimiter obtenido de DI:

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

En escenarios en los que el desarrollador desea especificar implementaciones delegadas de CompleteAsync y CompleteStreamingAsync en línea, y donde es importante poder escribir una implementación diferente para cada una para manejar especialmente sus tipos de retorno únicos, existe otra sobrecarga de Use que acepta un delegado para cada uno.

Inserción de dependencia

Las implementaciones de IChatClient normalmente se proporcionarán a una aplicación a través de inyección de dependencias (DI). En este ejemplo, se agrega un IDistributedCache al contenedor DI, al igual que un IChatClient. El registro de la IChatClient emplea un generador que crea una pipeline que contiene un cliente de caché (que luego usará un IDistributedCache recuperado a través de la DI) y el cliente de ejemplo. El IChatClient insertado se puede recuperar y usar en otra parte de la aplicación.

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

El ejemplo anterior depende de los siguientes paquetes NuGet:

La instancia y la configuración insertadas pueden diferir en función de las necesidades actuales de la aplicación y se pueden insertar varias canalizaciones con claves diferentes.

Interfaz IEmbeddingGenerator

La interfaz IEmbeddingGenerator<TInput,TEmbedding> representa un generador genérico de incrustaciones. Aquí, TInput es el tipo de valores de entrada que se insertan y TEmbedding es el tipo de inserción generado, que hereda de la clase Embedding.

La clase Embedding actúa como una clase base para incrustaciones generadas por un IEmbeddingGenerator. Está diseñado para almacenar y administrar los metadatos y los datos asociados a las incrustaciones. Los tipos derivados como Embedding<T> proporcionan los datos vectoriales de inserción concretos. Por ejemplo, una inserción expone una propiedad Embedding<T>.Vector para acceder a sus datos de representación.

La interfaz IEmbeddingGenerator define un método para generar de forma asincrónica inserciones para una colección de valores de entrada, con compatibilidad opcional de configuración y cancelación. También proporciona metadatos que describen el generador y permite la recuperación de servicios fuertemente tipados que el generador o sus servicios subyacentes pueden proporcionar.

Implementación de ejemplo

Considere la siguiente implementación de ejemplo de un IEmbeddingGenerator para mostrar la estructura general, pero que solo genera vectores de inserción aleatorios.

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

El código anterior:

  • Define una clase denominada SampleEmbeddingGenerator que implementa la interfaz IEmbeddingGenerator<string, Embedding<float>>.
  • Tiene un constructor principal que acepta un punto de conexión y un identificador de modelo, que se usan para identificar el generador.
  • Expone una propiedad Metadata que proporciona metadatos sobre el generador.
  • Implementa el método GenerateAsync para generar incrustaciones para una colección de valores de entrada:
    • Simula una operación asincrónica retrasando 100 milisegundos.
    • Devuelve incrustaciones aleatorias para cada valor de entrada.

Puede encontrar implementaciones concretas reales en los siguientes paquetes:

Creación de incrustaciones

La operación principal realizada con un IEmbeddingGenerator<TInput,TEmbedding> es la generación de inserciones, que se genera con su método 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()));
}

Middleware IEmbeddingGenerator personalizado

Al igual que con IChatClient, se pueden superponer las implementaciones de IEmbeddingGenerator. Al igual que Microsoft.Extensions.AI proporciona implementaciones de delegación de IChatClient para el almacenamiento en caché y la telemetría, también proporciona una implementación para 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()));
}

El IEmbeddingGenerator permite crear middleware personalizado que amplía la funcionalidad de un IEmbeddingGenerator. La clase DelegatingEmbeddingGenerator<TInput,TEmbedding> es una implementación de la interfaz IEmbeddingGenerator<TInput, TEmbedding> que actúa como clase base para crear generadores de inserción que deleguen sus operaciones a otra instancia de IEmbeddingGenerator<TInput, TEmbedding>. Permite encadenar varios generadores en cualquier orden, pasando llamadas a un generador subyacente. La clase proporciona implementaciones predeterminadas para métodos como GenerateAsync y Dispose, que reenvía las llamadas a la instancia del generador interno, lo que permite la generación de inserción flexible y modular.

A continuación se muestra un ejemplo de implementación de un generador de insertados delegante que limita la cantidad de solicitudes de generación de insertados.

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

Después, esto se puede superponer a una IEmbeddingGenerator<string, Embedding<float>> arbitraria para limitar la velocidad de todas las operaciones de generación de inserción realizadas.

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

De este modo, el RateLimitingEmbeddingGenerator se puede componer con otras instancias de IEmbeddingGenerator<string, Embedding<float>> para proporcionar funcionalidad de limitación de velocidad.

Consulte también