Artificiell intelligens i .NET (förhandsversion)
Med en växande mängd ai-tjänster (artificiell intelligens) behöver utvecklare ett sätt att integrera och interagera med dessa tjänster i sina .NET-program. Biblioteket Microsoft.Extensions.AI
ger en enhetlig metod för att representera generativa AI-komponenter, vilket möjliggör sömlös integrering och samverkan med olika AI-tjänster. Den här artikeln introducerar biblioteket och innehåller installationsanvisningar och användningsexempel som hjälper dig att komma igång.
Installera paketet
Om du vill installera 📦 Microsoft.Extensions.AI NuGet-paketet använder du .NET CLI eller lägger till en paketreferens direkt till C#-projektfilen:
dotnet add package Microsoft.Extensions.AI --prerelease
Mer information finns i dotnet add package eller Hantera paketberoenden i .NET-applikationer.
Användningsexempel
Gränssnittet IChatClient definierar en klientabstraktion som ansvarar för att interagera med AI-tjänster som tillhandahåller chattfunktioner. Den innehåller metoder för att skicka och ta emot meddelanden med multimodalt innehåll (till exempel text, bilder och ljud), antingen som en fullständig uppsättning eller strömmas stegvis. Dessutom innehåller den metadatainformation om klienten och gör det möjligt att hämta typsäkra tjänster.
Viktig
Fler användningsexempel och verkliga scenarier finns i AI för .NET-utvecklare.
I det här avsnittet
Gränssnittet IChatClient
Följande exempel implementerar IChatClient
för att visa den allmänna strukturen.
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() { }
}
Du hittar andra konkreta implementeringar av IChatClient
i följande NuGet-paket:
- 📦 Microsoft.Extensions.AI.AzureAIInference: Implementering som backas upp av AZURE AI Model Inference API.
- 📦 Microsoft.Extensions.AI.Ollama: Implementering som stöds av Ollama.
- 📦 Microsoft.Extensions.AI.OpenAI: Implementering som stöds av antingen OpenAI- eller OpenAI-kompatibla slutpunkter (till exempel Azure OpenAI-).
Begäran om att chatten ska slutföras
Om du vill begära ett slutförande anropar du metoden IChatClient.CompleteAsync. Begäran består av ett eller flera meddelanden som var och en består av ett eller flera innehåll. Acceleratormetoder finns för att förenkla vanliga fall, till exempel att skapa en begäran om ett enda textinnehåll.
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);
Kärnmetoden IChatClient.CompleteAsync
accepterar en lista med meddelanden. Den här listan representerar historiken för alla meddelanden som ingår i konversationen.
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?"),
]));
Varje meddelande i historiken representeras av ett ChatMessage objekt. Klassen ChatMessage
innehåller en egenskap för ChatMessage.Role som anger meddelandets roll. Som standard används ChatRole.User. Följande roller är tillgängliga:
- ChatRole.Assistant: Instruerar eller anger assistentens beteende.
- ChatRole.System: Ger svar på systeminstrukna, användarinstrukna indata.
- ChatRole.Tool: Innehåller ytterligare information och referenser för chattavslutningar.
- ChatRole.User: Tillhandahåller indata för chattavslutningar.
Varje chattmeddelande instansieras och tilldelar egenskapen Contents en ny TextContent. Det finns olika typer av innehåll som kan representeras, till exempel en enkel sträng eller ett mer komplext objekt som representerar ett multimodalt meddelande med text, bilder och ljud:
- AudioContent
- DataContent
- FunctionCallContent
- FunctionResultContent
- ImageContent
- TextContent
- UsageContent
Begära att chatten slutförs med direktuppspelning
Indata till IChatClient.CompleteStreamingAsync är identiska med indata för CompleteAsync
. Men i stället för att returnera det fullständiga svaret som en del av ett ChatCompletion objekt returnerar metoden en IAsyncEnumerable<T> där T
är StreamingChatCompletionUpdate, vilket ger en ström med uppdateringar som tillsammans utgör det enskilda svaret.
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);
}
Tips
API:er för direktuppspelning är nästan synonyma med AI-användarupplevelser. C# möjliggör övertygande scenarier med stöd för IAsyncEnumerable<T>
, vilket möjliggör ett naturligt och effektivt sätt att strömma data.
Verktygsanrop
Vissa modeller och tjänster stöder verktyg som anropar, där begäranden kan innehålla verktyg för modellen för att anropa funktioner för att samla in ytterligare information. I stället för att skicka ett slutligt svar begär modellen ett funktionsanrop med specifika argument. Klienten anropar sedan funktionen och skickar resultatet tillbaka till modellen tillsammans med konversationshistoriken.
Microsoft.Extensions.AI
-biblioteket innehåller abstraktioner för olika typer av meddelandeinnehåll, inklusive begäranden om funktionsanrop och resultat. Konsumenter kan interagera direkt med det här innehållet, men Microsoft.Extensions.AI
automatiserar dessa interaktioner och tillhandahåller:
- AIFunction: Representerar en funktion som kan beskrivas för en AI-tjänst och anropas.
-
AIFunctionFactory: Tillhandahåller fabriksmetoder för att skapa vanliga implementeringar av
AIFunction
. -
FunctionInvokingChatClient: Omsluter en
IChatClient
för att lägga till automatiska funktionsanrop.
Tänk dig följande exempel som visar ett slumpmässigt funktionsanrop:
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);
}
Föregående exempel beror på 📦 Microsoft.Extensions.AI.Ollama NuGet-paketet.
Föregående kod:
- Definierar en funktion med namnet
GetCurrentWeather
som returnerar en slumpmässig väderprognos.- Den här funktionen är dekorerad med en DescriptionAttribute, som används för att ge en beskrivning av funktionen till AI-tjänsten.
- Instansierar en ChatClientBuilder med en OllamaChatClient och konfigurerar den att använda funktionsanrop.
- Anropar
CompleteStreamingAsync
på klienten, skickar en prompt och en lista över verktyg som innehåller en funktion som skapats med Create. - Itererar över svaret och skriver ut varje uppdatering till konsolen.
Cache-svar
Om du är bekant med cachelagring i .NETär det bra att veta att Microsoft.Extensions.AI tillhandahåller andra sådana delegerande IChatClient
implementeringar.
DistributedCachingChatClient är en IChatClient
som lager cache runt en godtycklig annan IChatClient
instans. När en unik chatthistorik skickas till DistributedCachingChatClient
vidarebefordras den till den underliggande klienten och cachelagrar sedan svaret innan det skickas tillbaka till konsumenten. Nästa gång samma fråga skickas, så att ett cachelagrat svar kan hittas i cacheminnet, returnerar DistributedCachingChatClient
det cachelagrade svaret i stället för att behöva vidarebefordra begäran längs pipelinen.
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();
}
Föregående exempel beror på 📦 Microsoft.Extensions.Caching.Memory NuGet-paketet. För mer information, se Cachelagring i .NET.
Använda telemetri
Ett annat exempel på en delegerande chattklient är OpenTelemetryChatClient. Den här implementeringen följer OpenTelemetry semantiska konventioner för generativa AI-system. På samma sätt som andra IChatClient
delegerare lagrar den mått och sträcker sig över alla underliggande IChatClient
implementering, vilket ger förbättrad observerbarhet.
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);
Föregående exempel beror på 📦 OpenTelemetry.Exporter.Console NuGet-paketet.
Ange alternativ
Varje anrop till CompleteAsync eller CompleteStreamingAsync kan också ange en ChatOptions instans som innehåller ytterligare parametrar för åtgärden. De vanligaste parametrarna bland AI-modeller och -tjänster visas som starkt skrivna egenskaper för typen, till exempel ChatOptions.Temperature. Andra parametrar kan anges med namn på ett svagt typat sätt via uppslagsverket ChatOptions.AdditionalProperties.
Du kan också ange alternativ när du skapar en IChatClient
med API:et fluent ChatClientBuilder och kopplar ett anrop till ConfigureOptions
-tilläggsmetoden. Den här delegerande klienten omsluter en annan klient och anropar det angivna ombudet för att fylla i en ChatOptions
instans för varje anrop. Om du till exempel vill se till att egenskapen ChatOptions.ModelId är standard för ett visst modellnamn kan du använda kod som följande:
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" }));
Föregående exempel beror på 📦 Microsoft.Extensions.AI.Ollama NuGet-paketet.
Funktionspipelines
IChatClient
instanser kan staplas för att skapa en pipeline med komponenter som ger varje komponent specifika funktioner. Dessa komponenter kan komma från Microsoft.Extensions.AI
, andra NuGet-paket eller anpassade implementeringar. Med den här metoden kan du utöka beteendet för IChatClient
på olika sätt för att uppfylla dina specifika behov. Tänk på följande exempelkod som lagrar en distribuerad cache, funktionsanrop och OpenTelemetry-spårning runt en chattklientexempel:
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));
}
Föregående exempel beror på följande NuGet-paket:
- 📦 Microsoft.Extensions.Caching.Memory
- 📦 Microsoft.Extensions.AI.Ollama
- 📦 OpenTelemetry.Exporter.Console
Anpassat IChatClient
mellanprogram
Om du vill lägga till ytterligare funktioner kan du implementera IChatClient
direkt eller använda klassen DelegatingChatClient. Den här klassen fungerar som bas för att skapa chattklienter som delegerar åtgärder till en annan IChatClient
instans. Det gör det enklare att länka flera klienter så att anrop kan skickas till en underliggande klient.
Klassen DelegatingChatClient
tillhandahåller standardimplementeringar för metoder som CompleteAsync
, CompleteStreamingAsync
och Dispose
, som vidarebefordrar anrop till den inre klienten. Du kan härleda från den här klassen och endast åsidosätta de metoder som du behöver för att förbättra beteendet, samtidigt som du delegerar andra anrop till basimplementeringen. Den här metoden hjälper dig att skapa flexibla och modulära chattklienter som är enkla att utöka och skriva.
Följande är en exempelklass som härleds från DelegatingChatClient
för att tillhandahålla hastighetsbegränsningsfunktioner med hjälp av 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);
}
}
Föregående exempel beror på 📦 System.Threading.RateLimiting NuGet-paketet. Sammansättning av RateLimitingChatClient
med en annan klient är enkel:
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?");
För att förenkla sammansättningen av sådana komponenter med andra bör komponentförfattare skapa en Use*
tilläggsmetod för att registrera komponenten i en pipeline. Tänk till exempel på följande tilläggsmetod:
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>
Sådana tillägg kan också fråga efter relevanta tjänster från DI-containern. den IServiceProvider som används av pipelinen skickas som en valfri parameter:
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>
Konsumenten kan sedan enkelt använda detta i sin pipeline, till exempel:
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();
Det här exemplet visar värdbaserat scenario, där konsumenten förlitar sig på beroendeinmatning för att tillhandahålla den RateLimiter
instansen. De föregående tilläggsmetoderna visar användningen av en Use
-metod på ChatClientBuilder.
ChatClientBuilder
ger också Use överbelastningar som gör det enklare att skriva delegeringshanterare av den typen.
- 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>>)
I det tidigare exemplet RateLimitingChatClient
behöver överlagringarna av CompleteAsync
och CompleteStreamingAsync
bara utföra arbete före och efter delegering till nästa klient i pipelinen. Om du vill uppnå samma sak utan att skriva en anpassad klass kan du använda en överlagring av Use
som accepterar en delegat som används för både CompleteAsync
och CompleteStreamingAsync
, vilket minskar den mall som krävs.
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
Den föregående överlagringen använder internt en AnonymousDelegatingChatClient
, som möjliggör mer komplicerade mönster med bara lite extra kod. För att till exempel uppnå samma resultat men med RateLimiter hämtat från DI:
using System.Threading.RateLimiting;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
var client = new SampleChatClient(new Uri("http://localhost"), "test")
.AsBuilder()
.UseDistributedCache()
.Use(static (innerClient, services) =>
{
var rateLimiter = services.GetRequiredService<RateLimiter>();
return new AnonymousDelegatingChatClient(
innerClient, async (chatMessages, options, nextAsync, cancellationToken) =>
{
using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
.ConfigureAwait(false);
if (!lease.IsAcquired)
{
throw new InvalidOperationException("Unable to acquire lease.");
}
await nextAsync(chatMessages, options, cancellationToken);
});
})
.UseOpenTelemetry()
.Build();
För scenarier där utvecklaren vill ange delegerade implementeringar av CompleteAsync
och CompleteStreamingAsync
direkt, och där det är viktigt att kunna skriva en annan implementering för var och en för att hantera deras unika returtyper specifikt, finns det en annan överlagring av Use
som accepterar en delegat för var och en.
Beroendeinmatning
IChatClient implementeringar tillhandahålls vanligtvis till ett program via beroendeinmatning (DI). I det här exemplet läggs en IDistributedCache till i DI-containern, liksom en IChatClient
. Registreringen för IChatClient
använder en builder som skapar en pipeline som innehåller en caching-klient (som sedan använder en IDistributedCache
hämtad från DI) och provklienten. Det injekterade IChatClient
kan hämtas och användas någon annanstans i appen.
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();
Föregående exempel beror på följande NuGet-paket:
Vilken instans och konfiguration som matas in kan skilja sig beroende på programmets aktuella behov, och flera pipelines kan matas in med olika nycklar.
Gränssnittet IEmbeddingGenerator
Gränssnittet IEmbeddingGenerator<TInput,TEmbedding> representerar en allmän generator av inbäddningar. Här är TInput
typen av indatavärden som bäddas in och TEmbedding
är den typ av genererad inbäddning som ärver från klassen Embedding.
Klassen Embedding
fungerar som en basklass för inbäddningar som genereras av en IEmbeddingGenerator
. Den är utformad för att lagra och hantera metadata och data som är associerade med inbäddningar. Härledda typer som Embedding<T>
tillhandahålla konkreta inbäddningsvektordata. En inbäddning gör till exempel en Embedding<T>.Vector-egenskap tillgänglig för åtkomst till dess inbäddningsdata.
Gränssnittet IEmbeddingGenerator
definierar en metod för att asynkront generera inbäddningar för en samling indatavärden, med valfri konfiguration och stöd för annullering. Den innehåller även metadata som beskriver generatorn och möjliggör hämtning av starkt typade tjänster som kan tillhandahållas av generatorn eller dess underliggande tjänster.
Exempelimplementering
Överväg följande exempelimplementering av en IEmbeddingGenerator
för att visa den allmänna strukturen men som bara genererar slumpmässiga inbäddningsvektorer.
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() { }
}
Föregående kod:
- Definierar en klass med namnet
SampleEmbeddingGenerator
som implementerarIEmbeddingGenerator<string, Embedding<float>>
-gränssnittet. - Har en primär konstruktor som accepterar en slutpunkt och ett modell-ID som används för att identifiera generatorn.
- Exponerar en
Metadata
egenskap som tillhandahåller metadata om generatorn. - Implementerar metoden
GenerateAsync
för att generera inbäddningar för en samling indatavärden:- Simulerar en asynkron åtgärd genom att fördröja i 100 millisekunder.
- Returnerar slumpmässiga inbäddningar för varje indatavärde.
Du hittar faktiska konkreta implementeringar i följande paket:
Skapa inbäddningar
Den primära åtgärden som utförs med en IEmbeddingGenerator<TInput,TEmbedding> är inbäddningsgenerering, vilket utförs med dess GenerateAsync-metod.
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()));
}
Anpassat IEmbeddingGenerator
mellanprogram
Precis som med IChatClient
kan implementeringar av IEmbeddingGenerator
läggas i lager. Precis som Microsoft.Extensions.AI
tillhandahåller delegering av implementeringar av IChatClient
för cachelagring och telemetri, tillhandahåller det även en implementering för 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()));
}
Med IEmbeddingGenerator
kan du skapa anpassade mellanprogram som utökar funktionerna i en IEmbeddingGenerator
. Klassen DelegatingEmbeddingGenerator<TInput,TEmbedding> är en implementering av IEmbeddingGenerator<TInput, TEmbedding>
-gränssnittet som fungerar som en basklass för att skapa inbäddningsgeneratorer som delegerar sina åtgärder till en annan IEmbeddingGenerator<TInput, TEmbedding>
instans. Det gör det möjligt att länka flera generatorer i valfri ordning och skicka anrop till en underliggande generator. Klassen tillhandahåller standardimplementeringar för metoder som GenerateAsync och Dispose
, som vidarebefordrar anropen till den inre generatorinstansen, vilket möjliggör flexibel och modulär inbäddningsgenerering.
Följande är ett exempel på en implementering av en sådan delegerande inbäddningsgenerator som begränsar inbäddningsgenereringsbegäranden:
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);
}
}
Detta kan sedan läggas som ett lager runt en godtycklig IEmbeddingGenerator<string, Embedding<float>>
för att hastighetsbegränsa alla inbäddningsgenereringsåtgärder som utförs.
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()));
}
På så sätt kan RateLimitingEmbeddingGenerator
bestå av andra IEmbeddingGenerator<string, Embedding<float>>
instanser för att tillhandahålla hastighetsbegränsningsfunktioner.