Microsoft.Extensions.AI 库

.NET 开发人员需要在其应用中集成和与越来越多的人工智能(AI)服务进行交互。 这些 Microsoft.Extensions.AI 库提供了一种统一的方法来表示生成 AI 组件,并实现与各种 AI 服务的无缝集成和互作性。 本文介绍这些库,并提供深入的用法示例来帮助你入门。

包裹们

📦 Microsoft.Extensions.AI.Abstractions 包提供核心交换类型,包括IChatClientIEmbeddingGenerator<TInput,TEmbedding>。 提供 LLM 客户端的任何 .NET 库都可以实现 IChatClient 接口,以实现与使用代码的无缝集成。

📦 Microsoft.Extensions.AI 包对 Microsoft.Extensions.AI.Abstractions 包具有隐式依赖项。 通过此包,可以使用熟悉的依赖项注入和中间件模式轻松地将自动函数工具调用、遥测和缓存等组件集成到应用程序中。 例如,它提供了 UseOpenTelemetry(ChatClientBuilder, ILoggerFactory, String, Action<OpenTelemetryChatClient>) 扩展方法,该方法将 OpenTelemetry 支持添加到聊天客户端管道中。

要引用的包

提供抽象实现的库通常仅引用Microsoft.Extensions.AI.Abstractions

为了同时能访问用于生成式 AI 组件的高级实用工具,请改为引用 Microsoft.Extensions.AI 包(该包本身引用 Microsoft.Extensions.AI.Abstractions)。 大多数消耗的应用程序和服务应引用 Microsoft.Extensions.AI 包以及一个或多个库,这些库提供抽象的具体实现。

安装软件包

有关如何安装 NuGet 包的信息,请参阅 .NET 应用程序中 的 dotnet 包添加管理包依赖项

API 用法示例

以下小节显示了特定的 IChatClient 用法示例:

以下部分显示了特定的 IEmbeddingGenerator 用法示例:

IChatClient 接口

IChatClient 接口定义一个客户端抽象,负责与提供聊天功能的 AI 服务交互。 它包括发送和接收具有多模式内容(如文本、图像和音频)的消息的方法,无论是作为一个完整的集合还是增量流式传输。 此外,它还允许检索由客户端或其基础服务提供的严格类型服务。

为语言模型和服务提供客户端的 .NET 库可以提供接口的 IChatClient 实现。 然后,接口的任何使用者都可以通过抽象与这些模型和服务无缝互作。

请求聊天响应

使用实例 IChatClient,可以调用 IChatClient.GetResponseAsync 方法发送请求并获取响应。 请求由一个或多个消息组成,每个消息由一条或多条内容组成。 加速器方法的存在是为了简化常见情况,例如构造对单个文本内容的请求。

using Microsoft.Extensions.AI;

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

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

核心 IChatClient.GetResponseAsync 方法接受消息列表。 此列表表示对话中所有消息的历史记录。

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

返回自ChatResponseGetResponseAsync公开一个ChatMessage实例列表,这些实例表示在操作过程中生成的一个或多个消息。 在常见情况下,只有一条响应消息,但在某些情况下,可能会有多个消息。 对消息列表进行排序,以便列表中的最后一条消息表示请求的最后一条消息。 若要在后续请求中向服务提供所有这些响应消息,可以将响应中的消息添加回消息列表。

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

请求实时聊天响应

IChatClient.GetStreamingResponseAsync 的输入与 GetResponseAsync 的输入相同。 然而,该方法不是将完整的响应作为 ChatResponse 对象的一部分返回,而是返回一个 IAsyncEnumerable<T>,其中 TChatResponseUpdate,提供了一系列更新,这些更新共同形成了单个响应。

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

小窍门

流媒体接口几乎与 AI 用户体验密不可分。 C# 通过其对 IAsyncEnumerable<T> 的支持实现了引人注目的方案,允许以自然且高效的方式流式传输数据。

如同 GetResponseAsync,可以将 IChatClient.GetStreamingResponseAsync 的更新添加到消息列表中。 由于更新是响应的各个部分,因此可以使用类似 ToChatResponse(IEnumerable<ChatResponseUpdate>) 的辅助工具将一个或多个更新合并回单个 ChatResponse 实例。

