Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
.NET-Entwickler müssen in ihre Apps eine wachsende Vielfalt von KI-Diensten (Künstliche Intelligenz) integrieren und damit interagieren. Die Microsoft.Extensions.AI
Bibliotheken bieten einen einheitlichen Ansatz für die Darstellung von generativen KI-Komponenten und ermöglichen eine nahtlose Integration und Interoperabilität mit verschiedenen KI-Diensten. In diesem Artikel werden die Bibliotheken vorgestellt und ausführliche Verwendungsbeispiele bereitgestellt, die Ihnen bei den ersten Schritten helfen.
Die Pakete
Das 📦 Microsoft.Extensions.AI.Abstractions-Paket stellt die Kernaustauschtypen bereit, einschließlich IChatClient und IEmbeddingGenerator<TInput,TEmbedding>. Jede .NET-Bibliothek, die einen LLM-Client bereitstellt, kann die IChatClient
Schnittstelle implementieren, um eine nahtlose Integration mit Verwendungscode zu ermöglichen.
Das 📦 Microsoft.Extensions.AI-Paket hat eine implizite Abhängigkeit vom Microsoft.Extensions.AI.Abstractions
Paket. Mit diesem Paket können Sie Komponenten wie automatische Funktionstoolaufrufe, Telemetrie und Zwischenspeicherung einfach in Ihre Anwendungen integrieren, indem Sie vertraute Abhängigkeitsinjektions- und Middlewaremuster verwenden. Beispielsweise stellt sie die UseOpenTelemetry(ChatClientBuilder, ILoggerFactory, String, Action<OpenTelemetryChatClient>) Erweiterungsmethode bereit, die der Chatclientpipeline OpenTelemetry-Unterstützung hinzufügt.
Auf welches Paket verwiesen werden soll
Bibliotheken, die Implementierungen der Abstraktionen bereitstellen, verweisen in der Regel nur auf Microsoft.Extensions.AI.Abstractions
.
Um auch Zugriff auf Hilfsprogramme auf höherer Ebene zur Arbeit mit generativen KI-Komponenten zu haben, verweisen Sie stattdessen auf das Microsoft.Extensions.AI
Paket (was selbst auf Microsoft.Extensions.AI.Abstractions
verweist). Die meisten verbrauchenden Anwendungen und Dienste sollten zusammen mit einer oder mehreren Bibliotheken, die konkrete Implementierungen der Abstraktionen bereitstellen, auf das Microsoft.Extensions.AI
Paket verweisen.
Installieren der Pakete
Informationen zum Installieren von NuGet-Paketen finden Sie unter dotnet package add oder Paketabhängigkeiten in .NET-Anwendungen verwalten.
API-Verwendungsbeispiele
Die folgenden Unterabschnitte zeigen spezifische IChatClient-Verwendungsbeispiele :
- Anfordern einer Chatantwort
- Anfordern einer Streaming-Chatantwort
- Tool-Aufrufe
- Zwischenspeichern von Antworten
- Telemetrie verwenden
- Bieten Sie Optionen
- Pipelines der Funktionalität
- Angepasste
IChatClient
-Middleware - Dependency Injection
- Zustandslose vs. zustandsbehaftete Clients
In den folgenden Abschnitten werden spezifische IEmbeddingGenerator-Verwendungsbeispiele gezeigt:
Die IChatClient
-Schnittstelle
Die IChatClient-Schnittstelle definiert eine Client-Abstraktion, die für die Interaktion mit KI-Diensten verantwortlich ist, die Chatfunktionen bereitstellen. Es enthält Methoden zum Senden und Empfangen von Nachrichten mit multimodalem Inhalt (wie z. B. Text, Bilder und Audio), entweder als kompletter Satz oder inkrementell gestreamt. Darüber hinaus ermöglicht es das Abrufen stark typisierter Dienste, die vom Client oder seinen zugrunde liegenden Diensten bereitgestellt werden.
.NET-Bibliotheken, die Clients für Sprachmodelle und -dienste bereitstellen, können eine Implementierung der IChatClient
Schnittstelle bereitstellen. Alle Verbraucher der Schnittstelle können dann nahtlos mit diesen Modellen und Diensten über die Abstraktionen zusammenarbeiten. Sie können eine einfache Implementierung bei Beispielimplementierungen von IChatClient und IEmbeddingGenerator sehen.
Anfordern einer Chatantwort
Mit einer Instanz von IChatClientkönnen Sie die IChatClient.GetResponseAsync Methode aufrufen, um eine Anforderung zu senden und eine Antwort zu erhalten. Die Anforderung besteht aus einer oder mehreren Nachrichten, die jeweils aus einem oder mehreren Inhalten bestehen. Es gibt Accelerator-Methoden, die allgemeine Fälle vereinfachen, wie z. B. das Erstellen einer Anforderung für ein einzelnes Stück Textinhalt.
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?"));
Die Kernmethode IChatClient.GetResponseAsync
akzeptiert eine Liste von Nachrichten. Diese Liste stellt den Verlauf aller Nachrichten dar, die Teil der Unterhaltung sind.
Console.WriteLine(await client.GetResponseAsync(
[
new(ChatRole.System, "You are a helpful AI assistant"),
new(ChatRole.User, "What is AI?"),
]));
Das zurückgegebene ChatResponse-Element von GetResponseAsync
stellt eine Liste der ChatMessage-Instanzen bereit, die eine oder mehrere im Rahmen des Vorgangs generierte Nachrichten repräsentieren. In häufigen Fällen gibt es nur eine Antwortnachricht, aber in einigen Situationen kann es mehrere Nachrichten geben. Die Nachrichtenliste wird sortiert, sodass die letzte Nachricht in der Liste die endgültige Nachricht für die Anforderung darstellt. Um alle diese Antwortnachrichten in einer nachfolgenden Anforderung an den Dienst zurückzugeben, können Sie die Nachrichten aus der Antwort zurück zur Nachrichtenliste hinzufügen.
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);
}
Anfordern einer Streaming-Chatantwort
Die Eingaben für IChatClient.GetStreamingResponseAsync sind mit denen von GetResponseAsync
identisch. Anstatt jedoch die vollständige Antwort als Element eines ChatResponse-Objekts zurückzugeben, gibt die Methode ein IAsyncEnumerable<T>-Objekt zurück, wobei T
ChatResponseUpdate ist und einen Strom von Aktualisierungen liefert, die zusammen die einzige Antwort bilden.
await foreach (ChatResponseUpdate update in client.GetStreamingResponseAsync("What is AI?"))
{
Console.Write(update);
}
Tipp
Streaming-APIs sind fast gleichbedeutend mit KI-Benutzererfahrungen. C# ermöglicht überzeugende Szenarien durch seine IAsyncEnumerable<T>
-Unterstützung und bietet eine natürliche und effiziente Methode zum Streamen von Daten.
Wie bei GetResponseAsync
können Sie die Updates aus IChatClient.GetStreamingResponseAsync zur Nachrichtenliste wieder hinzuzufügen. Da die Updates einzelne Teile einer Antwort sind, können Sie Hilfsmittel wie ToChatResponse(IEnumerable<ChatResponseUpdate>) verwenden, um ein oder mehrere Updates zu einer einzigen ChatResponse Instanz zusammenzufassen.
Hilfsprogramme wie AddMessages erstellen ein ChatResponse und extrahieren dann die komponierten Nachrichten aus der Antwort, um sie einer Liste hinzuzufügen.
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);
}
Toolaufruf
Einige Modelle und Dienste unterstützen Aufrufe von Tools. Um zusätzliche Informationen zu sammeln, können Sie ChatOptions so konfigurieren, dass es Informationen zu Tools enthält (in der Regel .NET-Methoden), die das Modell anfordern kann, damit der Client sie aufruft. Anstatt eine endgültige Antwort zu senden, fordert das Modell einen Funktionsaufruf mit bestimmten Argumenten an. Der Client ruft dann die Funktion auf und sendet die Ergebnisse zurück an das Modell mit dem Unterhaltungsverlauf. Die Microsoft.Extensions.AI.Abstractions
-Bibliothek enthält Abstraktionen für verschiedene Nachrichteninhaltstypen, einschließlich Funktionsaufrufanforderungen und -ergebnisse. IChatClient
Während Verbraucher direkt mit diesen Inhalten interagieren können, stellen Sie Hilfsprogramme bereit, Microsoft.Extensions.AI
die das automatische Aufrufen der Tools als Reaktion auf entsprechende Anforderungen aktivieren können. Die Microsoft.Extensions.AI.Abstractions
und Microsoft.Extensions.AI
Bibliotheken stellen die folgenden Typen bereit:
- AIFunction: Stellt eine Funktion dar, die in einem KI-Modell beschrieben und aufgerufen werden kann.
- AIFunctionFactory: Stellt Factorymethoden zum Erstellen von
AIFunction
Instanzen bereit, die .NET-Methoden darstellen. - FunctionInvokingChatClient: Verpackt ein
IChatClient
als ein weiteresIChatClient
, das automatische Funktionsaufruffähigkeiten hinzufügt.
Das folgende Beispiel zeigt einen zufälligen Funktionsaufruf (dieses Beispiel hängt vom OllamaSharp NuGet-Paket ab📦):
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);
}
Der vorherige Code:
- Definiert eine Funktion namens
GetCurrentWeather
, die eine zufällige Wettervorhersage zurückgibt. - Instanziiert eine ChatClientBuilder mit einer
OllamaSharp.OllamaApiClient
und konfiguriert sie für die Verwendung von Funktionsaufrufen. - Ruft
GetStreamingResponseAsync
auf dem Client auf und übergibt einen Prompt und eine Liste von Tools, die eine mit Create erstellte Funktion enthält. - Iteriert über die Antwort und gibt jede Aktualisierung auf der Konsole aus.
Antworten aus dem Cache
Wenn Sie mit Zwischenspeichern in .NET vertraut sind, ist es gut zu wissen, dass Microsoft.Extensions.AI andere solche delegierenden IChatClient
-Implementierungen bietet. Das DistributedCachingChatClient ist ein IChatClient
, das das Zwischenspeichern über eine andere beliebige IChatClient
- Instanz legt. Wenn ein neuer Chatverlauf an die DistributedCachingChatClient
gesendet wird, leitet sie ihn an den zugrunde liegenden Client weiter und speichert die Antwort im Zwischenspeicher, bevor sie an den Verbraucher zurückgeschickt wird. Wenn derselbe Verlauf das nächste Mal übermittelt wird, sodass eine zwischengespeicherte Antwort im Cache gefunden werden kann, gibt die DistributedCachingChatClient
zwischengespeicherte Antwort zurück, anstatt die Anforderung entlang der Pipeline weiterzuleiten.
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();
}
Dieses Beispiel hängt vom Microsoft.Extensions.Caching.Memory NuGet-Paket ab📦. Weitere Informationen finden Sie unter Zwischenspeichern in .NET.
Verwenden der Telemetrie
Ein weiteres Beispiel für einen delegierenden Chat-Client ist die OpenTelemetryChatClient. Diese Implementierung entspricht den OpenTelemetry-Semantikkonventionen für generative KI-Systeme. Ähnlich wie andere IChatClient
-Delegatoren schichtet es Metriken und erstreckt sich um andere beliebige IChatClient
Implementierungen.
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);
(Das vorangehende Beispiel hängt vom 📦 NuGet-Paket "OpenTelemetry.Exporter.Console ".)
Alternativ bietet die LoggingChatClient und die entsprechende UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) Methode eine einfache Möglichkeit zum Schreiben von Protokolleinträgen in eine ILogger für jede Anforderung und Antwort.
Bieten Sie Optionen
Jeder Aufruf von GetResponseAsync oder GetStreamingResponseAsync kann optional eine ChatOptions-Instanz liefern, die zusätzliche Parameter für den Vorgang enthält. Die gängigsten Parameter bei KI-Modellen und Diensten werden als stark typisierte Eigenschaften des Typs angezeigt, wie z. B. ChatOptions.Temperature. Andere Parameter können über das Wörterbuch ChatOptions.AdditionalProperties in schwach typisierter Form namentlich angegeben werden oder über die Eigenschaft ChatOptions.RawRepresentationFactory über eine Optionsinstanz, die der zugrunde liegende Anbieter versteht.
Sie können auch Optionen angeben, wenn Sie ein IChatClient
mit der fluent ChatClientBuilder API erstellen oder einen Aufruf der ConfigureOptions(ChatClientBuilder, Action<ChatOptions>)-Erweiterungsmethode verketten. Dieser delegierende Client umhüllt einen anderen Client und ruft den bereitgestellten Delegaten auf, um bei jedem Aufruf eine ChatOptions
-Instanz zu befüllen. Um beispielsweise sicherzustellen, dass die Eigenschaft ChatOptions.ModelId standardmäßig einen bestimmten Modellnamen enthält, können Sie den folgenden Code verwenden:
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" }));
Funktionalitäts-Pipelines
IChatClient
Instanzen können überschichtet werden, um eine Pipeline von Komponenten zu erstellen, die jeweils zusätzliche Funktionen hinzufügen. Diese Komponenten können aus Microsoft.Extensions.AI
, anderen NuGet-Paketen oder benutzerdefinierten Implementierungen stammen. Mit diesem Ansatz können Sie das Verhalten der IChatClient
auf verschiedene Weise erweitern, um Ihre spezifischen Anforderungen zu erfüllen. Betrachten Sie den folgenden Codeausschnitt, der einen verteilten Cache, einen Funktionsaufruf und die OpenTelemetry-Ablaufverfolgung um einen Beispiel-Chatclient überlagert.
// 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();
Angepasste IChatClient
-Middleware
Um zusätzliche Funktionen hinzuzufügen, können Sie IChatClient
direkt implementieren oder die DelegatingChatClient Klasse verwenden. Diese Klasse dient als Basis zum Erstellen von Chatclients, die Vorgänge an eine andere IChatClient
-Instanz delegieren. Es vereinfacht das Verketten mehrerer Clients, sodass Anrufe an einen zugrunde liegenden Client übergeben werden können.
Die DelegatingChatClient
-Klasse stellt Standardimplementierungen für Methoden wie GetResponseAsync
, GetStreamingResponseAsync
und Dispose
bereit, die Aufrufe an den inneren Client weiterleiten. Eine abgeleitete Klasse kann dann nur die Methoden außer Kraft setzen, die zum Erweitern des Verhaltens erforderlich sind, während andere Aufrufe an die Basisimplementierung delegiert werden. Dieser Ansatz ist nützlich, um flexible und modulare Chatclients zu erstellen, die einfach zu erweitern und zu verfassen sind.
Im Folgenden finden Sie eine Beispielklasse, die von DelegatingChatClient
abgeleitet ist und die System.Threading.RateLimiting-Bibliothek nutzt, um rate-limiting-Funktionalität bereitzustellen.
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);
}
}
Wie bei anderen IChatClient
Implementierungen kann folgendes RateLimitingChatClient
erstellt werden:
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?"));
Um die Komposition solcher Komponenten mit anderen zu vereinfachen, sollten die Autoren der Komponente eine Use*
-Erweiterungsmethode erstellen, um die Komponente in einer Pipeline zu registrieren. Betrachten Sie beispielsweise die folgende UseRatingLimiting
Erweiterungsmethode:
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)
);
}
Solche Erweiterungen können auch relevante Dienste aus dem DI-Container abfragen; das von der Pipeline verwendete IServiceProvider wird als optionaler Parameter übergeben:
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>())
);
}
Jetzt ist es für den Verbraucher einfach, dies in ihrer Pipeline zu verwenden, z. B.:
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));
Die vorherigen Erweiterungsmethoden veranschaulichen die Verwendung einer Use
Methode für ChatClientBuilder. Sie ChatClientBuilder
bietet auch Use-Überladungen, die es einfacher machen, solche delegierenden Handler zu schreiben. Im früheren Beispiel RateLimitingChatClient
müssen die Überschreibungen von GetResponseAsync
und GetStreamingResponseAsync
nur vor und nach der Delegierung an den nächsten Client in der Pipeline ausgeführt werden. Um dasselbe zu erreichen, ohne eine angepasste Klasse schreiben zu müssen, können Sie eine Überladung von Use
verwenden, die einen Delegaten akzeptiert, der sowohl für GetResponseAsync
als auch für GetStreamingResponseAsync
verwendet wird, wodurch sich die erforderliche Boilerplate reduziert:
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();
Für Szenarien, in denen Sie für GetResponseAsync
und GetStreamingResponseAsync
eine jeweils andere Implementierung benötigen, um deren einzigartige Rückgabetypen zu handhaben, können Sie die Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken,
Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions,
IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>)-Überladung verwenden, die einen Delegaten für jeden akzeptiert.
Abhängigkeitsinjektion
IChatClient Implementierungen werden häufig über Abhängigkeitsinjektion (DI) für eine Anwendung bereitgestellt. In diesem Beispiel wird ein IDistributedCache in den DI-Container eingefügt, ebenso wie ein IChatClient
. Bei der Registrierung für IChatClient
wird ein Builder verwendet, der eine Pipeline erstellt, die einen Caching-Client (der dann einen IDistributedCache
verwendet, der von DI abgerufen wird) und den Beispiel-Client enthält. Das injizierte IChatClient
kann an anderer Stelle in der App abgerufen und verwendet werden.
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?"));
Welche Instanz und Konfiguration eingefügt wird, kann je nach den aktuellen Anforderungen der Anwendung unterschiedlich sein, und mehrere Pipelines können mit unterschiedlichen Schlüsseln eingefügt werden.
Zustandslose vs. zustandsbehaftete Clients
Zustandslose Dienste erfordern, dass der gesamte relevante Konversationsverlauf bei jeder Anfrage übermittelt wird. Im Gegensatz dazu verfolgen zustandsbehaftete Dienste den Verlauf und benötigen lediglich zusätzliche Nachrichten, die zusammen mit einer Anforderung gesendet werden. Die IChatClient Schnittstelle wurde entwickelt, um sowohl zustandslose als auch zustandsbehaftete KI-Dienste zu verarbeiten.
Beim Arbeiten mit einem zustandslosen Dienst verwalten Anrufer eine Liste aller Nachrichten. Sie fügen alle empfangenen Antwortnachrichten hinzu und stellen die Liste bei nachfolgenden Interaktionen wieder zur Verfügung.
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);
}
Für zustandsbehaftete Dienste kennen Sie möglicherweise bereits die Kennung, die für die relevante Konversation verwendet wird. Sie können diesen Bezeichner in ChatOptions.ConversationId einfügen. Die Verwendung folgt dann demselben Muster, es sei denn, es ist nicht erforderlich, einen Verlauf manuell zu verwalten.
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));
}
Einige Dienste unterstützen möglicherweise das automatische Erstellen einer Unterhaltungs-ID für eine Anforderung, die keine besitzt, oder das Erstellen einer neuen Unterhaltungs-ID, die den aktuellen Zustand der Unterhaltung nach der Integration der letzten Nachrichtenrunde repräsentiert. In solchen Fällen können Sie die ChatResponse.ConversationId für nachfolgende Anfragen in die ChatOptions.ConversationId
übertragen. Beispiel:
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;
}
Wenn Sie vorab nicht wissen, ob der Dienst zustandslos oder zustandsbehaftet ist, können Sie die Antwort ConversationId überprüfen und auf Grundlage ihres Wertes handeln. Wenn sie festgelegt ist, wird dieser Wert an die Optionen weitergegeben, und der Verlauf wird gelöscht, damit derselbe Verlauf nicht erneut zurückgegeben wird. Wenn die Antwort ConversationId
nicht festgelegt ist, wird die Antwortnachricht dem Verlauf hinzugefügt, sodass sie beim nächsten Schritt an den Dienst zurückgesendet wird.
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);
}
}
Die IEmbeddingGenerator
-Schnittstelle
Die Schnittstelle IEmbeddingGenerator<TInput,TEmbedding> stellt einen generischen Generator für Einbettungen dar. Bei den generischen Typparametern TInput
handelt es sich um den Typ der eingebetteten Eingabewerte und TEmbedding
ist der Typ der generierten Einbettung, die von der Embedding Klasse erbt.
Die Klasse Embedding
dient als Basisklasse für Einbettungen, die von einer IEmbeddingGenerator
generiert werden. Es wurde entwickelt, um die Metadaten und Daten zu speichern und zu verwalten, die mit Einbettungen verknüpft sind. Abgeleitete Typen, wie Embedding<T>, stellen die konkreten Einbettungsvektordaten bereit. Beispielsweise stellt ein Embedding<float>
eine ReadOnlyMemory<float> Vector { get; }
-Eigenschaft bereit, um auf die eingebetteten Daten zuzugreifen.
Die Schnittstelle IEmbeddingGenerator
definiert eine Methode zur asynchronen Generierung von Einbettungen für eine Sammlung von Eingabewerten, mit optionaler Unterstützung für Konfiguration und Abbruch. Außerdem werden Metadaten bereitgestellt, die den Generator beschreiben, und es ermöglicht den Abruf von stark typisierten Diensten, die entweder vom Generator selbst oder von seinen zugrunde liegenden Diensten bereitgestellt werden können.
Die meisten Benutzer müssen die IEmbeddingGenerator
Schnittstelle nicht implementieren. Wenn Sie jedoch ein Bibliotheksautor sind, können Sie eine einfache Implementierung bei Beispielimplementierungen von IChatClient und IEmbeddingGenerator sehen.
Erstellen von Einbettungen
Der primäre Vorgang, der mit IEmbeddingGenerator<TInput,TEmbedding> ausgeführt wird, ist die Generierung von Einbettungen, die mit der Methode GenerateAsync durchgeführt wird.
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()));
}
Es gibt auch Methoden zur Beschleunigungserweiterung, um häufige Fälle zu vereinfachen, z. B. das Generieren eines Einbettungsvektors aus einer einzigen Eingabe.
ReadOnlyMemory<float> vector = await generator.GenerateVectorAsync("What is AI?");
Funktionspipelines
Wie bei IChatClient
können IEmbeddingGenerator
-Implementierungen gestapelt werden. Microsoft.Extensions.AI
bietet eine delegierende Implementierung für IEmbeddingGenerator
, die zur Zwischenspeicherung und Telemetrie dient.
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()));
}
Die IEmbeddingGenerator
ermöglicht die Erstellung angepasster Middleware, die die Funktionalität einer IEmbeddingGenerator
erweitert. Die DelegatingEmbeddingGenerator<TInput,TEmbedding>-Klasse ist eine Implementierung der IEmbeddingGenerator<TInput, TEmbedding>
-Schnittstelle, die als Basisklasse zum Erstellen von Einbettungsgeneratoren dient, die ihre Vorgänge an eine andere IEmbeddingGenerator<TInput, TEmbedding>
-Instanz delegieren. Sie ermöglicht das Verketten mehrerer Generatoren in beliebiger Reihenfolge, indem Aufrufe an einen zugrunde liegenden Generator übergeben werden. Die Klasse stellt Standardimplementierungen für Methoden wie GenerateAsync und Dispose
bereit, die die Aufrufe an die innere Generatorinstanz weiterleiten und dadurch eine flexible und modulare Erstellung von Einbettungen ermöglichen.
Nachfolgend sehen Sie eine Beispielimplementierung eines delegierenden Einbettungsgenerators, der die Häufigkeit von Einbettungsgenerierungsanforderungen begrenzt.
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);
}
}
Dieser kann dann um einen beliebigen IEmbeddingGenerator<string, Embedding<float>>
herum geschichtet werden, um die Rate aller Einbettungsgenerierungsvorgänge zu begrenzen.
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()));
}
Auf diese Weise kann die RateLimitingEmbeddingGenerator
-Instanz mit anderen IEmbeddingGenerator<string, Embedding<float>>
-Instanzen kombiniert werden, um Bewertungsbegrenzungsfunktionen bereitzustellen.
Bauen mit Microsoft.Extensions.AI
Auf Microsoft.Extensions.AI
können Sie auf folgende Weise mit dem Aufbau beginnen:
- Bibliotheksentwickler: Wenn Sie Bibliotheken besitzen, die Clients für KI-Dienste bereitstellen, sollten Sie die Schnittstellen in Ihren Bibliotheken implementieren. Auf diese Weise können Benutzer Ihr NuGet-Paket einfach über die Abstraktionen integrieren. Beispielimplementierungen finden Sie unter Beispielimplementierungen von IChatClient und IEmbeddingGenerator.
- Servicekonsumenten: Wenn Sie Bibliotheken entwickeln, die KI-Dienste nutzen, verwenden Sie die Abstraktionen anstelle von Hardcoding für einen bestimmten KI-Dienst. Dieser Ansatz bietet Ihren Verbrauchern die Flexibilität, ihren bevorzugten Anbieter auszuwählen.
- Anwendungsentwickler: Verwenden Sie die Abstraktionen, um die Integration in Ihre Apps zu vereinfachen. Dies ermöglicht die Portabilität über Modelle und Dienste hinweg, erleichtert das Testen und Mocken, nutzt Middleware, die vom Ökosystem bereitgestellt wird, und behält eine konsistente API in Ihrer gesamten App bei, selbst wenn Sie verschiedene Dienste in unterschiedlichen Teilen Ihrer Anwendung verwenden.
- Ökosystemmitwirkende: Wenn Sie daran interessiert sind, zum Ökosystem beizutragen, sollten Sie benutzerdefinierte Middleware-Komponenten schreiben.
Weitere Beispiele finden Sie im GitHub-Repository "dotnet/ai-samples ". Ein End-to-End-Beispiel finden Sie unter "eShopSupport".