Sztuczna inteligencja na platformie .NET (wersja zapoznawcza)
W przypadku coraz większej gamy dostępnych usług sztucznej inteligencji deweloperzy potrzebują sposobu integrowania tych usług i interakcji z nimi w aplikacjach platformy .NET. Biblioteka Microsoft.Extensions.AI
zapewnia ujednolicone podejście do reprezentowania składników generacyjnych sztucznej inteligencji, co umożliwia bezproblemową integrację i współdziałanie z różnymi usługami sztucznej inteligencji. Ten artykuł zawiera wprowadzenie do biblioteki oraz zawiera instrukcje instalacji i przykłady użycia ułatwiające rozpoczęcie pracy.
Instalowanie pakietu
Aby zainstalować pakiet 📦 Microsoft.Extensions.AI NuGet, użyj interfejsu wiersza polecenia platformy .NET lub dodaj odwołanie do pakietu bezpośrednio do pliku projektu C#:
dotnet add package Microsoft.Extensions.AI --prelease
Aby uzyskać więcej informacji, zobacz dotnet add package lub Zarządzanie zależnościami pakietów w aplikacjach .NET.
Przykłady użycia
Interfejs IChatClient definiuje abstrakcję klienta odpowiedzialną za interakcję z usługami sztucznej inteligencji, które zapewniają możliwości czatu. Zawiera metody wysyłania i odbierania wiadomości z treściami multimodalnymi (takimi jak tekst, obrazy i dźwięk) jako kompletne zestawy lub strumieniowo w sposób przyrostowy. Ponadto udostępnia on metadane o kliencie i umożliwia pobieranie silnie typiowanych usług.
Ważny
Aby uzyskać więcej przykładów użycia i rzeczywistych scenariuszy, zobacz AI dla deweloperów platformy .NET.
W tej sekcji
Interfejs IChatClient
Poniższy przykład implementuje IChatClient
, aby pokazać ogólną strukturę.
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() { }
}
Inne konkretne implementacje IChatClient
można znaleźć w następujących pakietach NuGet:
- 📦 Microsoft.Extensions.AI.AzureAIInference: implementacja wspierana przez interfejs API wnioskowań modelu Azure AI .
- 📦 Microsoft.Extensions.AI.Ollama: implementacja wspierana przez Ollama.
- 📦 Microsoft.Extensions.AI.OpenAI: implementacja wspierana przez OpenAI lub punkty końcowe zgodne z platformą OpenAI (takie jak azure OpenAI).
Uzupełnienie czatu na żądanie
Aby zażądać ukończenia, wywołaj metodę IChatClient.CompleteAsync. Żądanie składa się z co najmniej jednego komunikatu, z których każda składa się z co najmniej jednego fragmentu zawartości. Istnieją metody akceleratora, aby uprościć typowe przypadki, takie jak konstruowanie żądania dla pojedynczego fragmentu zawartości tekstowej.
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);
Podstawowa metoda IChatClient.CompleteAsync
akceptuje listę komunikatów. Ta lista reprezentuje historię wszystkich wiadomości, które są częścią konwersacji.
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?"),
]));
Każdy komunikat w historii jest reprezentowany przez obiekt ChatMessage. Klasa ChatMessage
udostępnia właściwość ChatMessage.Role wskazującą rolę komunikatu. Domyślnie jest używana ChatRole.User. Dostępne są następujące role:
- ChatRole.Assistant: instruuje lub ustawia zachowanie asystenta.
- ChatRole.System: zapewnia odpowiedzi na dane wejściowe inicjowane przez użytkownika i poinstruowane przez system.
- ChatRole.Tool: zawiera dodatkowe informacje i odwołania do ukończenia czatu.
- ChatRole.User: udostępnia dane wejściowe na potrzeby uzupełniania czatu.
Każda wiadomość czatu jest tworzona, przypisując nową TextContentdo jej właściwości Contents. Istnieją różne typy zawartości, które mogą być reprezentowane, takie jak prosty ciąg lub bardziej złożony obiekt reprezentujący wielomodalny komunikat z tekstem, obrazami i dźwiękiem:
- AudioContent
- DataContent
- FunctionCallContent
- FunctionResultContent
- ImageContent
- TextContent
- UsageContent
Żądanie ukończenia czatu za pomocą przesyłania strumieniowego
Dane wejściowe IChatClient.CompleteStreamingAsync są identyczne z danymi CompleteAsync
. Jednak zamiast zwracać pełną odpowiedź w ramach obiektu ChatCompletion metoda zwraca IAsyncEnumerable<T>, w której T
jest StreamingChatCompletionUpdate, zapewniając strumień aktualizacji, które zbiorczo tworzą pojedynczą odpowiedź.
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);
}
Napiwek
Interfejsy API przesyłania strumieniowego są niemal synonimem doświadczeń użytkownika związanych ze sztuczną inteligencją. Język C# umożliwia interesujące scenariusze dzięki obsłudze IAsyncEnumerable<T>
, co pozwala na naturalne i wydajne strumieniowanie danych.
Wywoływanie narzędzi
Niektóre modele i usługi obsługują wywoływanie narzędzi , gdzie żądania mogą zawierać narzędzia umożliwiające modelowi wywoływanie funkcji w celu zebrania dodatkowych informacji. Zamiast wysyłać ostateczną odpowiedź, model żąda wywołania funkcji z określonymi argumentami. Następnie klient wywołuje funkcję i wysyła wyniki z powrotem do modelu wraz z historią konwersacji. Biblioteka Microsoft.Extensions.AI
zawiera abstrakcje dla różnych typów zawartości komunikatów, w tym żądania wywołań funkcji i wyniki. Użytkownicy mogą bezpośrednio korzystać z tej zawartości, Microsoft.Extensions.AI
automatyzuje te interakcje i zapewnia następujące możliwości:
- AIFunction: reprezentuje funkcję, którą można opisać w usłudze sztucznej inteligencji i wywołać.
-
AIFunctionFactory: udostępnia metody fabryczne do tworzenia powszechnie używanych implementacji
AIFunction
. -
FunctionInvokingChatClient: opakowuje
IChatClient
, aby dodać możliwość automatycznego wywołania funkcji.
Rozważmy następujący przykład, który demonstruje wywołanie funkcji losowej:
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);
}
Powyższy przykład zależy od pakietu 📦 Microsoft.Extensions.AI.Ollama NuGet.
Powyższy kod:
- Definiuje funkcję o nazwie
GetCurrentWeather
, która zwraca losową prognozę pogody.- Ta funkcja jest ozdobiona DescriptionAttribute, która służy do podawania opisu funkcji w usłudze sztucznej inteligencji.
- Tworzy wystąpienie ChatClientBuilder za pomocą OllamaChatClient i konfiguruje je do wywoływania funkcji.
- Wywołuje
CompleteStreamingAsync
na kliencie, przekazując monit oraz listę narzędzi, w której znajduje się funkcja utworzona za pomocą Create. - Iteruje przez odpowiedź, wyświetlając każdą aktualizację na konsoli.
Odpowiedzi pamięci podręcznej
Jeśli znasz buforowanie na platformie .NET, warto wiedzieć, że Microsoft.Extensions.AI udostępnia inne takie delegujące implementacje IChatClient
.
DistributedCachingChatClient to IChatClient
, która warstwuje buforowanie wokół innego dowolnego wystąpienia IChatClient
. Gdy do DistributedCachingChatClient
zostanie przesłana unikatowa historia czatu, przekazuje ją do klienta bazowego, a następnie buforuje odpowiedź przed wysłaniem jej z powrotem do odbiorcy. Przy następnym przesłaniu tej samej komendy w taki sposób, że w pamięci podręcznej można znaleźć odpowiedź, DistributedCachingChatClient
zwraca odpowiedź z pamięci podręcznej zamiast przekazywać żądanie dalej wzdłuż potoku.
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();
}
Powyższy przykład zależy od pakietu NuGet 📦 Microsoft.Extensions.Caching.Memory. Aby uzyskać więcej informacji, zobacz buforowanie na platformie .NET.
Korzystanie z telemetrii
Innym przykładem delegowania klienta czatu jest OpenTelemetryChatClient. Ta implementacja jest zgodna z konwencjami semantycznymi OpenTelemetry dla systemów generowania sztucznej inteligencji. Podobnie jak inne IChatClient
delegatory, warstwuje metryki i obejmuje wszystkie podstawowe IChatClient
implementacji, zapewniając lepszą obserwację.
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);
Powyższy przykład zależy od pakietu NuGet 📦 OpenTelemetry.Exporter.Console.
Podaj opcje
Każde wywołanie CompleteAsync lub CompleteStreamingAsync może opcjonalnie dostarczyć wystąpienie ChatOptions zawierające dodatkowe parametry dla operacji. Najbardziej powszechne parametry modeli i usług sztucznej inteligencji są wyświetlane jako silnie typizowane właściwości typu, takie jak ChatOptions.Temperature. Inne parametry mogą być podawane z użyciem ich nazw w sposób słabo typowany za pośrednictwem słownika ChatOptions.AdditionalProperties.
Opcje można również określić podczas tworzenia IChatClient
za pomocą płynnego interfejsu API ChatClientBuilder i łączenia wywołania metody rozszerzenia ConfigureOptions
. Ten delegujący klient opakowuje innego klienta i wywołuje dostarczonego delegata, aby wypełniał wystąpienie ChatOptions
przy każdym wywołaniu. Aby na przykład upewnić się, że właściwość ChatOptions.ModelId jest domyślnie ustawiona na określoną nazwę modelu, możesz użyć kodu podobnego do następującego:
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" }));
Powyższy przykład zależy od pakietu 📦 Microsoft.Extensions.AI.Ollama NuGet.
Linie funkcjonalności
IChatClient
wystąpienia można warstwować w celu utworzenia potoku składników, z których każdy dodaje określoną funkcjonalność. Te składniki mogą pochodzić z Microsoft.Extensions.AI
, innych pakietów NuGet lub niestandardowych implementacji. Takie podejście pozwala rozszerzyć zachowanie IChatClient
na różne sposoby, aby spełnić określone potrzeby. Rozważmy następujący przykładowy kod, który warstwuje rozproszoną pamięć podręczną, wywołanie funkcji i śledzenie OpenTelemetry wokół przykładowego klienta czatu:
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));
}
Powyższy przykład zależy od następujących pakietów NuGet:
- 📦 Microsoft.Extensions.Caching.Memory
- 📦 Microsoft.Extensions.AI.Ollama
- 📦 OpenTelemetry.Exporter.Console
Niestandardowe oprogramowanie pośredniczące IChatClient
Aby dodać dodatkowe funkcje, możesz zaimplementować IChatClient
bezpośrednio lub użyć klasy DelegatingChatClient. Ta klasa służy jako podstawa do tworzenia klientów czatu, którzy delegują operacje do innego wystąpienia IChatClient
. Upraszcza tworzenie łańcuchów wielu klientów, umożliwiając przekazywanie wywołań do bazowego klienta.
Klasa DelegatingChatClient
udostępnia domyślne implementacje metod, takich jak CompleteAsync
, CompleteStreamingAsync
i Dispose
, które przekazują wywołania do klienta wewnętrznego. Możesz dziedziczyć z tej klasy i nadpisać tylko te metody, które są potrzebne do rozszerzenia działania, jednocześnie delegując inne wywołania do implementacji bazowej. Takie podejście ułatwia tworzenie elastycznych i modułowych klientów czatów, które można łatwo rozszerzać i tworzyć.
Poniżej przedstawiono przykładową klasę pochodzącą z DelegatingChatClient
w celu zapewnienia funkcji ograniczania szybkości przy użyciu 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);
}
}
Powyższy przykład zależy od pakietu 📦 System.Threading.RateLimiting NuGet. Kompozycja RateLimitingChatClient
z innym klientem jest prosta:
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?");
Aby uprościć kompozycję takich składników z innymi, autorzy składników powinni utworzyć metodę rozszerzającą Use*
w celu zarejestrowania składnika w potoku. Rozważmy na przykład następującą metodę rozszerzenia:
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>
Takie rozszerzenia mogą również wysyłać zapytania o odpowiednie usługi z kontenera DI; IServiceProvider używany przez potok jest przekazywany jako opcjonalny parametr:
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>
Konsument może następnie łatwo użyć tego w swoim procesie przetwarzania danych, na przykład:
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();
W tym przykładzie pokazano hostowany scenariusz, w którym użytkownik korzysta z wstrzykiwania zależności w celu udostępnienia wystąpienia RateLimiter
. Powyższe metody rozszerzenia pokazują użycie metody Use
w ChatClientBuilder.
ChatClientBuilder
zapewnia również przeciążenia Use, które ułatwiają pisanie takich procedur obsługi delegowania.
- Use(AnonymousDelegatingChatClient+CompleteSharedFunc)
- Use(Func<IChatClient,IChatClient>)
- Use(Func<IChatClient,IServiceProvider,IChatClient>)
- Use(Func<IList<ChatMessage>,ChatOptions,IChatClient,CancellationToken, Task<ChatCompletion>>, Func<IList<ChatMessage>,ChatOptions,IChatClient, CancellationToken,IAsyncEnumerable<StreamingChatCompletionUpdate>>)
Na przykład we wcześniejszym przykładzie RateLimitingChatClient
, przesłonięcia CompleteAsync
i CompleteStreamingAsync
muszą wykonać swoją pracę tylko przed i po delegowaniu do następnego klienta w linii przetwarzania. Aby osiągnąć to samo bez konieczności pisania klasy niestandardowej, można użyć przeciążenia Use
, które akceptuje delegat funkcji używany zarówno do CompleteAsync
, jak i CompleteStreamingAsync
, zmniejszając wymaganą ilość kodu powtarzalnego.
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
Powyższe przeciążenie wewnętrznie wykorzystuje AnonymousDelegatingChatClient
, co umożliwia bardziej skomplikowane wzorce przy użyciu tylko niewielkiego dodatkowego kodu. Aby na przykład osiągnąć ten sam wynik, ale z RateLimiter pobranym z DI:
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();
W scenariuszach, w których deweloper chce określić delegowanie implementacji CompleteAsync
i CompleteStreamingAsync
wbudowanych oraz gdzie ważne jest, aby móc napisać inną implementację dla każdego z nich w celu obsługi ich unikatowych typów zwracanych specjalnie, istnieje kolejne przeciążenie Use
, które akceptuje delegata dla każdego z nich.
Wstrzykiwanie zależności
IChatClient implementacje są zwykle udostępniane aplikacji za pośrednictwem wstrzykiwania zależności (DI). W tym przykładzie IDistributedCache jest dodawany do kontenera DI, podobnie jak IChatClient
. Rejestracja dla IChatClient
używa konstruktora, który buduje potok zawierający klienta buforującego (który następnie użyje IDistributedCache
pobranego z DI) oraz klienta przykładowego. Wstrzyknięte IChatClient
można pobrać i używać w innym miejscu w aplikacji.
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();
Powyższy przykład zależy od następujących pakietów NuGet:
Wprowadzone wystąpienie i konfiguracja mogą się różnić w zależności od bieżących potrzeb aplikacji, a wiele potoków można wstrzykiwać za pomocą różnych kluczy.
Interfejs IEmbeddingGenerator
Interfejs IEmbeddingGenerator<TInput,TEmbedding> reprezentuje ogólny generator osadzonych elementów. W tym miejscu TInput
jest typem osadzonych wartości wejściowych, a TEmbedding
jest typem wygenerowanego osadzania, który dziedziczy z klasy Embedding.
Klasa Embedding
służy jako klasa bazowa dla osadzeń generowanych przez IEmbeddingGenerator
. Jest ona przeznaczona do przechowywania metadanych i danych skojarzonych z osadzaniem i zarządzania nimi. Typy pochodne, takie jak Embedding<T>
zapewniają konkretne dane wektorów osadzania. Na przykład osadzanie uwidacznia właściwość Embedding<T>.Vector w celu uzyskania dostępu do danych osadzania.
Interfejs IEmbeddingGenerator
definiuje metodę do asynchronicznego generowania osadzeń dla kolekcji wartości wejściowych, z opcjonalną konfiguracją i obsługą anulowania. Udostępnia również metadane opisujące generator i umożliwia pobieranie silnie typiowanych usług, które mogą być udostępniane przez generator lub jego podstawowe usługi.
Przykładowa implementacja
Rozważmy następującą przykładową implementację IEmbeddingGenerator
, aby pokazać ogólną strukturę, ale generuje tylko losowe wektory osadzania.
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() { }
}
Powyższy kod:
- Definiuje klasę o nazwie
SampleEmbeddingGenerator
, która implementuje interfejsIEmbeddingGenerator<string, Embedding<float>>
. - Ma podstawowy konstruktor, który akceptuje punkt końcowy oraz identyfikator modelu, wykorzystywane do identyfikacji generatora.
- Uwidacznia właściwość
Metadata
, która udostępnia metadane generatora. - Implementuje metodę
GenerateAsync
do generowania osadzeń dla kolekcji wartości wejściowych:- Symuluje operację asynchroniczną przez opóźnienie dla 100 milisekund.
- Zwraca losowe osadzanie dla każdej wartości wejściowej.
Rzeczywiste konkretne implementacje można znaleźć w następujących pakietach:
Tworzenie osadzeń
Podstawowa operacja wykonywana przy użyciu IEmbeddingGenerator<TInput,TEmbedding> to generowanie osadzeń, które jest realizowane za pomocą metody 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()));
}
Niestandardowe oprogramowanie pośredniczące IEmbeddingGenerator
Podobnie jak w przypadku IChatClient
implementacje IEmbeddingGenerator
mogą być warstwowe. Podobnie jak Microsoft.Extensions.AI
zapewnia delegowanie implementacji IChatClient
na potrzeby buforowania i telemetrii, zapewnia również implementację 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
umożliwia tworzenie niestandardowego oprogramowania pośredniczącego, które rozszerza funkcjonalność IEmbeddingGenerator
. Klasa DelegatingEmbeddingGenerator<TInput,TEmbedding> to implementacja interfejsu IEmbeddingGenerator<TInput, TEmbedding>
, który służy jako klasa bazowa do tworzenia generatorów osadzania, które delegują swoje operacje do innego wystąpienia IEmbeddingGenerator<TInput, TEmbedding>
. Umożliwia łączenie wielu generatorów w dowolnej kolejności z przekazywaniem wywołań do bazowego generatora. Klasa udostępnia domyślne implementacje metod, takich jak GenerateAsync i Dispose
, które przekazują wywołania do wewnętrznego wystąpienia generatora, umożliwiając elastyczne i modułowe generowanie osadzonych elementów.
Poniżej znajduje się przykładowa implementacja generatora osadzeń delegującego, który ogranicza tempo żądań generowania osadzeń.
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);
}
}
Można to następnie warstwować wokół dowolnego IEmbeddingGenerator<string, Embedding<float>>
, aby ograniczyć liczbę wykonanych operacji generowania osadzania.
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()));
}
W ten sposób RateLimitingEmbeddingGenerator
mogą składać się z innych wystąpień IEmbeddingGenerator<string, Embedding<float>>
w celu zapewnienia funkcji ograniczania szybkości.
Zobacz też
- Tworzenie aplikacji platformy .NET przy użyciu funkcji sztucznej inteligencji
- ujednolicone bloki konstrukcyjne sztucznej inteligencji dla platformy .NET przy użyciu Microsoft.Extensions.AI
- Tworzenie aplikacji do czatu sztucznej inteligencji przy użyciu platformy .NET
- .NET Wstrzykiwanie Zależności
- Ograniczanie szybkości obsługi żądań HTTP na platformie .NET
- hosta ogólnego platformy .NET
- Pamięciowanie w .NET