帮助程序(例如 AddMessages 撰写消息) ChatResponse 然后从响应中提取撰写的消息,并将其添加到列表中。

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(history))
    {
        Console.Write(update);
        updates.Add(update);
    }
    Console.WriteLine();

    chatHistory.AddMessages(updates);
}

工具调用

某些模型和服务支持 工具调用。 若要收集其他信息,可以配置ChatOptions以包含有关工具的信息(通常是 .NET 方法),模型可请求客户端调用这些工具。 模型不发送最终响应,而是请求带有特定参数的函数调用。 然后,客户端调用函数并将结果发送回具有会话历史记录的模型。 Microsoft.Extensions.AI.Abstractions 库包括各种消息内容类型的抽象,包括函数调用请求和结果。 虽然 IChatClient 使用者可以直接与此内容交互, Microsoft.Extensions.AI 但提供了帮助程序,这些帮助程序可以自动调用工具以响应相应的请求。 这两个库Microsoft.Extensions.AI.AbstractionsMicrosoft.Extensions.AI提供以下类型:

  • AIFunction:表示可描述为 AI 模型并调用的函数。
  • AIFunctionFactory:提供用于创建 AIFunction 表示 .NET 方法的实例的工厂方法。
  • FunctionInvokingChatClient:将IChatClient包装成另一个IChatClient,并添加自动函数调用功能。

以下示例演示随机函数调用(此示例依赖于 📦 OllamaSharp NuGet 包):

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

前面的代码:

  • 定义一个名为 GetCurrentWeather 的函数,返回随机天气预报。
  • 使用 ChatClientBuilder 实例化 OllamaSharp.OllamaApiClient,并将其配置为使用函数调用。
  • 在客户端调用 GetStreamingResponseAsync,传递一个提示和一个工具列表,其中包括用 Create 创建的函数。
  • 遍历响应,将每个更新输出到控制台。

缓存响应

如果你熟悉 .NET 中的缓存,那么应该了解 Microsoft.Extensions.AI 提供了其他这样的委托 IChatClient 实现。 DistributedCachingChatClient 是一个 IChatClient,它围绕另一个任意的 IChatClient 实例分层缓存。 向基础客户端提交 DistributedCachingChatClient新聊天历史记录时,它会将其转发到基础客户端,然后在将其发送回使用者之前缓存响应。 下次提交相同的历史记录时,如果在缓存中可以找到缓存的响应,DistributedCachingChatClient 将返回该缓存响应,而不是在管道中转发请求。

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

此示例取决于 📦 Microsoft.Extensions.Caching.Memory NuGet 包。 有关详细信息,请参阅 .NET 中的缓存

使用遥测

另一个委派聊天客户端的示例是 OpenTelemetryChatClient。 此实现遵循生成式 AI 系统的 OpenTelemetry 语义约定。 与其他 IChatClient 委派器类似,它会对指标进行分层,并跨越其他任意 IChatClient 实现。

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

// Configure OpenTelemetry exporter.
string sourceName = Guid.NewGuid().ToString();
TracerProvider 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: c => c.EnableSensitiveData = true)
    .Build();

Console.WriteLine((await client.GetResponseAsync("What is AI?")).Text);

(前面的示例取决于 📦 OpenTelemetry.Exporter.Console NuGet 包。

或者, LoggingChatClient 和相应的 UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) 方法提供了将日志条目写入每个请求和响应的 ILogger 简单方法。

提供选项

每次调用 GetResponseAsyncGetStreamingResponseAsync 时,可以选用一个包含附加操作参数的 ChatOptions 实例。 AI 模型和服务中的最常见参数呈现为类型的强类型化属性,例如 ChatOptions.Temperature。 可以以弱类型方式通过名称提供其他参数,方法是通过 ChatOptions.AdditionalProperties 字典,或通过基础提供程序理解的 ChatOptions.RawRepresentationFactory 属性的选项实例。

还可以通过将调用IChatClient链接到扩展方法来指定使用 Fluent ChatClientBuilder API 生成ConfigureOptions(ChatClientBuilder, Action<ChatOptions>)时的选项。 此委托客户端包装另一个客户端,并调用提供的委托为每次调用填充一个 ChatOptions 实例。 例如,为了确保 ChatOptions.ModelId 属性默认为特定的模型名称,可以使用以下代码:

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

