다음을 통해 공유


.NET의 인공 지능(미리 보기)

점점 더 다양한 AI(인공 지능) 서비스를 사용할 수 있으므로 개발자는 .NET 애플리케이션에서 이러한 서비스를 통합하고 상호 작용하는 방법이 필요합니다. Microsoft.Extensions.AI 라이브러리는 다양한 AI 서비스와의 원활한 통합 및 상호 운용성을 가능하게 하는 생성 AI 구성 요소를 나타내는 통합된 접근 방식을 제공합니다. 이 문서에서는 라이브러리를 소개하고 시작하는 데 도움이 되는 설치 지침 및 사용 예제를 제공합니다.

패키지 설치

📦 Microsoft.Extensions.AI NuGet 패키지를 설치하려면 .NET CLI를 사용하거나 C# 프로젝트 파일에 직접 패키지 참조를 추가합니다.

dotnet add package Microsoft.Extensions.AI --prerelease

자세한 내용은 dotnet 패키지 추가 또는 .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() { }
}

다음 NuGet 패키지에서 IChatClient 다른 구체적인 구현을 찾을 수 있습니다.

채팅 완료 요청

완료를 요청하려면 IChatClient.CompleteAsync 메서드를 호출합니다. 요청은 하나 이상의 메시지로 구성되며 각 메시지는 하나 이상의 콘텐츠로 구성됩니다. 액셀러레이터 메서드는 단일 텍스트 콘텐츠에 대한 요청 생성과 같은 일반적인 사례를 간소화하기 위해 존재합니다.

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>TStreamingChatCompletionUpdate을 반환하여, 이 업데이트 스트림이 모여서 단일 응답을 형성하도록 합니다.

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캐싱을 잘 알고 있다면, 에서 다른 위임 구현을 제공한다는 점을 알아두면 유익합니다. 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에서의 캐싱을 참조하세요.

원격 분석 사용

위임 채팅 클라이언트의 또 다른 예는 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 사전을 통해 약하게 형식화된 방식으로 이름으로 제공할 수 있습니다.

옵션을 지정하여 IChatClient를 빌드할 수 있으며, 이를 위해 fluent ChatClientBuilder API를 사용하고 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 클래스는 내부 클라이언트에 호출을 전달하는 CompleteAsync, CompleteStreamingAsyncDispose같은 메서드에 대한 기본 구현을 제공합니다. 필요한 메서드만 재정의하여 동작을 향상시키는 동시에 다른 호출은 기본 구현에 위임할 수 있도록 이 클래스를 상속받을 수 있습니다. 이 방법은 확장 및 작성하기 쉬운 유연하고 모듈식 채팅 클라이언트를 만드는 데 도움이 됩니다.

다음은 DelegatingChatClient에서 파생되어 RateLimiter을 활용하여 속도 제한 기능을 제공하는 예제 클래스입니다.

using Microsoft.Extensions.AI;
using System.Runtime.CompilerServices;
using System.Threading.RateLimiting;

public sealed class RateLimitingChatClient(
    IChatClient innerClient, RateLimiter rateLimiter)
        : DelegatingChatClient(innerClient)
{
    public override async Task<ChatCompletion> CompleteAsync(
        IList<ChatMessage> chatMessages,
        ChatOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
            .ConfigureAwait(false);

        if (!lease.IsAcquired)
        {
            throw new InvalidOperationException("Unable to acquire lease.");
        }

        return await base.CompleteAsync(chatMessages, options, cancellationToken)
            .ConfigureAwait(false);
    }

    public override async IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(
        IList<ChatMessage> chatMessages,
        ChatOptions? options = null,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
            .ConfigureAwait(false);

        if (!lease.IsAcquired)
        {
            throw new InvalidOperationException("Unable to acquire lease.");
        }

        await foreach (var update in base.CompleteStreamingAsync(chatMessages, options, cancellationToken)
            .ConfigureAwait(false))
        {
            yield return update;
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            rateLimiter.Dispose();
        }

        base.Dispose(disposing);
    }
}

앞의 예제는 📦 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을 보여줍니다. 앞서 언급한 확장 메서드는 Use 메서드를 ChatClientBuilder에서 사용하는 방법을 보여 줍니다. 또한 ChatClientBuilder 이러한 위임 처리기를 더 쉽게 작성할 수 있는 Use 오버로드를 제공합니다.

예를 들어 이전 RateLimitingChatClient 예제에서 CompleteAsyncCompleteStreamingAsync 재정의는 파이프라인의 다음 클라이언트에 위임하기 전과 후에만 작업을 진행해야 합니다. 사용자 지정 클래스를 작성할 필요 없이 동일한 작업을 수행하려면 UseCompleteAsync모두에 사용될 대리자를 허용하는 CompleteStreamingAsync의 오버로드 기능을 사용하여 반복적인 코드를 줄일 수 있습니다.

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)을 통해 애플리케이션에 제공될 것입니다. 이 예제에서는 IDistributedCache이 DI 컨테이너에 추가되고, IChatClient도 추가됩니다. 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 포함되는 입력 값의 형식이며, TEmbeddingEmbedding 클래스에서 상속되는 생성된 포함 형식입니다.

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

앞의 코드는 다음과 같습니다.

  • SampleEmbeddingGenerator 인터페이스를 구현하는 IEmbeddingGenerator<string, Embedding<float>> 클래스를 정의합니다.
  • 생성기를 식별하는 데 사용되는 엔드포인트 및 모델 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()));
}

IEmbeddingGeneratorIEmbeddingGenerator의 기능을 확장할 수 있는 사용자 지정 미들웨어를 구축할 수 있도록 합니다. 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()));
}

이러한 방식으로 RateLimitingEmbeddingGenerator 다른 IEmbeddingGenerator<string, Embedding<float>> 인스턴스와 함께 구성하여 속도 제한 기능을 제공할 수 있습니다.

참고하세요