Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Les développeurs .NET doivent intégrer et interagir avec un large éventail de services d’intelligence artificielle (IA) dans leurs applications. Les Microsoft.Extensions.AI
bibliothèques fournissent une approche unifiée pour représenter les composants d’INTELLIGENCE artificielle générative et permettent l’intégration et l’interopérabilité transparentes avec différents services IA. Cet article présente les bibliothèques et fournit des exemples d’utilisation détaillés pour vous aider à commencer.
Les packages
Le 📦 package Microsoft.Extensions.AI.Abstractions fournit les types d’échange principaux, y compris IChatClient et IEmbeddingGenerator<TInput,TEmbedding>. Toute bibliothèque .NET qui fournit un client LLM peut implémenter l’interface IChatClient
pour permettre une intégration transparente avec le code consommant.
Le 📦 package Microsoft.Extensions.AI a une dépendance implicite sur le Microsoft.Extensions.AI.Abstractions
package. Ce package vous permet d’intégrer facilement des composants tels que l’appel automatique de l’outil de fonction, la télémétrie et la mise en cache dans vos applications à l’aide de modèles familiers d’injection de dépendances et d’intergiciels. Par exemple, cela fournit la méthode d’extension UseOpenTelemetry(ChatClientBuilder, ILoggerFactory, String, Action<OpenTelemetryChatClient>), qui ajoute la prise en charge d’OpenTelemetry au pipeline du client de discussion.
Quel package référencer
Bibliothèques qui fournissent des implémentations des abstractions référencent généralement uniquement Microsoft.Extensions.AI.Abstractions
.
Pour avoir également accès à des outils de niveau supérieur pour utiliser des composants d'intelligence artificielle générative, référez-vous plutôt au package Microsoft.Extensions.AI
(qui se réfère lui-même à Microsoft.Extensions.AI.Abstractions
). La plupart des applications et services consommants doivent référencer le Microsoft.Extensions.AI
package avec une ou plusieurs bibliothèques qui fournissent des implémentations concrètes des abstractions.
Installer les packages
Pour plus d’informations sur l’installation des packages NuGet, consultez dotnet package add ou Manage package dependencies in .NET applications.
Exemples d’utilisation d’API
Les sous-sections suivantes présentent des exemples d’utilisation IChatClient spécifiques :
- Demander une réponse de conversation
- Demander une réponse de conversation en streaming
- Appel de l’outil
- Mettre en cache les réponses
- Utiliser la télémétrie
- Proposer des options
- Pipelines de fonctionnalités
- Middleware
IChatClient
personnalisé - Injection de dépendances
- Clients sans état et avec état
Les sections suivantes présentent des exemples d’utilisation IEmbeddingGenerator spécifiques :
L’interface IChatClient
L’interface IChatClient définit une abstraction cliente chargée d’interagir avec les services IA qui fournissent des fonctionnalités de conversation. Elle inclut des méthodes d’envoi et de réception de messages avec du contenu multimodal (par exemple, du texte, des images et de l’audio), en tant qu’ensemble complet ou diffusé de manière incrémentielle. En outre, il permet de récupérer des services fortement typés fournis par le client ou ses services sous-jacents.
Les bibliothèques .NET qui fournissent des clients pour les modèles de langage et les services peuvent fournir une implémentation de l’interface IChatClient
. Tous les consommateurs de l’interface sont alors en mesure d’interagir en toute transparence avec ces modèles et services via les abstractions. Vous pouvez voir une implémentation simple dans les exemples d’implémentations iChatClient et IEmbeddingGenerator.
Demander une réponse de conversation
Avec une instance de IChatClient, vous pouvez appeler la IChatClient.GetResponseAsync méthode pour envoyer une demande et obtenir une réponse. La requête est composée d’un ou plusieurs messages, chacun composé d’une ou plusieurs parties de contenu. Des méthodes d’accélérateur existent pour simplifier les cas courants, tels que la construction d’une requête pour un seul élément de contenu texte.
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?"));
La méthode principale IChatClient.GetResponseAsync
accepte une liste de messages. Cette liste représente l’historique de tous les messages qui font partie de la conversation.
Console.WriteLine(await client.GetResponseAsync(
[
new(ChatRole.System, "You are a helpful AI assistant"),
new(ChatRole.User, "What is AI?"),
]));
L'objet ChatResponse retourné depuis GetResponseAsync
fournit une liste d'instances de ChatMessage qui représentent un ou plusieurs messages générés. Dans les cas courants, il n’existe qu’un seul message de réponse, mais dans certaines situations, il peut y avoir plusieurs messages. La liste des messages est triée, de sorte que le dernier message de la liste représente le message final à la demande. Pour fournir tous ces messages de réponse au service dans une demande ultérieure, vous pouvez ajouter les messages de la réponse dans la liste des messages.
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);
}
Demander une réponse de conversation en streaming
Les entrées dans IChatClient.GetStreamingResponseAsync sont identiques à celles de GetResponseAsync
. Toutefois, au lieu de renvoyer la réponse complète dans le cadre d’un objet ChatResponse, la méthode retourne un IAsyncEnumerable<T> où T
est ChatResponseUpdate, fournissant ainsi un flux de mises à jour qui forment collectivement la réponse unique.
await foreach (ChatResponseUpdate update in client.GetStreamingResponseAsync("What is AI?"))
{
Console.Write(update);
}
Conseil / Astuce
Les API de streaming sont presque synonymes d’expériences utilisateur IA. C# permet des scénarios convaincants avec sa prise en charge de IAsyncEnumerable<T>
, offrant un moyen naturel et efficace de diffuser des données de flux.
Comme avec GetResponseAsync
, vous pouvez ajouter à nouveau les mises à jour de IChatClient.GetStreamingResponseAsync dans la liste des messages. Étant donné que les mises à jour sont des éléments individuels d’une réponse, vous pouvez utiliser des helpers comme ToChatResponse(IEnumerable<ChatResponseUpdate>) pour composer une ou plusieurs mises à jour dans une seule ChatResponse instance.
Les assistants comme AddMessages composent un ChatResponse, puis extraient les messages composés de la réponse et les ajoutent à une liste.
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);
}
Appel de l’outil
Certains modèles et services prennent en charge l’appel d’outils. Pour collecter des informations supplémentaires, vous pouvez configurer le ChatOptions avec des informations sur les outils (généralement des méthodes .NET) que le modèle peut demander au client d'invoquer. Au lieu d’envoyer une réponse finale, le modèle demande un appel de fonction avec des arguments spécifiques. Le client appelle ensuite la fonction et renvoie les résultats au modèle avec l’historique des conversations. La bibliothèque Microsoft.Extensions.AI.Abstractions
inclut des abstractions pour différents types de contenu de message, notamment les demandes d’appel de fonction et les résultats. Bien que IChatClient
les consommateurs puissent interagir directement avec ce contenu, Microsoft.Extensions.AI
fournit des assistances qui peuvent activer l’appel automatique des outils en réponse aux demandes correspondantes. Les bibliothèques Microsoft.Extensions.AI.Abstractions
et Microsoft.Extensions.AI
offrent les types suivants :
- AIFunction: représente une fonction qui peut être décrite dans un modèle IA et appelée.
- AIFunctionFactory: fournit des méthodes de fabrique pour la création
AIFunction
d’instances qui représentent des méthodes .NET. - FunctionInvokingChatClient : enveloppe un
IChatClient
en tant queIChatClient
supplémentaire qui ajoute des capacités d'invocation automatique de fonction.
L’exemple suivant illustre un appel de fonction aléatoire (cet exemple dépend du 📦 package NuGet OllamaSharp ) :
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);
}
Code précédent :
- Définit une fonction nommée
GetCurrentWeather
qui retourne une prévision météorologique aléatoire. - Instancie un ChatClientBuilder avec un
OllamaSharp.OllamaApiClient
et le configure pour utiliser l'invocation de fonction. - Appelle
GetStreamingResponseAsync
sur le client, en transmettant une invite et une liste d’outils qui incluent une fonction créée avec Create. - Effectue une itération sur la réponse, en imprimant chaque mise à jour sur la console.
Mettre en cache des réponses
Si vous connaissez Mise en cache dans .NET, il est judicieux de savoir que Microsoft.Extensions.AI fournit d’autres implémentations IChatClient
de délégation. Le DistributedCachingChatClient est un IChatClient
qui applique une mise en cache par couches autour d'une autre instance IChatClient
arbitraire. Lorsqu'un nouvel historique de conversation est soumis au DistributedCachingChatClient
, il le transfère au client sous-jacent, puis met en cache la réponse avant de la renvoyer au consommateur. La prochaine fois que le même historique est envoyé, de sorte qu’une réponse mise en cache se trouve dans le cache, elle DistributedCachingChatClient
renvoie la réponse mise en cache plutôt que de transférer la requête le long du pipeline.
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();
}
Cet exemple dépend du 📦 package NuGet Microsoft.Extensions.Caching.Memory . Pour plus d’informations, consultez Mise en cache dans .NET.
Utiliser la télémétrie
Un autre exemple d’un client de conversation délégué est le OpenTelemetryChatClient. Cette implémentation respecte les conventions sémantiques openTelemetry pour les systèmes d’IA générative. Comme pour d'autres délégateurs IChatClient
, il superpose les mesures et englobe d'autres implémentations arbitraires 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);
(L’exemple précédent dépend du 📦 package NuGet OpenTelemetry.Exporter.Console .)
Alternativement, la méthode LoggingChatClient et la méthode correspondante UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) offrent un moyen simple d'écrire des entrées de journal dans un ILogger pour chaque requête et réponse.
Proposer des options
Chaque appel à GetResponseAsync ou GetStreamingResponseAsync peut éventuellement fournir une instance de ChatOptions contenant des paramètres supplémentaires pour l’opération. Les paramètres les plus courants parmi les modèles et services d’IA s’affichent comme des propriétés fortement typées sur le type, telles que ChatOptions.Temperature. D’autres paramètres peuvent être fournis par nom de manière faiblement typée, via le ChatOptions.AdditionalProperties dictionnaire ou via une instance d’options que le fournisseur sous-jacent comprend, via la ChatOptions.RawRepresentationFactory propriété.
Vous pouvez également spécifier des options lors de la construction d'une IChatClient
avec l'API fluente ChatClientBuilder en chaînant un appel à la méthode d'extension ConfigureOptions(ChatClientBuilder, Action<ChatOptions>). Ce client délégateur enveloppe un autre client et appelle le délégué fourni pour remplir une instance ChatOptions
pour chaque appel. Par exemple, pour vous assurer que la propriété ChatOptions.ModelId est par défaut un nom de modèle particulier, vous pouvez utiliser du code comme suit :
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" }));
Pipelines de fonctionnalités
IChatClient
les instances peuvent être superposées pour créer un pipeline de composants qui ajoutent chacune des fonctionnalités supplémentaires. Ces composants peuvent provenir de Microsoft.Extensions.AI
, d’autres packages NuGet ou d’implémentations personnalisées. Cette approche vous permet d’augmenter le comportement du IChatClient
de différentes façons pour répondre à vos besoins spécifiques. Considérez l’extrait de code suivant qui couche un cache distribué, un appel de fonction et un suivi OpenTelemetry autour d’un exemple de client de conversation :
// 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();
Middleware IChatClient
personnalisé
Pour ajouter des fonctionnalités supplémentaires, vous pouvez implémenter IChatClient
directement ou utiliser la classe DelegatingChatClient. Cette classe sert de base pour créer des clients de conversation qui délèguent des opérations à une autre instance IChatClient
. Il simplifie le chaînage de plusieurs clients, ce qui permet aux appels de passer par un client sous-jacent.
La classe DelegatingChatClient
fournit des implémentations par défaut pour les méthodes telles que GetResponseAsync
, GetStreamingResponseAsync
et Dispose
, qui transfèrent les appels au client interne. Une classe dérivée peut ensuite remplacer uniquement les méthodes dont elle a besoin pour augmenter le comportement, tout en déléguant d’autres appels à l’implémentation de base. Cette approche est utile pour créer des clients de conversation flexibles et modulaires faciles à étendre et à composer.
Voici un exemple de classe dérivée de DelegatingChatClient
laquelle utilise la bibliothèque System.Threading.RateLimiting pour fournir des fonctionnalités de limitation de débit.
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);
}
}
Comme avec d'autres IChatClient
implémentations, RateLimitingChatClient
peut être composé :
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?"));
Pour simplifier l’association de ces composants avec d’autres, les auteurs de composants doivent créer une méthode d’extension Use*
pour inscrire le composant dans un pipeline. Par exemple, considérez la méthode d’extension suivante 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)
);
}
Ces extensions peuvent également demander les services pertinents au conteneur DI ; le IServiceProvider utilisé par le pipeline est transmis en tant que paramètre facultatif :
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>())
);
}
Maintenant, il est facile pour le consommateur d’utiliser ceci dans son pipeline, par exemple :
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));
Les méthodes d’extension précédentes illustrent l’utilisation d’une Use
méthode sur ChatClientBuilder. ChatClientBuilder
fournit également des surcharges Use qui facilitent l’écriture de tels gestionnaires de délégation. Par exemple, dans l’exemple précédent de RateLimitingChatClient
, les remplacements de GetResponseAsync
et GetStreamingResponseAsync
doivent uniquement effectuer des tâches avant et après la délégation au client suivant dans le pipeline. Pour obtenir le même résultat sans écrire une classe personnalisée, vous pouvez utiliser une surcharge de Use
qui accepte un délégué utilisé à la fois pour GetResponseAsync
et GetStreamingResponseAsync
, ce qui réduit les lignes de code nécessaires :
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();
Pour les scénarios où vous avez besoin d’une implémentation différente pour GetResponseAsync
et GetStreamingResponseAsync
pour gérer leurs types de retour uniques, vous pouvez utiliser la Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken,
Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions,
IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>) surcharge qui accepte un délégué pour chacun d’eux.
Injection de dépendances
IChatClientLes implémentations sont souvent fournies à une application via l’injection de dépendances (DI). Dans cet exemple, un IDistributedCache est ajouté au conteneur DI, ainsi qu'un IChatClient
. L’inscription du IChatClient
utilise un générateur qui crée un pipeline contenant un client de mise en cache (qui utilise ensuite un IDistributedCache
récupéré à partir de la DI) et le client d'exemple. Le IChatClient
injecté peut être récupéré et utilisé ailleurs dans l’application.
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?"));
L'instance et la configuration injectées peuvent différer en fonction des besoins actuels de l'application, et plusieurs pipelines peuvent être injectés avec des clés différentes.
Clients sans état et avec état
Les services sans état nécessitent que tous les historiques de conversation pertinents soient envoyés à nouveau à chaque requête. En revanche, les services avec état effectuent le suivi de l’historique et requièrent uniquement que des messages supplémentaires soient envoyés avec une demande. L'interface IChatClient est conçue pour gérer les services d'IA, qu'ils soient sans état ou avec état.
Lors de l’utilisation d’un service sans état, les appelants gèrent une liste de tous les messages. Ils ajoutent tous les messages de réponse reçus et fournissent la liste à nouveau sur les interactions suivantes.
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);
}
Pour les services avec état, vous pouvez déjà connaître l’identificateur utilisé pour la conversation concernée. Vous pouvez placer cet identificateur dans ChatOptions.ConversationId. L’utilisation suit ensuite le même modèle, sauf qu’il n’est pas nécessaire de conserver manuellement un historique.
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));
}
Certains services peuvent prendre en charge la création automatique d’un ID de conversation pour une demande qui n’en a pas, ou la création d’un ID de conversation qui représente l’état actuel de la conversation après avoir incorporé le dernier cycle de messages. Dans ce cas, vous pouvez transférer le ChatResponse.ConversationId au ChatOptions.ConversationId
pour les demandes suivantes. Par exemple:
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;
}
Si vous ne savez pas à l’avance si le service est sans état ou avec état, vous pouvez vérifier la réponse ConversationId et agir en fonction de sa valeur. Si elle est définie, cette valeur est propagée aux options et l’historique est effacé afin de ne pas renvoyer à nouveau le même historique. Si la réponse ConversationId
n’est pas définie, le message de réponse est ajouté à l’historique afin qu’il soit renvoyé au service au tour suivant.
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);
}
}
L’interface IEmbeddingGenerator
L’interface IEmbeddingGenerator<TInput,TEmbedding> représente un générateur générique d’incorporations. Pour les paramètres de type générique, TInput
est le type de valeurs d’entrée incorporées et TEmbedding
est le type d’incorporation généré, qui hérite de la Embedding classe.
La classe Embedding
sert de classe de base pour les incorporations générées par un IEmbeddingGenerator
. Il est conçu pour stocker et gérer les métadonnées et les données associées aux incorporations. Les types dérivés, tels que Embedding<T>, fournissent les données vectorielles d’incorporation concrètes. Par exemple, un Embedding<float>
expose une propriété ReadOnlyMemory<float> Vector { get; }
permettant d'accéder à ses données intégrées.
L’interface IEmbeddingGenerator
définit une méthode pour générer de manière asynchrone des incorporations pour une collection de valeurs d’entrée, avec prise en charge facultative de la configuration et de l’annulation. Il fournit également des métadonnées décrivant le générateur et permet la récupération de services fortement typés qui peuvent être fournis par le générateur ou ses services sous-jacents.
La plupart des utilisateurs n’ont pas besoin d’implémenter l’interface IEmbeddingGenerator
. Toutefois, si vous êtes un auteur de bibliothèque, vous pouvez voir une implémentation simple dans les exemples d’implémentations d’IChatClient et IEmbeddingGenerator.
Créer des embeddings
L’opération principale effectuée avec une IEmbeddingGenerator<TInput,TEmbedding> est la génération d'embeddings, qui est réalisée grâce à sa méthode 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()));
}
Les méthodes d’extension d'accélérateur existent également pour simplifier les cas courants, tels que la génération d’un vecteur d’incorporation à partir d’une seule entrée.
ReadOnlyMemory<float> vector = await generator.GenerateVectorAsync("What is AI?");
Pipelines de fonctionnalités
Comme avec le IChatClient
, les implémentations du IEmbeddingGenerator
peuvent être superposées. Microsoft.Extensions.AI
fournit une implémentation de délégation pour IEmbeddingGenerator
la mise en cache et la télémétrie.
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()));
}
Le IEmbeddingGenerator
permet de créer un middleware personnalisé qui étend les fonctionnalités d’un IEmbeddingGenerator
. La classe DelegatingEmbeddingGenerator<TInput,TEmbedding> est une implémentation de l’interface IEmbeddingGenerator<TInput, TEmbedding>
qui sert de classe de base pour créer des générateurs d’incorporation qui délèguent leurs opérations à une autre instance IEmbeddingGenerator<TInput, TEmbedding>
. Elle permet de chaîner plusieurs générateurs dans n’importe quel ordre, en passant des appels à un générateur sous-jacent. La classe fournit des implémentations par défaut pour les méthodes telles que GenerateAsync et Dispose
, qui transfèrent les appels à l’instance de générateur interne, ce qui permet une génération d’incorporation flexible et modulaire.
Voici un exemple d’implémentation d’un tel générateur d’incorporation par délégation qui limite les demandes de génération d’incorporation :
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);
}
}
On peut ensuite le superposer à un IEmbeddingGenerator<string, Embedding<float>>
arbitraire pour limiter le débit de toutes les opérations de génération d'incorporations.
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()));
}
De cette façon, le RateLimitingEmbeddingGenerator
peut être composé avec d'autres instances IEmbeddingGenerator<string, Embedding<float>>
pour offrir une fonctionnalité de limitation de débit.
Générer avec Microsoft.Extensions.AI
Vous pouvez commencer à créer avec Microsoft.Extensions.AI
en suivant les méthodes suivantes :
- Développeurs de bibliothèques : si vous possédez des bibliothèques qui fournissent des clients pour les services IA, envisagez d’implémenter les interfaces dans vos bibliothèques. Cela permet aux utilisateurs d’intégrer facilement votre package NuGet via les abstractions. Pour obtenir des exemples d’implémentations, consultez Exemples d’implémentations de IChatClient et IEmbeddingGenerator.
- Consommateurs de services : si vous développez des bibliothèques qui consomment des services IA, utilisez les abstractions au lieu de coder en dur vers un service IA spécifique. Cette approche offre à vos consommateurs la possibilité de choisir leur fournisseur préféré.
- Développeurs d’applications : utilisez les abstractions pour simplifier l’intégration dans vos applications. Cela permet la portabilité entre les modèles et services, facilite le test et la simulation, tire parti du middleware fourni par l’écosystème et gère une API cohérente dans votre application, même si vous utilisez différents services dans différentes parties de votre application.
- Contributeurs de l’écosystème : si vous souhaitez contribuer à l’écosystème, envisagez d’écrire des composants intergiciels personnalisés.
Pour plus d’exemples, consultez le dépôt GitHub dotnet/ai-samples . Pour obtenir un exemple de bout en bout, consultez eShopSupport.