次の方法で共有


.NET における人工知能 (プレビュー)

さまざまな人工知能 (AI) サービスが利用できるようになると、開発者はこれらのサービスを .NET アプリケーションに統合して操作する方法が必要になります。 Microsoft.Extensions.AI ライブラリは、生成型 AI コンポーネントを表す統合されたアプローチを提供します。これにより、さまざまな AI サービスとのシームレスな統合と相互運用性が可能になります。 この記事では、ライブラリについて説明し、インストール手順と使用例を示します。

パッケージをインストールする

📦 Microsoft.Extensions.AI NuGet パッケージをインストールするには、.NET CLI を使用するか、パッケージ参照を C# プロジェクト ファイルに直接追加します。

dotnet add package Microsoft.Extensions.AI --prelease

詳しくは、「dotnet add package」または「.NET アプリケーションでパッケージの依存関係を管理する」をご覧ください。

使用例

IChatClient インターフェイスは、チャット機能を提供する AI サービスとの対話を担当するクライアントの抽象化を定義します。 これには、完全なセットとして、または段階的にストリーミングされたマルチモーダル コンテンツ (テキスト、画像、オーディオなど) を含むメッセージを送受信するためのメソッドが含まれています。 さらに、クライアントに関するメタデータ情報を提供し、厳密に型指定されたサービスを取得できます。

重要

その他の使用例と実際のシナリオについては、 .NET 開発者向けの AIを参照してください。

このセクションの内容

IChatClient インターフェイス

次の例では、一般的な構造を示す IChatClient を実装しています。

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

IChatClient のその他の具体的な実装は、次の NuGet パッケージにあります。

チャット完了を要求する

完了を要求するには、 IChatClient.CompleteAsync メソッドを呼び出します。 要求は 1 つ以上のメッセージで構成され、それぞれが 1 つ以上のコンテンツで構成されます。 アクセラレータ メソッドは、テキスト コンテンツの 1 つの部分に対する要求の構築など、一般的なケースを簡略化するために存在します。

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

コア IChatClient.CompleteAsync メソッドは、メッセージの一覧を受け入れます。 このリストは、会話の一部であるすべてのメッセージの履歴を表します。

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

履歴内の各メッセージは、 ChatMessage オブジェクトによって表されます。 ChatMessage クラスは、メッセージの役割を示す ChatMessage.Role プロパティを提供します。 デフォルトでは ChatRole.User が使用されます。 次のロールを使用できます。

  • ChatRole.Assistant: アシスタントの動作を指示または設定します。
  • ChatRole.System: システム指示のユーザープロンプト入力に対する応答を提供します。
  • ChatRole.Tool: チャットの完了に関する追加情報と参照を提供します。
  • ChatRole.User: チャットの完了に対する入力を提供します。

各チャット メッセージがインスタンス化され、その Contents プロパティに新しい TextContentが割り当てられます。 単純な文字列や、テキスト、画像、オーディオを含むマルチモーダル メッセージを表すより複雑なオブジェクトなど、さまざまな 種類のコンテンツ を表すことができます。

ストリーミング を使用してチャットの完了を要求する

IChatClient.CompleteStreamingAsync への入力は、CompleteAsyncの入力と同じです。 ただし、完全な応答をChatCompletionオブジェクトの一部として返すのではなく、このメソッドはIAsyncEnumerable<T>TとするStreamingChatCompletionUpdateを返し、それによって単一の応答を形成する更新のストリームを提供します。

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

ヒント

ストリーミング API は、AI ユーザー エクスペリエンスとほぼ同義です。 C# を使用すると、 IAsyncEnumerable<T> サポートを使用して説得力のあるシナリオを実現し、自然で効率的な方法でデータをストリーミングできます。

ツールの呼び出し

一部のモデルとサービスでは を呼び出すツール がサポートされています。要求には、モデルが関数を呼び出して追加情報を収集するためのツールを含めることができます。 モデルは、最終的な応答を送信する代わりに、特定の引数を使用して関数呼び出しを要求します。 その後、クライアントは関数を呼び出し、会話履歴と共に結果をモデルに返します。 Microsoft.Extensions.AI ライブラリには、関数呼び出し要求や結果など、さまざまなメッセージ コンテンツ タイプの抽象化が含まれています。 コンシューマーはこのコンテンツを直接操作できますが、 Microsoft.Extensions.AI はこれらの対話を自動化し、次の機能を提供します。

  • AIFunction: AI サービスに記述して呼び出すことができる関数を表します。
  • AIFunctionFactory: AIFunctionの一般的に使用される実装を作成するためのファクトリ メソッドを提供します。
  • FunctionInvokingChatClient: IChatClient をラップして、関数の自動呼び出し機能を追加します。

