Inteligência artificial em .NET (Pré-visualização)
Com uma crescente variedade de serviços de inteligência artificial (IA) disponíveis, os desenvolvedores precisam de uma maneira de integrar e interagir com esses serviços em seus aplicativos .NET. A biblioteca de Microsoft.Extensions.AI
fornece uma abordagem unificada para representar componentes de IA generativos, que permite integração e interoperabilidade perfeitas com vários serviços de IA. Este artigo apresenta a biblioteca e fornece instruções de instalação e exemplos de uso para ajudá-lo a começar.
Instalar o pacote
Para instalar o pacote NuGet do 📦 Microsoft.Extensions.AI, use a CLI do .NET ou adicione uma referência de pacote diretamente ao seu arquivo de projeto C#:
Para obter mais informações, consulte dotnet add package ou Gerir dependências de pacotes em aplicações .NET.
Exemplos de utilização
A interface IChatClient define uma abstração de cliente responsável por interagir com serviços de IA que fornecem recursos de bate-papo. Inclui métodos para enviar e receber mensagens com conteúdo multimodal (como texto, imagens e áudio), como um conjunto completo ou transmitido incrementalmente. Além disso, fornece informações de metadados sobre o cliente e permite recuperar serviços fortemente tipados.
Importante
Para obter mais exemplos de uso e cenários do mundo real, consulte AI para desenvolvedores .NET.
Nesta secção
A interface IChatClient
O exemplo a seguir implementa IChatClient
para mostrar a estrutura geral.
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() { }
}
Você pode encontrar outras implementações concretas de IChatClient
nos seguintes pacotes NuGet:
- 📦 Microsoft.Extensions.AI.AzureAIInference: Implementação apoiada pelo Azure AI Model Inference API.
- 📦 Microsoft.Extensions.AI.Ollama: Implementação suportada pelo Ollama.
- 📦 Microsoft.Extensions.AI.OpenAI: Implementação apoiada por OpenAI ou pontos de extremidade compatíveis com OpenAI (como Azure OpenAI).
Solicitar conclusão do chat
Para solicitar uma conclusão, chame o método IChatClient.CompleteAsync. O pedido é composto por uma ou mais mensagens, cada uma das quais é composta por um ou mais conteúdos. Os métodos de aceleração existem para simplificar casos comuns, como a construção de uma solicitação para uma única parte do conteúdo de texto.
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);
O método IChatClient.CompleteAsync
principal aceita uma lista de mensagens. Esta lista representa o histórico de todas as mensagens que fazem parte da conversa.
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?"),
]));
Cada mensagem no histórico é representada por um objeto ChatMessage. A classe ChatMessage
fornece uma propriedade ChatMessage.Role que indica a função da mensagem. Por padrão, o ChatRole.User é usado. As seguintes funções estão disponíveis:
- ChatRole.Assistant: Instrui ou define o comportamento do assistente.
- ChatRole.System: Fornece respostas à entrada instruída pelo sistema e solicitada pelo usuário.
- ChatRole.Tool: Fornece informações adicionais e referências para a conclusão do chat.
- ChatRole.User: Fornece informações para a conclusão do chat.
Cada mensagem de chat é instanciada, atribuindo à sua propriedade Contents um novo TextContent. Existem vários tipos de de conteúdo que podem ser representados, como uma cadeia de caracteres simples ou um objeto mais complexo que representa uma mensagem multimodal com texto, imagens e áudio:
- AudioContent
- DataContent
- FunctionCallContent
- FunctionResultContent
- ImageContent
- TextContent
- UsageContent
Solicitar conclusão de chat com transmissão
As entradas para IChatClient.CompleteStreamingAsync são idênticas às de CompleteAsync
. No entanto, em vez de retornar a resposta completa como parte de um objeto ChatCompletion, o método retorna um IAsyncEnumerable<T> onde T
é StreamingChatCompletionUpdate, fornecendo um fluxo de atualizações que formam coletivamente a resposta única.
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);
}
Dica
APIs de streaming são quase sinônimo de experiências de usuário de IA. O C# permite cenários atraentes com seu suporte IAsyncEnumerable<T>
, permitindo uma maneira natural e eficiente de transmitir dados.
Chamada da ferramenta
Alguns modelos e serviços suportam a chamada de ferramenta , onde as solicitações podem incluir ferramentas para que o modelo invoque funções para coletar informações adicionais. Em vez de enviar uma resposta final, o modelo solicita uma invocação de função com argumentos específicos. Em seguida, o cliente invoca a função e envia os resultados de volta para o modelo junto com o histórico de conversas. A biblioteca de Microsoft.Extensions.AI
inclui abstrações para vários tipos de conteúdo de mensagem, incluindo solicitações de chamada de função e resultados. Embora os consumidores possam interagir diretamente com esse conteúdo, o Microsoft.Extensions.AI
automatiza essas interações e fornece:
- AIFunction: Representa uma função que pode ser descrita para um serviço de IA e invocada.
-
AIFunctionFactory: Fornece métodos de fábrica para criar implementações comumente usadas de
AIFunction
. -
FunctionInvokingChatClient: Envolve um
IChatClient
para adicionar recursos de invocação automática de funções.
Considere o exemplo a seguir que demonstra uma invocação de função aleatória:
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);
}
O exemplo anterior depende do 📦 Microsoft.Extensions.AI.Ollama pacote NuGet.
O código anterior:
- Define uma função chamada
GetCurrentWeather
que retorna uma previsão do tempo aleatória.- Esta função é decorada com um DescriptionAttribute, que é usado para fornecer uma descrição da função para o serviço de IA.
- Instancia um ChatClientBuilder com um OllamaChatClient e configura-o para utilizar a invocação de função.
- Chama
CompleteStreamingAsync
no cliente, passando um prompt e uma lista de ferramentas que inclui uma função criada com Create. - Itera sobre a resposta, imprimindo cada atualização no console.
Respostas de cache
Se você estiver familiarizado com o Caching no .NET, é bom saber que Microsoft.Extensions.AI fornece outras implementações de IChatClient
delegadas. O DistributedCachingChatClient é um IChatClient
que organiza o cache em camadas ao redor de outra instância arbitrária IChatClient
. Quando um histórico de bate-papo exclusivo é enviado para o DistributedCachingChatClient
, ele o encaminha para o cliente subjacente e, em seguida, armazena em cache a resposta antes de enviá-la de volta ao consumidor. Quando o mesmo prompt for enviado novamente e uma resposta em cache puder ser encontrada, o DistributedCachingChatClient
retornará a resposta em cache em vez de precisar encaminhar a solicitação pelo fluxo de trabalho.
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();
}
O exemplo anterior depende do 📦 pacote Microsoft.Extensions.Caching.Memory NuGet. Para mais informações, veja Caching no .NET.
Usar telemetria
Outro exemplo de um cliente de chat de delegação é o OpenTelemetryChatClient. Esta implementação adere ao OpenTelemetry Semantic Conventions for Generative AI systems. Semelhante a outros IChatClient
delegadores, ele coloca métricas em camadas e se estende em torno de qualquer implementação de IChatClient
subjacente, fornecendo observabilidade aprimorada.
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);
O exemplo anterior depende do pacote 📦 OpenTelemetry.Exporter.Console NuGet.
Fornecer opções
Cada chamada para CompleteAsync ou CompleteStreamingAsync pode, opcionalmente, fornecer uma instância ChatOptions contendo parâmetros adicionais para a operação. Os parâmetros mais comuns entre modelos e serviços de IA aparecem como propriedades fortemente tipadas no tipo, como ChatOptions.Temperature. Outros parâmetros podem ser fornecidos pelo nome de forma fracamente digitada através do dicionário ChatOptions.AdditionalProperties.
Você também pode especificar opções ao criar um IChatClient
com a API fluente de ChatClientBuilder e encadear uma chamada para o método de extensão ConfigureOptions
. Esse cliente de delegação encapsula outro cliente e invoca o delegado fornecido para preencher uma instância ChatOptions
para cada chamada. Por exemplo, para garantir que a propriedade ChatOptions.ModelId seja padronizada para um nome de modelo específico, você pode usar um código como o seguinte:
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" }));
O exemplo anterior depende do 📦 Microsoft.Extensions.AI.Ollama pacote NuGet.
Fluxos de funcionalidade
IChatClient
instâncias podem ser estruturadas em camadas para criar um pipeline de componentes, cada um adicionando funcionalidade específica. Esses componentes podem vir de Microsoft.Extensions.AI
, outros pacotes NuGet ou implementações personalizadas. Essa abordagem permite que você aumente o comportamento do IChatClient
de várias maneiras para atender às suas necessidades específicas. Considere o seguinte código de exemplo que integra um cache distribuído, a invocação de funções e o rastreamento do OpenTelemetry num cliente de chat de exemplo:
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));
}
O exemplo anterior depende dos seguintes pacotes NuGet:
- 📦 Microsoft.Extensions.Caching.Memory
- 📦 Microsoft.Extensions.AI.Ollama
- 📦 OpenTelemetry.Exporter.Console
Middleware IChatClient
personalizado
Para adicionar mais funcionalidades, pode implementar IChatClient
diretamente ou usar a classe DelegatingChatClient. Essa classe serve como base para a criação de clientes de chat que delegam operações a outra instância IChatClient
. Ele simplifica o encadeamento de vários clientes, permitindo que as chamadas passem para um cliente subjacente.
A classe DelegatingChatClient
fornece implementações padrão para métodos como CompleteAsync
, CompleteStreamingAsync
e Dispose
, que encaminham chamadas para o cliente interno. Você pode derivar dessa classe e substituir apenas os métodos necessários para aprimorar o comportamento, enquanto delega outras chamadas à implementação base. Essa abordagem ajuda a criar clientes de bate-papo flexíveis e modulares que são fáceis de estender e compor.
A seguir está uma classe de exemplo derivada de DelegatingChatClient
para fornecer funcionalidade de limitação de taxa, utilizando o 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);
}
}
O exemplo anterior depende do pacote 📦 System.Threading.RateLimiting NuGet. A composição do RateLimitingChatClient
com outro cliente é simples:
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?");
Para simplificar a composição de tais componentes com outros, os autores de componentes devem criar um método de extensão Use*
para registrar o componente em um pipeline. Por exemplo, considere o seguinte método de extensão:
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>
Tais extensões também podem consultar serviços relevantes a partir do contêiner DI; O IServiceProvider usado pelo pipeline é passado como um parâmetro opcional:
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>
O consumidor pode então utilizá-lo facilmente no seu processo, por exemplo:
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();
Este exemplo demonstra o cenário hospedado , em que o consumidor depende de injeção de dependências para fornecer a instância RateLimiter
. Os métodos de extensão anteriores demonstram o uso de um método Use
em ChatClientBuilder. O ChatClientBuilder
também fornece Use sobrecargas que tornam mais fácil escrever esses manipuladores delegados.
- 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>>)
Por exemplo, no exemplo anterior RateLimitingChatClient
, as sobreposições de CompleteAsync
e CompleteStreamingAsync
precisam apenas realizar trabalho antes e depois de delegar para o próximo cliente na cadeia de execução. Para conseguir a mesma coisa sem escrever uma classe personalizada, pode usar uma sobrecarga de Use
que aceita um delegado que é utilizado tanto para CompleteAsync
como para CompleteStreamingAsync
, reduzindo o código repetitivo necessário.
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
A sobrecarga anterior usa internamente um AnonymousDelegatingChatClient
, que permite padrões mais complicados com apenas um pouco de código adicional. Por exemplo, para alcançar o mesmo resultado, mas com o RateLimiter recuperado do 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();
Para cenários em que o desenvolvedor gostaria de especificar implementações delegadas de CompleteAsync
e CompleteStreamingAsync
diretamente, e onde é importante poder escrever uma implementação diferente para cada, a fim de lidar especialmente com seus tipos de retorno exclusivos, existe uma sobrecarga adicional de Use
que aceita um delegado para cada um.
Injeção de dependência
IChatClient implementações normalmente serão fornecidas a uma aplicação através de injeção de dependência (DI). Neste exemplo, um IDistributedCache é adicionado ao contêiner DI, assim como um IChatClient
. O registo para o IChatClient
emprega um builder que cria um pipeline contendo um cliente de cache (que usará um IDistributedCache
recuperado do DI) e o cliente exemplo. O IChatClient
injetado pode ser recuperado e utilizado noutra parte da aplicação.
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();
O exemplo anterior depende dos seguintes pacotes NuGet:
A instância e a configuração injetadas podem diferir com base nas necessidades atuais do aplicativo, e vários pipelines podem ser injetados com chaves diferentes.
Interface IEmbeddingGenerator
A interface IEmbeddingGenerator<TInput,TEmbedding> representa um gerador genérico de incorporações. Aqui, TInput
é o tipo de valores de entrada que estão sendo incorporados, e TEmbedding
é o tipo de incorporação gerada, que herda da classe Embedding.
A classe Embedding
serve como uma classe base para incorporações geradas por um IEmbeddingGenerator
. Ele foi projetado para armazenar e gerenciar os metadados e dados associados às incorporações. Tipos derivados como Embedding<T>
fornecem os dados vetoriais de incorporação concretos. Por exemplo, uma incorporação expõe uma propriedade Embedding<T>.Vector para acessar seus dados de incorporação.
A interface IEmbeddingGenerator
define um método para gerar incorporações de forma assíncrona para uma coleção de valores de entrada, com suporte opcional a configuração e cancelamento. Ele também fornece metadados que descrevem o gerador e permite a recuperação de serviços fortemente tipados que podem ser fornecidos pelo gerador ou seus serviços subjacentes.
Exemplo de implementação
Considere a seguinte implementação de exemplo de um IEmbeddingGenerator
para mostrar a estrutura geral, mas que apenas gera vetores de incorporação aleatórios.
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() { }
}
O código anterior:
- Define uma classe chamada
SampleEmbeddingGenerator
que implementa a interfaceIEmbeddingGenerator<string, Embedding<float>>
. - Tem um construtor principal que aceita um ponto de extremidade e um identificador de modelo, que são usados para identificar o gerador.
- Expõe uma propriedade
Metadata
que fornece metadados sobre o gerador. - Implementa o método
GenerateAsync
para gerar incorporações para uma coleção de valores de entrada:- Simula uma operação assíncrona atrasando por 100 milissegundos.
- Retorna incorporações aleatórias para cada valor de entrada.
Você pode encontrar implementações concretas reais nos seguintes pacotes:
- 📦 Microsoft.Extensions.AI.OpenAI
- 📦 Microsoft.Extensions.AI.Ollama
Criar incorporações
A principal operação realizada com um IEmbeddingGenerator<TInput,TEmbedding> é a geração de inserções, que é efetuada através do seu método 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
personalizado
Tal como acontece com IChatClient
, IEmbeddingGenerator
implementações podem ser colocadas em camadas. Assim como Microsoft.Extensions.AI
fornece implementações delegadas de IChatClient
para cache e telemetria, também fornece uma implementação para 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()));
}
O IEmbeddingGenerator
permite a criação de middleware personalizado que estende a funcionalidade de um IEmbeddingGenerator
. A classe DelegatingEmbeddingGenerator<TInput,TEmbedding> é uma implementação da interface IEmbeddingGenerator<TInput, TEmbedding>
que serve como uma classe base para criar geradores de incorporação que delegam suas operações a outra instância IEmbeddingGenerator<TInput, TEmbedding>
. Ele permite encadear vários geradores em qualquer ordem, redirecionando chamadas para um gerador subjacente. A classe fornece implementações padrão para métodos como GenerateAsync e Dispose
, que encaminham as chamadas para a instância interna do gerador, permitindo a geração de incorporação flexível e modular.
A seguir está um exemplo de implementação de um gerador de incorporação delegada que limita a taxa de solicitações de incorporação geradas.
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);
}
}
Isto pode, então, ser aplicado em camadas em torno de um IEmbeddingGenerator<string, Embedding<float>>
arbitrário para limitar a taxa de todas as operações de geração de embeddings realizadas.
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()));
}
Dessa forma, o RateLimitingEmbeddingGenerator
pode ser composto com outras instâncias IEmbeddingGenerator<string, Embedding<float>>
para fornecer funcionalidade de limitação de velocidade.
Ver também
- Desenvolver aplicativos .NET com recursos de IA
- Componentes unificados de inteligência artificial para .NET usando Microsoft.Extensions.AI
- Crie um aplicativo de bate-papo de IA com o .NET
- Injeção de dependência do .NET
- Limite de taxa de um manipulador HTTP no .NET
- de Host Genérico .NET
- Cacheamento no .NET