Intelligenza artificiale in .NET (anteprima)
Con un'ampia gamma di servizi di intelligenza artificiale (AI) disponibili, gli sviluppatori hanno bisogno di un modo per integrare e interagire con questi servizi nelle applicazioni .NET. La libreria Microsoft.Extensions.AI
offre un approccio unificato per rappresentare i componenti generativi di intelligenza artificiale, che consente l'integrazione e l'interoperabilità senza problemi con vari servizi di intelligenza artificiale. Questo articolo presenta la libreria e fornisce istruzioni di installazione ed esempi di utilizzo per iniziare.
Installare il pacchetto
Per installare il pacchetto NuGet 📦 Microsoft.Extensions.AI, usare la .NET CLI o aggiungere un riferimento al pacchetto direttamente nel file di progetto in C#
dotnet add package Microsoft.Extensions.AI --prelease
Per altre informazioni, vedere dotnet add package o Gestire le dipendenze dei pacchetti nelle applicazioni .NET.
Esempi di utilizzo
L'interfaccia IChatClient definisce un'astrazione client responsabile dell'interazione con i servizi di intelligenza artificiale che forniscono funzionalità di chat. Include metodi per l'invio e la ricezione di messaggi con contenuto multi modale (ad esempio testo, immagini e audio), come set completo o trasmesso in modo incrementale. Fornisce inoltre informazioni sui metadati sul client e permette di recuperare servizi fortemente tipizzati.
Importante
Per altri esempi di utilizzo e scenari reali, vedere IA per sviluppatori .NET.
In questa sezione
-
L'interfaccia
IChatClient
-
L'interfaccia
IEmbeddingGenerator
- esempio di implementazione
- Crea incorporamenti
-
IEmbeddingGenerator
middleware personalizzato
Interfaccia IChatClient
L'esempio seguente implementa IChatClient
per mostrare la struttura generale.
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() { }
}
È possibile trovare altre implementazioni concrete di IChatClient
nei pacchetti NuGet seguenti:
- 📦 Microsoft.Extensions.AI.AzureAIInference: implementazione supportata da 'API di inferenza del modello di intelligenza artificiale di Azure.
- 📦 Microsoft.Extensions.AI.Ollama: implementazione supportata da Ollama.
- 📦 Microsoft.Extensions.AI.OpenAI: implementazione supportata da OpenAI o endpoint compatibili con OpenAI ,ad esempio Azure OpenAI).
Richiedere il completamento della chat
Per richiedere un completamento, chiamare il metodo IChatClient.CompleteAsync. La richiesta è costituita da uno o più messaggi, ognuno dei quali è composto da una o più parti di contenuto. Esistono metodi acceleratori per semplificare i casi comuni, ad esempio la creazione di una richiesta per una singola parte di contenuto di testo.
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);
Il metodo principale IChatClient.CompleteAsync
accetta un elenco di messaggi. Questo elenco rappresenta la cronologia di tutti i messaggi che fanno parte della conversazione.
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?"),
]));
Ogni messaggio nella cronologia è rappresentato da un oggetto ChatMessage. La classe ChatMessage
fornisce una proprietà ChatMessage.Role che indica il ruolo del messaggio. Per impostazione predefinita, viene usato il ChatRole.User. Sono disponibili i ruoli seguenti:
- ChatRole.Assistant: indica o imposta il comportamento dell'assistente.
- ChatRole.System: fornisce risposte all'input richiesto dal sistema e richiesto dall'utente.
- ChatRole.Tool: fornisce informazioni e riferimenti aggiuntivi per i completamenti della chat.
- ChatRole.User: fornisce l'input per i completamenti della chat.
Ogni messaggio di chat viene creato, assegnando alla proprietà Contents un nuovo TextContent. Esistono diversi tipi di di contenuto che possono essere rappresentati, ad esempio una stringa semplice o un oggetto più complesso che rappresenta un messaggio multi modale con testo, immagini e audio:
- AudioContent
- DataContent
- FunctionCallContent
- FunctionResultContent
- ImageContent
- TextContent
- UsageContent
Richiedere il completamento della chat con lo streaming
Gli input per IChatClient.CompleteStreamingAsync sono identici a quelli di CompleteAsync
. Tuttavia, anziché restituire la risposta completa come parte di un oggetto ChatCompletion, il metodo restituisce un IAsyncEnumerable<T> in cui T
è StreamingChatCompletionUpdate, fornendo un flusso di aggiornamenti che formano collettivamente la singola risposta.
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);
}
Mancia
Le API di streaming sono quasi sinonimi di esperienze utente di intelligenza artificiale. C# consente scenari accattivanti con il supporto IAsyncEnumerable<T>
, consentendo un modo naturale ed efficiente per trasmettere i dati.
Richiamo dello strumento
Alcuni modelli e servizi supportano la chiamata di strumenti , dove le richieste possono includere strumenti affinché il modello richiami delle funzioni per raccogliere ulteriori informazioni. Anziché inviare una risposta finale, il modello richiede una chiamata di funzione con argomenti specifici. Il client richiama quindi la funzione e invia i risultati al modello insieme alla cronologia della conversazione. La libreria Microsoft.Extensions.AI
include astrazioni per vari tipi di contenuto del messaggio, incluse le richieste di chiamata di funzione e i risultati. Anche se gli utenti possono interagire direttamente con questo contenuto, Microsoft.Extensions.AI
automatizza queste interazioni e fornisce:
- AIFunction: rappresenta una funzione che può essere descritta in un servizio di intelligenza artificiale e richiamata.
-
AIFunctionFactory: fornisce metodi di fabbrica per creare implementazioni comunemente utilizzate di
AIFunction
. -
FunctionInvokingChatClient: avvolge un
IChatClient
per aggiungere capacità di invocazione automatica della funzione.
Si consideri l'esempio seguente che illustra una chiamata a una funzione casuale:
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);
}
L'esempio precedente dipende dal pacchetto NuGet 📦 Microsoft.Extensions.AI.Ollama.
Il codice precedente:
- Definisce una funzione denominata
GetCurrentWeather
che restituisce una previsione meteo casuale.- Questa funzione è contrassegnata con un DescriptionAttribute, utilizzato per fornire una descrizione della funzione al servizio di intelligenza artificiale.
- Crea un'istanza di un ChatClientBuilder con un OllamaChatClient e la configura per invocare una funzione.
- Chiama
CompleteStreamingAsync
sul client, passando un prompt e un elenco di strumenti che includono una funzione creata con Create. - Itera sulla risposta, stampando ogni aggiornamento sul terminale.
Memorizzare nella cache le risposte
Se si ha familiarità con memorizzazione nella cache in .NET, è consigliabile sapere che Microsoft.Extensions.AI fornisce altre implementazioni di delega IChatClient
. Il DistributedCachingChatClient è un IChatClient
che applica la memorizzazione nella cache a un'altra istanza arbitraria di IChatClient
. Quando una cronologia di chat univoca viene inviata al DistributedCachingChatClient
, la inoltra al client sottostante e quindi memorizza nella cache la risposta prima di inviarla al consumer. Alla successiva invio della stessa richiesta, in modo che sia possibile trovare una risposta memorizzata nella cache, il DistributedCachingChatClient
restituisce la risposta memorizzata nella cache anziché dover inoltrare la richiesta lungo la pipeline.
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();
}
L'esempio precedente dipende dal pacchetto 📦 Microsoft.Extensions.Caching.Memory NuGet. Per ulteriori informazioni, vedere cache in .NET.
Usare i dati di telemetria
Un altro esempio di client di chat delegante è il OpenTelemetryChatClient. Questa implementazione è conforme alle OpenTelemetry Semantic Conventions for Generative AI systems. Analogamente ad altri delegati IChatClient
, stratifica le metriche e avvolge qualsiasi implementazione IChatClient
sottostante, offrendo una maggiore osservabilità.
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);
L'esempio precedente dipende dal pacchetto NuGet 📦 OpenTelemetry.Exporter.Console.
Specificare le opzioni
Ogni chiamata a CompleteAsync o CompleteStreamingAsync può facoltativamente fornire un'istanza di ChatOptions contenente parametri aggiuntivi per l'operazione. I parametri più comuni tra i modelli e i servizi di intelligenza artificiale vengono visualizzati come proprietà fortemente tipizzate nel tipo, ad esempio ChatOptions.Temperature. È possibile fornire altri parametri per nome in modalità a tipizzazione debole tramite il dizionario ChatOptions.AdditionalProperties.
È anche possibile specificare le opzioni quando si costruisce un IChatClient
con l'API Fluent ChatClientBuilder e collegando una chiamata al Metodo di Estensione ConfigureOptions
. Questo client delegante avvolge un altro client e invoca il delegato fornito per popolare un'istanza di ChatOptions
per ogni chiamata. Ad esempio, per assicurarsi che per impostazione predefinita la proprietà ChatOptions.ModelId sia un nome di modello specifico, è possibile usare codice simile al seguente:
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" }));
L'esempio precedente dipende dal pacchetto NuGet 📦 Microsoft.Extensions.AI.Ollama.
Pipeline delle funzionalità
IChatClient
istanze possono essere sovrapposte per creare una pipeline di componenti, ognuna delle quali aggiunge funzionalità specifiche. Questi componenti possono provenire da Microsoft.Extensions.AI
, altri pacchetti NuGet o implementazioni personalizzate. Questo approccio consente di aumentare il comportamento dei IChatClient
in vari modi per soddisfare le esigenze specifiche. Si consideri il seguente codice di esempio che crea un'architettura a strati con una cache distribuita, un'invocazione di funzione e un tracciamento OpenTelemetry su un client di chat di esempio.
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));
}
L'esempio precedente dipende dai pacchetti NuGet seguenti:
- 📦 Microsoft.Extensions.Caching.Memory
- 📦 Microsoft.Extensions.AI.Ollama
- 📦 OpenTelemetry.Exporter.Console
Middleware IChatClient
personalizzato
Per aggiungere altre funzionalità, è possibile implementare IChatClient
direttamente o usare la classe DelegatingChatClient. Questa classe funge da base per la creazione di client di chat che delegano le operazioni a un'altra istanza di IChatClient
. Semplifica il concatenamento di più client, consentendo alle chiamate di passare attraverso un client sottostante.
La classe DelegatingChatClient
fornisce implementazioni predefinite per metodi come CompleteAsync
, CompleteStreamingAsync
e Dispose
, che inoltrano le chiamate al client interno. È possibile derivare da questa classe ed eseguire l'override solo dei metodi necessari per migliorare il comportamento, delegando altre chiamate all'implementazione di base. Questo approccio consente di creare client di chat flessibili e modulari facili da estendere e comporre.
Di seguito è riportata una classe di esempio derivata da DelegatingChatClient
per fornire funzionalità di limitazione della velocità, utilizzando il 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);
}
}
L'esempio precedente dipende dal pacchetto NuGet 📦 System.Threading.RateLimiting. La composizione del RateLimitingChatClient
con un altro client è semplice:
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?");
Per semplificare la composizione di tali componenti con altri componenti, gli autori di componenti devono creare un metodo di estensione Use*
per registrare il componente in una pipeline. Si consideri ad esempio il metodo di estensione seguente:
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>
Tali estensioni possono anche richiedere i servizi pertinenti dal contenitore DI (Dependency Injection); il IServiceProvider usato dalla pipeline viene passato come parametro facoltativo.
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>
Il consumatore può quindi usarlo facilmente nella pipeline, ad esempio:
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();
Questo esempio illustra scenario ospitato, in cui il consumer si basa su di inserimento delle dipendenze per fornire l'istanza di RateLimiter
. I precedenti metodi di estensione illustrano l'uso di un metodo Use
in ChatClientBuilder. Il ChatClientBuilder
fornisce anche sovraccarichi Use che facilitano la scrittura di gestori delegati.
- 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>>)
Nell'esempio precedente RateLimitingChatClient
, gli override di CompleteAsync
e CompleteStreamingAsync
devono solo eseguire operazioni prima e dopo la delega al client successivo nella pipeline. Per ottenere la stessa operazione senza scrivere una classe personalizzata, è possibile usare un overload di Use
che accetta un delegato usato sia per CompleteAsync
che per CompleteStreamingAsync
, riducendo il boilerplate necessario:
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
L'overload precedente utilizza internamente un AnonymousDelegatingChatClient
, che consente modelli più complessi con solo un po' di codice aggiuntivo. Ad esempio, per ottenere lo stesso risultato, ma con il RateLimiter recuperato dall'inserimento delle dipendenze:
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();
Per gli scenari in cui lo sviluppatore vuole specificare implementazioni delegate di CompleteAsync
e CompleteStreamingAsync
in linea, e dove è importante scrivere un'implementazione diversa per ciascuno per gestire in modo specifico i loro tipi di ritorno univoci, esiste un'altra versione di overload di Use
che accetta un delegato per ciascuno.
Iniezione delle dipendenze
IChatClient implementazioni verranno in genere fornite a un'applicazione tramite "inserimento delle dipendenze" (DI). In questo esempio, un IDistributedCache viene aggiunto al contenitore DI, così come un IChatClient
. La registrazione per il IChatClient
utilizza un costruttore che crea una pipeline contenente un client di memorizzazione nella cache (che utilizzerà un IDistributedCache
recuperato da DI) e il client di esempio. Il IChatClient
inserito può essere recuperato e usato altrove nell'app.
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();
L'esempio precedente dipende dai pacchetti NuGet seguenti:
- 📦 Microsoft.Extensions.Hosting
- 📦 Microsoft.Extensions.Caching.Memory
L'istanza e la configurazione inserita possono variare in base alle esigenze correnti dell'applicazione e possono essere inserite più pipeline con chiavi diverse.
Interfaccia IEmbeddingGenerator
L'interfaccia IEmbeddingGenerator<TInput,TEmbedding> rappresenta un generatore generico di incorporamenti. In questo caso, TInput
è il tipo di valori di input incorporati e TEmbedding
è il tipo di incorporamento generato, che eredita dalla classe Embedding.
La classe Embedding
funge da classe base per gli incorporamenti generati da un IEmbeddingGenerator
. È progettato per archiviare e gestire i metadati e i dati associati agli incorporamenti. I tipi derivati come Embedding<T>
forniscono i dati vettoriali di incorporamento concreti. Ad esempio, un embedding espone una proprietà Embedding<T>.Vector per accedere ai dati di embedding.
L'interfaccia IEmbeddingGenerator
definisce un metodo per generare in modo asincrono incorporamenti per una raccolta di valori di input, con supporto facoltativo per la configurazione e l'annullamento. Fornisce inoltre metadati che descrivono il generatore e consente il recupero di servizi fortemente tipizzati che possono essere forniti dal generatore o dai suoi servizi di base.
Implementazione di esempio
Si consideri l'implementazione di esempio seguente di un IEmbeddingGenerator
per mostrare la struttura generale, ma che genera solo vettori di incorporamento casuali.
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() { }
}
Il codice precedente:
- Definisce una classe denominata
SampleEmbeddingGenerator
che implementa l'interfacciaIEmbeddingGenerator<string, Embedding<float>>
. - Dispone di un costruttore principale che accetta un endpoint e un ID modello, utilizzati per identificare il generatore.
- Espone una proprietà
Metadata
che fornisce metadati sul generatore. - Implementa il metodo
GenerateAsync
per generare incorporamenti per una raccolta di valori di input:- Simula un'operazione asincrona ritardando per 100 millisecondi.
- Restituisce incorporamenti casuali per ogni valore di input.
È possibile trovare implementazioni concrete effettive nei pacchetti seguenti:
- 📦 Microsoft.Extensions.AI.OpenAI
- 📦 Microsoft.Extensions.AI.Ollama
Creare incorporamenti
L'operazione primaria eseguita con un IEmbeddingGenerator<TInput,TEmbedding> è la generazione di incorporamento, che viene eseguita con il relativo metodo 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()));
}
Middleware IEmbeddingGenerator
personalizzato
Come nel caso di IChatClient
, le implementazioni di IEmbeddingGenerator
possono essere stratificate. Allo stesso modo in cui Microsoft.Extensions.AI
fornisce implementazioni delegate di IChatClient
per la memorizzazione nella cache e la telemetria, esso fornisce anche un'implementazione per 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()));
}
Il IEmbeddingGenerator
consente di creare middleware personalizzato che estende la funzionalità di un IEmbeddingGenerator
. La classe DelegatingEmbeddingGenerator<TInput,TEmbedding> è un'implementazione dell'interfaccia IEmbeddingGenerator<TInput, TEmbedding>
che funge da classe di base per la creazione di generatori di incorporamento che delegano le operazioni a un'altra istanza di IEmbeddingGenerator<TInput, TEmbedding>
. Consente di concatenare più generatori in qualsiasi ordine, passando chiamate a un generatore sottostante. La classe fornisce implementazioni predefinite per metodi quali GenerateAsync e Dispose
, che inoltrano le chiamate all'istanza del generatore interno, abilitando la generazione di incorporamenti flessibili e modulari.
Di seguito è riportato un esempio di implementazione di un generatore delegato di embedding che limita le richieste di generazione di embedding.
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);
}
}
Può quindi essere sovrapposto a un IEmbeddingGenerator<string, Embedding<float>>
arbitrario per limitare la frequenza di tutte le operazioni di generazione di incorporamento eseguite.
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()));
}
In questo modo, il RateLimitingEmbeddingGenerator
può essere composto con altre istanze di IEmbeddingGenerator<string, Embedding<float>>
per fornire funzionalità di limitazione della velocità.
Vedere anche
- Sviluppare applicazioni .NET con funzionalità di intelligenza artificiale
- Blocchi unificati di intelligenza artificiale per .NET tramite Microsoft.Extensions.AI
- Creare un'app di chat di intelligenza artificiale con .NET
- Iniezione di dipendenze .NET
- limite di frequenza di un gestore HTTP in .NET
- Host generico .NET
- Memorizzazione nella Cache in .NET