ランダム関数の呼び出しを示す次の例を考えてみましょう。

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

上記の例は、 📦 Microsoft.Extensions.AI.Ollama NuGet パッケージによって異なります。

上記のコードでは次の操作が行われます。

  • ランダムな天気予報を返す GetCurrentWeather という名前の関数を定義します。
    • この関数は、AI サービスに関数の説明を提供するために使用される DescriptionAttributeで修飾されます。
  • ChatClientBuilder を使用して OllamaChatClient をインスタンス化し、関数呼び出しを使用するように構成します。
  • クライアントで CompleteStreamingAsync を呼び出し、プロンプトと、 Createで作成された関数を含むツールの一覧を渡します。
  • 応答を反復処理し、各更新をコンソールに出力します。

応答のキャッシュ

.NETでの Cachingに慣れている場合は、 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;

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

上記の例は、 📦 Microsoft.Extensions.Caching.Memory NuGet パッケージに依存しています。 詳細については、「.NETでのキャッシュ」を参照してください。

テレメトリを使用する

委任チャット クライアントのもう 1 つの例として、 OpenTelemetryChatClientがあります。 この実装は、 生成 AI システムの OpenTelemetry セマンティック規則に準拠しています。 他の IChatClient デリゲーターと同様、メトリックを階層化し、基盤となる IChatClient 実装の周囲で、監視可能性を強化します。

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

上記の例は、 📦 OpenTelemetry.Exporter.Console NuGet パッケージに依存しています。

プロバイダー オプション

CompleteAsync または CompleteStreamingAsync を呼び出すたびに、必要に応じて、操作の追加パラメーターを含む ChatOptions インスタンスを指定できます。 AI モデルとサービスの中で最も一般的なパラメーターは、 ChatOptions.Temperatureなど、型に厳密に型指定されたプロパティとして表示されます。 他のパラメーターは、ChatOptions.AdditionalProperties ディクショナリを介し、弱い型指定を使用して名前で渡すことができます。

fluent IChatClient API を使用して ChatClientBuilder をビルドし、 ConfigureOptions 拡張メソッドの呼び出しをチェーンする場合にも、オプションを指定できます。 この委任クライアントは、別のクライアントをラップし、指定されたデリゲートを呼び出して、呼び出しごとに ChatOptions インスタンスを設定します。 たとえば、 ChatOptions.ModelId プロパティの既定値が特定のモデル名であることを確認するには、次のようなコードを使用できます。

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

上記の例は、 📦 Microsoft.Extensions.AI.Ollama NuGet パッケージによって異なります。

機能パイプライン

IChatClient インスタンスを階層化してコンポーネントのパイプラインを作成し、それぞれが特定の機能を追加できます。 これらのコンポーネントは、 Microsoft.Extensions.AI、他の NuGet パッケージ、またはカスタム実装から取得できます。 このアプローチを使用すると、特定のニーズを満たすためにさまざまな方法で IChatClient の動作を拡張できます。 サンプル チャット クライアントを中心に分散キャッシュ、関数呼び出し、OpenTelemetry トレースを階層化する次のコード例を考えてみましょう。

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

上記の例は、次の NuGet パッケージに依存しています。

カスタム IChatClient ミドルウェア

追加の機能を追加するには、 IChatClient を直接実装するか、 DelegatingChatClient クラスを使用します。 このクラスは、操作を別の IChatClient インスタンスに委任するチャット クライアントを作成するためのベースとして機能します。 これにより、複数のクライアントのチェーンが簡略化され、基になるクライアントへの呼び出しのパススルーが可能になります。

DelegatingChatClient クラスは、内部クライアントに呼び出しを転送する CompleteAsyncCompleteStreamingAsyncDisposeなどのメソッドの既定の実装を提供します。 このクラスから派生し、基本実装への他の呼び出しを委任しながら、動作を強化するために必要なメソッドのみをオーバーライドできます。 このアプローチは、拡張と作成が簡単な柔軟でモジュール式のチャット クライアントを作成するのに役立ちます。

RateLimiterを利用してレート制限機能を提供するために DelegatingChatClient から派生したクラスの例を次に示します。

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

上記の例は、 📦 System.Threading.RateLimiting NuGet パッケージに依存しています。 別のクライアントとの RateLimitingChatClient の構成は簡単です。

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

このようなコンポーネントを他のコンポーネントと簡単に構成するには、コンポーネント作成者は、コンポーネントをパイプラインに登録するための Use* 拡張メソッドを作成する必要があります。 たとえば、次のような拡張方法について考えてみます。

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>

このような拡張機能は、DI コンテナーから関連するサービスを照会することもできます。パイプラインで使用される IServiceProvider は、省略可能なパラメーターとして渡されます。

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>

