冠军

导航

Microsoft.Extensions.AI 库

https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai

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

NuGet 包

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

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

引用哪些 NuGet 包

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

要获取用于处理生成式 AI 组件的更高级实用工具,请改为引用 Microsoft.Extensions.AI 包(该包本身引用 Microsoft.Extensions.AI.Abstractions)。大多数消费应用程序和服务应同时引用 Microsoft.Extensions.AI 包及一个或多个提供抽象具体实现的库。

安装包

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

API 使用示例

The following subsections show specific IChatClient usage examples:

  • Request a chat response
  • Request a streaming chat response
  • Tool calling
  • Cache responses
  • Use telemetry
  • Provide options
  • Pipelines of functionality
  • Custom IChatClient middleware
  • Dependency injection
  • Stateless vs. stateful clients

下面内容展示了特定的 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?"),
]));

从 GetResponseAsync 返回的 ChatResponse 公开了一系列 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 的输入相同。但是,该方法返回的是一个 IAsyncEnumerable,其中 T 是 ChatResponseUpdate,而不是将完整响应作为 ChatResponse 对象的一部分返回,从而提供一个更新的流,这些更新共同构成单个响应。

// 获得异步流
await foreach (ChatResponseUpdate update in client.GetStreamingResponseAsync("What is AI?"))
{
    Console.Write(update);
}

提示
流媒体 API 几乎与 AI 用户体验同义。C# 通过其 IAsyncEnumerable 支持实现引人入胜的场景,提供了一种自然且高效的数据流传输方式。

与 GetResponseAsync 类似,您可以将 IChatClient.GetStreamingResponseAsync 的更新添加回消息列表。由于更新是响应的单个部分,您可以使用类似 ToChatResponse(IEnumerable) 的助手将一个或多个更新合成一个 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 列表中
        updates.Add(update);
    }
    Console.WriteLine();

    // 将 updates 列表加入历史
    chatHistory.AddMessages(updates);
}

工具调用

一些模型和服务支持工具调用。为了收集更多信息,您可以通过配置 ChatOptions 来提供关于工具的信息(通常是 .NET 方法),以便模型请求客户端调用。模型不发送最终响应,而是请求以特定参数调用一个函数。然后客户端调用该函数,并将结果与对话历史一起发送回模型。Microsoft.Extensions.AI.Abstractions 库包含不同消息内容类型的抽象,包括函数调用请求和结果。尽管 IChatClient 消费者可以直接与这些内容互动,Microsoft.Extensions.AI 提供的助手可以使自动调用工具以响应相应请求变得更加容易。Microsoft.Extensions.AI.Abstractions 和 Microsoft.Extensions.AI 库提供以下类型:

  • AIFunction:表示可以向 AI 模型描述并调用的函数。
  • AIFunctionFactory:提供创建代表 .NET 方法的 AIFunction 实例的工厂方法。
  • 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 来连接函数调用
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 的函数,该函数返回一个随机天气预报。
  • 用 OllamaSharp.OllamaApiClient 实例化一个 ChatClientBuilder,并将其配置为使用函数调用。
  • 在客户端上调用 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。此实现遵循 OpenTelemetry 生成式 AI 系统的语义约定。与其他 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)方法提供了一种简单的方法,将每个请求和响应的日志条目写入ILogger。

提供选项 Options

每次调用 GetResponseAsync 或 GetStreamingResponseAsync 时,可以选择性地提供一个ChatOptions实例,包含操作的附加参数。在 AI 模型和服务中,最常见的参数会作为强类型属性出现在该类型上,例如C hatOptions.Temperature。其他参数可以通过弱类型的方式按名称提供,采用ChatOptions.AdditionalProperties字典,或者通过底层提供程序理解的选项实例,通过ChatOptions.RawRepresentationFactory属性。

您还可以在使用流畅的 ChatClientBuilder API 构建 IChatClient时,通过链式调用C onfigureOptions(ChatClientBuilder, Action)扩展方法来指定选项。这个委托客户端封装了另一个客户端,并在每次调用时调用提供的委托来填充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 类为方法提供了默认实现,如 GetResponseAsync、GetStreamingResponseAsync 和 Dispose,这些方法将调用转发给内部客户端。派生类可以覆盖它所需增强行为的方法,而将其他调用委托给基本实现。这种方法对于创建灵活和模块化的聊天客户端非常有用,便于扩展和组合。以下是一个从 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));

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

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

对于需要为 GetResponseAsync 和 GetStreamingResponseAsync 提供不同实现的场景,以处理它们独特的返回类型,您可以使用 Accepts 一个委托的重载:Use(Func<IEnumerable, ChatOptions, IChatClient, CancellationToken, Task> 和 Func<IEnumerable, ChatOptions, IChatClient, CancellationToken, IAsyncEnumerable)。

依赖注入

IChatClient 的实现通常通过依赖注入(DI)提供给应用程序。在这个例子中,IDistributedCache 被添加到 DI 容器中,IChatClient 也是如此。IChatClient 的注册使用一个构建器,该构建器创建一个包含缓存客户端的管道(该缓存客户端然后使用从 DI 中获取的 IDistributedCache)和示例客户端。注入的 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.ConversationId 转移到 ChatOptions.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,提供具体的嵌入向量数据。例如,Embedding 提供了一个ReadOnlyMemory 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> 接口。
  • 具有一个主构造函数,该构造函数接受一个端点和一个模型 ID,用于识别生成器。
  • 实现了 GenerateAsync 方法,以便为一组输入值生成嵌入。

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

OllamaSharp 的实现

public class OllamaApiClient : IOllamaApiClient, IChatClient, IEmbeddingGenerator<string, Embedding<float>>

可以看到 OllamaApiClient 实现了 3 个接口

  1. IOllamaApiClient
  2. IChatClient
  3. IEmbeddingGenerator<string, Embedding>

见:https://github.com/awaescher/OllamaSharp/blob/dd90ff26acb9f55898b237d7475eaed880c573db/src/OllamaSharp/OllamaApiClient.cs#L20C1-L21C2

创建嵌入

与 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> 实例的嵌入生成器的基类。它允许以任意顺序链式处理多个生成器,将调用传递给底层生成器。该类为 GenerateAsync 和 Dispose 等方法提供默认实现,这些方法将调用转发到内部生成器实例,实现灵活和模块化的嵌入生成。

以下是一个委托嵌入生成器实现的示例,它对嵌入生成请求进行速率限制:

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> 组合,以限制所有嵌入生成操作的速率。

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> 实例组合,以提供速率限制功能。

使用 Microsoft.Extensions.AI 构建

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

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

有关更多示例,请参见 dotnet/ai-samples GitHub 存储库。如需完整的示例,请参见 eShopSupport。

参考资料

posted on 2025-08-13 15:23  冠军  阅读(84)  评论(0)    收藏  举报