功能管道

可以将 IChatClient 实例进行分层,以创建一个各组件都添加附加功能的管道。 这些组件可以来自 Microsoft.Extensions.AI、其他 NuGet 包或自定义实现。 这种方法允许你以各种方式增强 IChatClient 的行为,以满足特定需求。 请考虑以下代码片段,该代码片段围绕示例聊天客户端分层分布式缓存、函数调用和 OpenTelemetry 跟踪:

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

自定义 IChatClient 中间件

若要添加其他功能,可以直接实现 IChatClient 或使用 DelegatingChatClient 类。 此类用作创建专门将操作委托给另一个 IChatClient 实例的聊天客户端的基础。 它简化了链接多个客户端,允许调用传递到基础客户端。

DelegatingChatClient 类为 GetResponseAsyncGetStreamingResponseAsyncDispose 等方法提供了默认实现,这些方法将调用转发到内部客户端。 派生类可以重载它需要扩充行为的方法,同时将其他调用委托给基类的实现。 此方法可用于创建易于扩展和撰写的灵活模块化聊天客户端。

下面是派生自 DelegatingChatClient 的一个示例类,该类使用 System.Threading.RateLimiting 库提供速率限制功能。

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

与其他IChatClient实现一样,可以组合:RateLimitingChatClient

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

为了简化这些组件与其他组件的组合,组件作者应该创建一个 Use* 扩展方法,将组件注册到管道中。 例如,请考虑以下 UseRatingLimiting 扩展方法:

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

这样的扩展还可以从 DI 容器查询相关服务;管道使用的 IServiceProvider 作为可选参数传入:

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

现在,用户可以轻松地在流程中使用它,例如:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

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

前面的扩展方法演示了如何在 Use 上使用 ChatClientBuilder 方法。 ChatClientBuilder还提供Use重载,从而更容易编写这些委托处理程序。 例如,在前面的 RateLimitingChatClient 示例中,GetResponseAsyncGetStreamingResponseAsync 的重写只需要在委托给管道中的下一个客户端之前和之后执行工作。 为了在不编写自定义类的情况下实现同样的目的,你可以使用 Use 的重载来接受一个同时用于 GetResponseAsyncGetStreamingResponseAsync 的委托,从而减少所需的样板代码:

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

对于需要为 GetResponseAsyncGetStreamingResponseAsync 提供不同实现以处理其独特返回类型的场景,可以使用用于每一个委托的 Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken, Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions, IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>) 重载。

依赖项注入

IChatClient 实现通常通过 依赖项注入(DI)提供给应用程序。 在此示例中,将 IDistributedCache 添加到 DI 容器中,IChatClient也同样被添加。 注册 IChatClient 使用生成器创建包含缓存客户端的管道(然后使用 IDistributedCache 从 DI 检索到的管道)和示例客户端。 注入的 IChatClient 可以在应用程序的其他地方检索和使用。

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

注入的实例和配置可以根据应用程序的当前需求而有所不同,并且可以用不同的密钥注入多个管道。

无状态客户端与有状态客户端

无状态 服务要求在每次请求时都发送回所有相关聊天历史记录。 相比之下,有状态服务会跟踪服务的历史,只需在请求中发送附加消息。 该 IChatClient 接口旨在处理无状态和有状态 AI 服务。

使用无状态服务时,调用方将维护所有消息的列表。 它们会添加所有收到的响应消息,并在后续交互时提供列表。

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

对于有状态服务,您可能已知用于相关对话的标识符。 可以将该标识符放入ChatOptions.ConversationId 。 然后,使用情况遵循相同的模式,但无需手动维护历史记录。

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

某些服务可能支持为没有会话的请求自动创建会话 ID,或者创建一个新的会话 ID,该 ID 表示在合并最后一轮消息后会话的当前状态。 在这种情况下,可以将控制权转移到ChatResponse.ConversationIdChatOptions.ConversationId后续请求。 例如:

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

如果事先不知道服务是无状态还是有状态,则可以检查响应 ConversationId 并根据其值进行作。 如果已设置,则会将该值传播到选项,并清除历史记录,以免再次重新发送相同的历史记录。 如果未设置响应 ConversationId,则会将响应消息添加到历史记录中,以便在下一轮中发送回服务。

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

