다음을 통해 공유


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을(를) 자체적으로 참조합니다. 대부분의 소비 애플리케이션 및 서비스는 추상화의 Microsoft.Extensions.AI 구체적인 구현을 제공하는 하나 이상의 라이브러리와 함께 패키지를 참조해야 합니다.

패키지 설치

NuGet 패키지를 설치하는 방법에 대한 자세한 내용은 .NET 애플리케이션에서 dotnet 패키지 추가 또는 패키지 종속성 관리를 참조하세요.

API 사용 예시

다음 하위 섹션에서는 특정 IChatClient 사용 예제를 보여 줍니다.

다음 섹션에서는 특정 IEmbeddingGenerator 사용 예제를 보여 줍니다.

IChatClient 인터페이스

IChatClient 인터페이스는 채팅 기능을 제공하는 AI 서비스와 상호 작용하는 클라이언트 추상화 작업을 정의합니다. 이 방법에는 텍스트, 이미지 및 오디오와 같은 다중 모달 콘텐츠를 사용하여 메시지를 보내고 받는 기능이 포함되며, 이는 전체 집합으로 또는 점진적 스트리밍으로 이루어질 수 있습니다. 또한 클라이언트 또는 해당 기본 서비스에서 제공하는 강력한 형식의 서비스를 검색할 수 있습니다.

언어 모델 및 서비스에 대한 클라이언트를 제공하는 .NET 라이브러리는 IChatClient 인터페이스의 구현을 제공할 수 있습니다. 그러면 인터페이스의 모든 소비자가 추상화로 이러한 모델 및 서비스와 원활하게 상호 운용할 수 있습니다. IChatClient 및 IEmbeddingGenerator의 샘플 구현에서 간단한 구현을 볼 수 있습니다.

채팅 응답 요청

인스턴스 IChatClient를 사용하여 메서드를 IChatClient.GetResponseAsync 호출하여 요청을 보내고 응답을 가져올 수 있습니다. 요청은 하나 이상의 메시지로 구성되며 각 메시지는 하나 이상의 콘텐츠로 구성됩니다. 액셀러레이터 메서드는 단일 텍스트 콘텐츠에 대한 요청 생성과 같은 일반적인 사례를 간소화하기 위해 존재합니다.

using Microsoft.Extensions.AI;
using OllamaSharp;

IChatClient client = new OllamaApiClient(
    new Uri("http://localhost:11434/"), "phi3:mini");

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는 작업의 일부로 생성된 하나 이상의 메시지를 나타내는 ChatMessage 인스턴스 목록을 포함합니다. 일반적으로 하나의 응답 메시지만 있지만 경우에 따라 여러 메시지가 있을 수 있습니다. 목록의 마지막 메시지가 요청에 대한 최종 메시지를 나타내도록 메시지 목록의 순서가 지정됩니다. 후속 요청에서 이러한 모든 응답 메시지를 서비스에 다시 제공하려면 응답의 메시지를 메시지 목록에 다시 추가할 수 있습니다.

List<ChatMessage> history = [];
while (true)
{
    Console.Write("Q: ");
    history.Add(new(ChatRole.User, Console.ReadLine()));

    ChatResponse response = await client.GetResponseAsync(history);
    Console.WriteLine(response);

    history.AddMessages(response);
}

스트리밍 채팅 응답 요청

IChatClient.GetStreamingResponseAsync 입력은 GetResponseAsync입력과 동일합니다. 메서드는 전체 응답을 ChatResponse 개체의 일부로 반환하는 대신 IAsyncEnumerable<T>TChatResponseUpdate을 반환하여, 이 업데이트 스트림이 모여서 단일 응답을 형성하도록 합니다.

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

팁 (조언)

스트리밍 API는 AI 사용자 환경과 거의 동의어입니다. C#을 사용하면 IAsyncEnumerable<T> 지원으로 매력적인 시나리오를 구현하여 자연스럽고 효율적인 방식으로 데이터를 스트리밍할 수 있습니다.

GetResponseAsync와 마찬가지로, 업데이트를 IChatClient.GetStreamingResponseAsync에서 메시지 목록에 다시 추가할 수 있습니다. 업데이트는 응답의 개별 부분이므로 도우미를 사용하여 ToChatResponse(IEnumerable<ChatResponseUpdate>) 하나 이상의 업데이트를 단일 ChatResponse 인스턴스로 다시 작성할 수 있습니다.

도우미AddMessagesChatResponse을 구성한 다음, 응답에서 구성된 메시지를 추출하여 목록에 추가합니다.

List<ChatMessage> chatHistory = [];
while (true)
{
    Console.Write("Q: ");
    chatHistory.Add(new(ChatRole.User, Console.ReadLine()));

    List<ChatResponseUpdate> updates = [];
    await foreach (ChatResponseUpdate update in
        client.GetStreamingResponseAsync(history))
    {
        Console.Write(update);
        updates.Add(update);
    }
    Console.WriteLine();

    chatHistory.AddMessages(updates);
}