その後、コンシューマーはパイプラインでこれを簡単に使用できます。次に例を示します。

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

この例では、コンシューマーが依存関係の挿入を利用して RateLimiter インスタンスを提供するホステッド シナリオを示します。 上記の拡張メソッドは、 ChatClientBuilderUse メソッドを使用する方法を示しています。 ChatClientBuilder には、このような委任ハンドラーの記述を容易にする Use オーバーロードも用意されています。

たとえば、前の RateLimitingChatClient の例では、 CompleteAsyncCompleteStreamingAsync のオーバーライドは、パイプライン内の次のクライアントに委任する前と後にのみ必要です。 カスタム クラスを記述せずに同じことを実現するには、 CompleteAsyncCompleteStreamingAsyncの両方に使用されるデリゲートを受け入れる Use のオーバーロードを使用して、必要な定型句を減らすことができます。

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

上記のオーバーロードは内部的に AnonymousDelegatingChatClientを使用します。これにより、少し追加のコードだけで、より複雑なパターンが可能になります。 たとえば、DIからRateLimiterを取得して同じ結果を得るには、次のようにします。

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

開発者が CompleteAsyncCompleteStreamingAsync の委任実装をインラインで指定し、一意の戻り値の型を特別に処理するためにそれぞれ異なる実装を記述できることが重要なシナリオでは、それぞれにデリゲートを受け入れる Use の別のオーバーロードが存在します。

依存関係の挿入

IChatClient 実装は、通常、 依存関係挿入 (DI)を介してアプリケーションに提供されます。 この例では、 IChatClientと同様に、 IDistributedCache が DI コンテナーに追加されます。 IChatClient の登録では、キャッシュ クライアント (DI から取得した IDistributedCache を使用します) とサンプル クライアントを含むパイプラインを作成するビルダーが使用されます。 挿入された IChatClient は、アプリ内の別の場所で取得して使用できます。

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

上記の例は、次の NuGet パッケージに依存しています。

挿入されるインスタンスと構成は、アプリケーションの現在のニーズに応じて異なる場合があり、複数のパイプラインを異なるキーで挿入できます。

IEmbeddingGenerator インターフェイス

IEmbeddingGenerator<TInput,TEmbedding> インターフェイスは、埋め込みの汎用ジェネレーターを表します。 ここで、 TInput は埋め込まれる入力値の型であり、 TEmbedding は生成された埋め込みの型であり、 Embedding クラスから継承されます。

Embedding クラスは、 IEmbeddingGeneratorによって生成された埋め込みの基底クラスとして機能します。 埋め込みに関連付けられているメタデータとデータを格納および管理するように設計されています。 Embedding<T> などの派生型は、具象埋め込みベクター データを提供します。 たとえば、埋め込みでは、埋め込みデータにアクセスするための Embedding<T>.Vector プロパティが公開されます。

IEmbeddingGenerator インターフェイスは、オプションの構成とキャンセルのサポートを使用して、入力値のコレクションの埋め込みを非同期的に生成するメソッドを定義します。 また、ジェネレーターを記述するメタデータも提供され、ジェネレーターまたはその基になるサービスによって提供できる厳密に型指定されたサービスを取得できます。

実装例

一般的な構造を示すために、ランダム埋め込みベクトルを生成するだけの IEmbeddingGenerator の次の実装例を考えてみましょう。

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

上記のコードでは次の操作が行われます。

  • IEmbeddingGenerator<string, Embedding<float>> インターフェースを実装する SampleEmbeddingGenerator という名前のクラスを定義します。
  • ジェネレーターを識別するために使用されるエンドポイントとモデル ID を受け入れるプライマリ コンストラクターがあります。
  • ジェネレーターに関するメタデータを提供する Metadata プロパティを公開します。
  • 入力値のコレクションの埋め込みを生成する GenerateAsync メソッドを実装します。
    • 100 ミリ秒の遅延によって非同期操作をシミュレートします。
    • 各入力値のランダム埋め込みを返します。

実際の具体的な実装は、次のパッケージで確認できます。

埋め込みの作成

IEmbeddingGenerator<TInput,TEmbedding> で実行される主な操作は、 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()));
}

カスタム IEmbeddingGenerator ミドルウェア

IChatClient と同様に、 IEmbeddingGenerator 実装を階層化できます。 Microsoft.Extensions.AI がキャッシュとテレメトリの IChatClient の委任実装を提供するのと同様に、 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()));
}

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

このようにして、レート制限機能を提供するために、 IEmbeddingGenerator<string, Embedding<float>> を他の RateLimitingEmbeddingGenerator インスタンスと共に構成できます。

関連項目