IEmbeddingGenerator 接口

IEmbeddingGenerator<TInput,TEmbedding> 接口表示嵌入的泛型生成器。 此处,TInput 是嵌入的输入值的类型,TEmbedding 是生成的嵌入的类型,它继承自 Embedding 类。

Embedding 类充当由 IEmbeddingGenerator 生成的嵌入的基类。 它旨在存储和管理与嵌入相关的元数据和数据。 派生类型(例如 Embedding<T>)提供具体的嵌入向量数据。 例如,Embedding<float> 公开一个 ReadOnlyMemory<float> Vector { get; } 属性以便访问其嵌入的数据。

IEmbeddingGenerator 接口定义一种方法,以异步方式为输入值的集合生成嵌入,并提供可选配置和取消支持。 它还提供描述生成器的元数据,并允许检索可由生成器或其基础服务提供的强类型服务。

示例实现

以下示例实现 IEmbeddingGenerator 显示了一般结构。

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 new GeneratedEmbeddings<Embedding<float>>(
            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() { }
}

前面的代码:

  • 定义实现 SampleEmbeddingGenerator 接口的名为 IEmbeddingGenerator<string, Embedding<float>> 的类。
  • 有一个接受终结点和模型 ID 的主构造函数,用于标识生成器。
  • GenerateAsync实现为输入值的集合生成嵌入的方法。

示例实现只生成随机嵌入向量。 可以在 Microsoft.Extensions.AI.OpenAI 包中找到📦具体实现。

创建嵌入

使用 IEmbeddingGenerator<TInput,TEmbedding> 执行的主要操作是嵌入生成,这是通过其 GenerateAsync 方法完成的。

using Microsoft.Extensions.AI;

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

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

加速器扩展方法还存在以简化常见情况,例如从单个输入生成嵌入向量。

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

功能管道

IChatClient一样,IEmbeddingGenerator 实现可以分层。 Microsoft.Extensions.AI 提供用于 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
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 SampleEmbeddingGenerator(new Uri("http://coolsite.ai"), "target-ai-model"))
    .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()));
}

IEmbeddingGenerator 支持生成扩展 IEmbeddingGenerator 的功能的自定义中间件。 DelegatingEmbeddingGenerator<TInput,TEmbedding> 类是 IEmbeddingGenerator<TInput, TEmbedding> 接口的实现,它作为创建嵌入生成器的基类,将其操作委托给另一个 IEmbeddingGenerator<TInput, TEmbedding> 实例。 它允许以任何顺序链接多个生成器,将调用传递给基础生成器。 该类为方法(如 GenerateAsyncDispose)提供默认实现,这些方法将调用转发到内部生成器实例,从而实现灵活的模块化嵌入生成。

下面是此类委派嵌入生成器的示例实现,该生成器对嵌入生成请求进行速率限制:

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

然后,可以在任意 IEmbeddingGenerator<string, Embedding<float>> 周围进行分层,以限制所有嵌入生成操作的速率。

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

这样,RateLimitingEmbeddingGenerator 可以与其他 IEmbeddingGenerator<string, Embedding<float>> 实例组合,以提供速率限制功能。

使用 Microsoft.Extensions.AI 进行构建

可以通过以下方式使用 Microsoft.Extensions.AI 开始构建:

  • 库开发人员:如果你拥有为 AI 服务提供客户端的库,请考虑在你的库中实现相关接口。 这使用户能够通过抽象概念轻松集成你的 NuGet 包。
  • 服务使用者:如果要开发使用 AI 服务的库,请使用抽象,而不是硬编码到特定的 AI 服务。 此方法使使用者能够灵活地选择首选提供商。
  • 应用程序开发人员:使用这些抽象概念来简化与你的应用程序的集成。 这使得应用程序能够在不同模型和服务之间实现可移植性,便于进行测试和模拟,利用生态系统提供的中间件,并且即使在应用程序的不同部分使用不同的服务,也能在整个应用程序中保持一致的 API。
  • 生态系统贡献者:如果你有兴趣为生态系统做出贡献,请考虑编写自定义中间件组件。

有关更多示例,请参阅 dotnet/ai-samples GitHub 存储库。 有关端到端示例,请参阅 eShopSupport

另请参阅