도구 호출

일부 모델 및 서비스는 도구 호출을 지원합니다. 추가 정보를 수집하려면 모델이 클라이언트에 ChatOptions 호출을 요청할 수 있는 도구(일반적으로 .NET 메서드)에 대한 정보를 사용하여 구성할 수 있습니다. 최종 응답을 보내는 대신 모델은 특정 인수를 사용하여 함수 호출을 요청합니다. 그런 다음, 클라이언트는 함수를 호출하고 대화 기록을 사용하여 결과를 모델로 다시 보냅니다. Microsoft.Extensions.AI.Abstractions 라이브러리에는 함수 호출 요청 및 결과를 포함하여 다양한 메시지 콘텐츠 형식에 대한 추상화가 포함됩니다. 소비자는 이 콘텐츠와 직접 상호 작용할 수 있지만 IChatClient 해당 Microsoft.Extensions.AI 요청에 대한 응답으로 도구를 자동으로 호출할 수 있는 도우미를 제공합니다. Microsoft.Extensions.AI.AbstractionsMicrosoft.Extensions.AI 라이브러리는 다음 형식을 제공합니다.

  • AIFunction: AI 모델에 설명하고 호출할 수 있는 함수를 나타냅니다.
  • AIFunctionFactory: .NET 메서드를 나타내는 인스턴스를 만들기 AIFunction 위한 팩터리 메서드를 제공합니다.
  • FunctionInvokingChatClient: IChatClientIChatClient을(를) 감싸 자동 함수 호출 기능을 추가합니다.

다음 예제에서는 임의 함수 호출을 보여 줍니다(이 예제는 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캐싱을 잘 알고 있다면, 에서 다른 위임 구현을 제공한다는 점을 알아두면 유익합니다. DistributedCachingChatClient은 다른 임의의 IChatClient 인스턴스 주위에 캐싱을 레이어하는 IChatClient입니다. 새 채팅 기록이 제출되면 DistributedCachingChatClient기본 클라이언트로 전달한 다음 응답을 캐시한 후 다시 소비자에게 보냅니다. 캐시된 응답을 캐시 DistributedCachingChatClient 에서 찾을 수 있도록 다음에 동일한 기록이 제출될 때 파이프라인을 따라 요청을 전달하지 않고 캐시된 응답을 반환합니다.

using Microsoft.Extensions.AI;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using OllamaSharp;

var sampleChatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1");

IChatClient client = new ChatClientBuilder(sampleChatClient)
    .UseDistributedCache(new MemoryDistributedCache(
        Options.Create(new MemoryDistributedCacheOptions())))
    .Build();

string[] prompts = ["What is AI?", "What is .NET?", "What is AI?"];

foreach (var prompt in prompts)
{
    await foreach (var update in client.GetStreamingResponseAsync(prompt))
    {
        Console.Write(update);
    }
    Console.WriteLine();
}

이 예제는 Microsoft.Extensions.Caching.Memory NuGet 패키지에📦 따라 달라집니다. 자세한 내용은 .NET에서의 캐싱을 참조하세요.

원격 분석 사용

위임 채팅 클라이언트의 또 다른 예는 OpenTelemetryChatClient. 이 구현은생성 AI 시스템에 대한 OpenTelemetry 의미 체계 규칙을 준수합니다. 다른 IChatClient 위임자와 마찬가지로, 메트릭과 스팬을 계층화하여 다른 임의의 IChatClient 구현을 둘러쌉니다.

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

// Configure OpenTelemetry exporter.
string sourceName = Guid.NewGuid().ToString();
TracerProvider tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
    .AddSource(sourceName)
    .AddConsoleExporter()
    .Build();

IChatClient ollamaClient = new OllamaApiClient(
    new Uri("http://localhost:11434/"), "phi3:mini");

IChatClient client = new ChatClientBuilder(ollamaClient)
    .UseOpenTelemetry(
        sourceName: sourceName,
        configure: c => c.EnableSensitiveData = true)
    .Build();

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

앞의 📦 예제는 OpenTelemetry.Exporter.Console NuGet 패키지에 따라 달라집니다.

LoggingChatClientUseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) 메서드는 모든 요청 및 응답에 대한 로그 항목을 ILogger에 기록하는 간단한 방법을 제공합니다.

옵션 제공

GetResponseAsync 또는 GetStreamingResponseAsync 대한 모든 호출은 필요에 따라 작업에 대한 추가 매개 변수를 포함하는 ChatOptions 인스턴스를 제공할 수 있습니다. AI 모델 및 서비스에서 가장 일반적인 매개 변수는 형식에서 강력한 형 지정 속성으로(예: ChatOptions.Temperature) 표시됩니다. 다른 매개 변수는 약하게 형식화된 방식으로 ChatOptions.AdditionalProperties 사전을 통해 이름으로 제공되거나, 기본 공급자가 이해하는 옵션 인스턴스를 통해 ChatOptions.RawRepresentationFactory 속성으로 제공될 수 있습니다.

