次の方法で共有


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 パッケージへの暗黙的な依存関係があります。 このパッケージを使用すると、使い慣れた依存関係の挿入とミドルウェア パターンを使用して、関数ツールの自動呼び出し、テレメトリ、キャッシュなどのコンポーネントをアプリケーションに簡単に統合できます。 たとえば、チャット クライアント パイプラインに OpenTelemetry のサポートを追加する UseOpenTelemetry(ChatClientBuilder, ILoggerFactory, String, Action<OpenTelemetryChatClient>) 拡張メソッドを提供します。

参照するパッケージ

抽象化の実装を提供するライブラリは、通常、 Microsoft.Extensions.AI.Abstractionsのみを参照します。

また、生成型 AI コンポーネントを操作するための上位レベルのユーティリティにもアクセスできるようにするには、代わりに Microsoft.Extensions.AI パッケージを参照します (それ自体は Microsoft.Extensions.AI.Abstractionsを参照します)。 ほとんどの使用アプリケーションとサービスは、抽象化の具象実装を提供する 1 つ以上のライブラリと共に、 Microsoft.Extensions.AI パッケージを参照する必要があります。

パッケージのインストール

NuGet パッケージをインストールする方法については、「.NET アプリケーションでの dotnet パッケージの追加 または パッケージの依存関係の管理」を参照してください。

API の使用例

次のサブセクションでは、特定の IChatClient の使用例を示します。

次のセクションでは、 特定の IEmbeddingGenerator の使用例を示します。

IChatClient インターフェイス

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

言語モデルとサービスのクライアントを提供する .NET ライブラリは、 IChatClient インターフェイスの実装を提供できます。 その後、インターフェイスのすべてのコンシューマーは、抽象化を介してこれらのモデルやサービスとシームレスに相互運用できます。

チャット応答を要求する

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

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

ChatResponseから返されるGetResponseAsyncは、操作の一部として生成された 1 つ以上のメッセージを表すChatMessageインスタンスの一覧を公開します。 一般的なケースでは、応答メッセージは 1 つだけですが、状況によっては複数のメッセージが存在する場合があります。 メッセージ 一覧は、リスト内の最後のメッセージが要求の最後のメッセージを表すように並べ替えされます。 後続の要求でこれらのすべての応答メッセージをサービスに返すには、応答からのメッセージをメッセージ一覧に追加し直します。

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>TとするChatResponseUpdateを返し、それによって単一の応答を形成する更新のストリームを提供します。

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

ヒント

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

GetResponseAsyncと同様に、IChatClient.GetStreamingResponseAsyncからの更新をメッセージ一覧に追加できます。 更新は応答の個々の部分であるため、 ToChatResponse(IEnumerable<ChatResponseUpdate>) などのヘルパーを使用して、1 つ以上の更新を 1 つの 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);
}

ツールの呼び出し

一部のモデルとサービスでは 、ツールの呼び出しがサポートされています。 追加情報を収集するために、モデルがクライアントに呼び出しを要求できるツール (通常は .NET メソッド) に関する情報を使用して ChatOptions を構成できます。 モデルは、最終的な応答を送信する代わりに、特定の引数を使用して関数呼び出しを要求します。 その後、クライアントは関数を呼び出し、会話履歴を使用して結果をモデルに送り返します。 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 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での 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;
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();
}

この例は📦 NuGet パッケージによって異なります。 詳細については、「.NETでのキャッシュ」を参照してください。

テレメトリを使用する

委任チャット クライアントのもう 1 つの例として、 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 に簡単に書き込む方法が提供されます。

プロバイダー オプション

GetResponseAsync または GetStreamingResponseAsync を呼び出すたびに、必要に応じて、操作の追加パラメーターを含む 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 ライブラリを使用してレート制限機能を提供するから派生したクラスの例を次に示します。

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

前の拡張メソッドは、UseChatClientBuilderメソッドを使用する方法を示しています。 ChatClientBuilder また、このような委任ハンドラーの記述を容易にする Use オーバーロードも提供します。 たとえば、前の RateLimitingChatClient の例では、 GetResponseAsyncGetStreamingResponseAsync のオーバーライドは、パイプライン内の次のクライアントに委任する前と後にのみ必要です。 カスタム クラスを記述せずに同じことを実現するには、 UseGetResponseAsyncの両方に使用されるデリゲートを受け入れる 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();

一意の戻り値の型を処理するために、 GetResponseAsyncGetStreamingResponseAsync に異なる実装が必要なシナリオでは、それぞれにデリゲートを受け入れる Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken, Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions, IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>) オーバーロードを使用できます。

依存関係の挿入

IChatClient 実装は、多くの場合、 依存関係の挿入 (DI) を介してアプリケーションに提供されます。 この例では、 IDistributedCacheと同様に、 IChatClient が DI コンテナーに追加されます。 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 サービスとステートフル 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));
}

一部のサービスでは、1 つも持たない要求の会話 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()));
}

アクセラレータ拡張メソッドは、1 つの入力から埋め込みベクターを生成するなど、一般的なケースを簡略化するためにも存在します。

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 を参照してください。

こちらも参照ください