흐름 API인 IChatClient를 사용하여 ChatClientBuilder을(를) 빌드할 때 확장 메서드 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 클래스는 내부 클라이언트에 호출을 전달하는 GetResponseAsync, GetStreamingResponseAsyncDispose같은 메서드에 대한 기본 구현을 제공합니다. 파생 클래스는 기본 구현에 대한 다른 호출을 위임하는 동시에 동작을 보강하는 데 필요한 메서드만 재정의할 수 있습니다. 이 방법은 확장 및 작성하기 쉬운 유연하고 모듈식 채팅 클라이언트를 만드는 데 유용합니다.

다음은 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);

IChatClient client = new OllamaApiClient(
    new Uri("http://localhost:11434/"),
    "phi3:mini");

builder.Services.AddChatClient(services =>
        client
        .AsBuilder()
        .UseDistributedCache()
        .UseRateLimiting()
        .UseOpenTelemetry()
        .Build(services));

이전 확장 메서드는 Use 메서드를 ChatClientBuilder에 사용하는 방법을 보여 줍니다. 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이 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를 만들 수 있습니다. 이러한 경우, 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는 포함되는 입력 값의 타입이고, TEmbeddingEmbedding 클래스를 상속받는 생성된 포함의 타입입니다.

Embedding 클래스는 IEmbeddingGenerator에 의해 생성된 포함의 기본 클래스 역할을 합니다. 포함과 관련된 메타데이터 및 데이터를 저장하고 관리하도록 설계되었습니다. 파생 형식(예: Embedding<T>구체적인 포함 벡터 데이터)을 제공합니다. 예를 들어, Embedding<float>는 그것의 포함 데이터를 액세스하기 위한 ReadOnlyMemory<float> Vector { get; } 속성을 노출합니다.

IEmbeddingGenerator 인터페이스는 선택적 구성 및 취소 지원을 사용하여 입력 값 모음에 대해 임베딩을 비동기적으로 생성하는 메서드를 정의합니다. 또한 생성기를 설명하는 메타데이터를 제공하고 생성기 또는 해당 기본 서비스에서 제공할 수 있는 강력한 형식의 서비스를 검색할 수 있습니다.

대부분의 사용자는 인터페이스를 구현 IEmbeddingGenerator 할 필요가 없습니다. 그러나 라이브러리 작성자인 경우 IChatClient 및 IEmbeddingGenerator의 샘플 구현에서 간단한 구현을 볼 수 있습니다.

임베딩 생성

IEmbeddingGenerator<TInput,TEmbedding>을 사용하여 수행되는 주요 작업은 GenerateAsync 메서드를 이용한 임베딩 생성입니다.

using Microsoft.Extensions.AI;
using OllamaSharp;

IEmbeddingGenerator<string, Embedding<float>> generator =
    new OllamaApiClient(new Uri("http://localhost:11434/"), "phi3:mini");

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 OllamaSharp;
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 OllamaApiClient(new Uri("http://localhost:11434/"), "phi3:mini"))
    .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()));
}

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 OllamaSharp;
using System.Threading.RateLimiting;

IEmbeddingGenerator<string, Embedding<float>> generator =
    new RateLimitingEmbeddingGenerator(
        new OllamaApiClient(new Uri("http://localhost:11434/"), "phi3:mini"),
        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 패키지를 쉽게 통합할 수 있습니다. 예제 구현은 IChatClient 및 IEmbeddingGenerator의 샘플 구현을 참조하세요.
  • 서비스 소비자: AI 서비스를 사용하는 라이브러리를 개발하는 경우 특정 AI 서비스에 대한 하드코딩 대신 추상화 기능을 사용합니다. 이 접근 방식을 통해 소비자는 선호하는 공급자를 유연하게 선택할 수 있습니다.
  • 애플리케이션 개발자: 추상화로 앱 통합을 간소화합니다. 이렇게 하면 모델 및 서비스 전반에서 이식성을 구현하고, 테스트 및 모의 작업을 용이하게 하고, 에코시스템에서 제공하는 미들웨어를 활용하며, 애플리케이션의 여러 부분에서 다른 서비스를 사용하더라도 앱 전체에서 일관된 API를 유지 관리합니다.
  • 에코시스템 기여자는: 에코시스템에 기여하려는 경우 사용자 지정 미들웨어 구성 요소를 작성하는 것이 좋습니다.

자세한 샘플은 dotnet/ai-samples GitHub 리포지토리를 참조하세요. 엔드 투 엔드 샘플은 eShopSupport를 참조하세요.

참고하십시오