Inteligência artificial no .NET (versão preliminar)
Com uma variedade cada vez maior de serviços de IA (inteligência artificial) disponíveis, os desenvolvedores precisam de uma maneira de integrar e interagir com esses serviços em seus aplicativos .NET. A biblioteca Microsoft.Extensions.AI
fornece uma abordagem unificada para representar componentes de IA generativa, o 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 ajudar você a começar.
Instalar o pacote
Para instalar o pacote NuGet da biblioteca 📦 Microsoft.Extensions.AI, use a CLI do .NET ou adicione uma referência de pacote diretamente ao arquivo de projeto C#:
dotnet add package Microsoft.Extensions.AI --prerelease
Para obter mais informações, consulte dotnet add package ou Gerenciar dependências de pacotes em aplicativos .NET.
Exemplos de uso
A interface do IChatClient define uma abstração do cliente responsável por interagir com serviços de IA que fornecem recursos de chat. Ela inclui métodos para enviar e receber mensagens com conteúdo multimodal (como texto, imagens e áudio), como um conjunto completo ou transmitidas de maneira incremental. Além disso, ele fornece informações de metadados sobre o cliente e permite recuperar serviços fortemente tipados.
Importante
Para ver mais exemplos de uso e cenários reais, consulte o documento IA para desenvolvedores do .NET.
Nesta seção
A interface IChatClient
.
O exemplo a seguir implementa o 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 do IChatClient
nos seguintes pacotes NuGet:
- 📦 Microsoft.Extensions.AI.AzureAIInference: implementação com suporte da API de Inferência do Modelo de IA do Azure.
- 📦 Microsoft.Extensions.AI.Ollama: implementação com suporte do Ollama.
- 📦 Microsoft.Extensions.AI.OpenAI: Implementação com suporte por endpoints de OpenAI ou compatíveis com OpenAI (como Azure OpenAI).
Solicitar a conclusão do chat
Para solicitar uma conclusão, chame o método IChatClient.CompleteAsync. A solicitação é composta por uma ou mais mensagens, e cada uma delas é composta por uma ou mais partes de conteúdo. Existem métodos do Acelerador para simplificar casos comuns, como a criação de uma solicitação para um único 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 de IChatClient.CompleteAsync
principal aceita uma lista de mensagens. Essa 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. O ChatRole.User é usado por padrão. As seguintes funções estão disponíveis:
- ChatRole.Assistant: instrui ou define o comportamento do assistente.
- ChatRole.System: fornece respostas para entradas orientadas pelo sistema e solicitadas pelo usuário.
- ChatRole.Tool: fornece informações e referências adicionais para completamentos de chat.
- ChatRole.User: fornece entrada para conclusões de chat.
Cada mensagem de chat é instanciada, atribuindo à sua propriedade Contents um novo TextContent. Há vários tipos 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 a conclusão do chat com streaming
As entradas de 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> em que 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
As APIs de streaming são quase idênticas às experiências de usuário de IA. O C# proporciona cenários atraentes com seu suporte a IAsyncEnumerable<T>
, permitindo a transmissão de dados de maneira natural e eficiente.
Ferramenta de chamado
Alguns modelos e serviços dão suporte à funcionalidade de "chamada de ferramentas" , em que as solicitações podem incluir ferramentas para o modelo invocar funções e 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, a biblioteca 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: encapsula um
IChatClient
para adicionar capacidades 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 pacote NuGet 📦 Microsoft.Extensions.AI.Ollama.
O código anterior:
- Define uma função chamada
GetCurrentWeather
que retorna uma previsão de tempo aleatória.- Essa função é decorada com um DescriptionAttribute, que é usado para fornecer uma descrição da função para o serviço de IA.
- Cria uma instância de um ChatClientBuilder com um OllamaChatClient e o configura para usar 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. - Percorre a resposta, imprimindo cada atualização no console.
Respostas de cache
Se você estiver familiarizado com Cache no .NET, é bom saber que ele Microsoft.Extensions.AI fornece outras implementações de delegação IChatClient
. O DistributedCachingChatClient é um IChatClient
que aplica camadas de cache em torno de outra instância arbitrária IChatClient
. Quando um histórico de chats exclusivo é enviado para o DistributedCachingChatClient
, ele o encaminha para o cliente subjacente e armazena em cache a resposta antes de enviá-la de volta ao consumidor. Na próxima vez que o mesmo prompt for enviado e uma resposta em cache puder ser encontrada, o DistributedCachingChatClient
retornará essa resposta em vez de precisar passar a solicitação ao longo do 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();
}
O exemplo anterior depende do pacote NuGet de 📦 Microsoft.Extensions.Caching.Memory. Para mais informações, consulte Cache no .NET.
Usar telemetria
Outro exemplo de um cliente de chat de delegação é o OpenTelemetryChatClient. Essa implementação segue as convenções semânticas OpenTelemetry para sistemas de IA generativa. Semelhante a outros delegadores IChatClient
, ele coloca camadas de métricas e spans em torno de qualquer implementação de IChatClient
subjacente, proporcionando uma 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 NuGet de 📦 OpenTelemetry.Exporter.Console.
Fornecer opções
Cada chamada para CompleteAsync ou CompleteStreamingAsync pode fornecer uma instância de 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 maneira fraca por meio do dicionário de ChatOptions.AdditionalProperties.
Você também pode especificar opções ao montar um IChatClient
com a API ChatClientBuilder fluente e encadear uma chamada ao método de extensão ConfigureOptions
. Esse cliente de delegação encapsula outro cliente e invoca o delegado fornecido para preencher uma instância de 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 códigos 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 pacote NuGet 📦 Microsoft.Extensions.AI.Ollama.
Pipelines de funcionalidade
IChatClient
instâncias podem ser estratificadas para criar um pipeline de componentes, cada uma adicionando funcionalidade específica. Esses componentes podem vir da biblioteca 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 executa camadas de um cache distribuído, invocação de função e rastreamento OpenTelemetry sobre um 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 outras funcionalidades, você pode implementar o IChatClient
diretamente ou usar a classe DelegatingChatClient. Essa classe serve como base para criar clientes de chat que delegam operações a outra instância do 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. É possível derivar dessa classe e substituir somente os métodos necessários para aprimorar o comportamento, delegando outras chamadas para a implementação base. Essa abordagem ajuda a criar clientes de chat flexíveis e modulares que são fáceis de estender e redigir.
Veja a seguir 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 NuGet de 📦 System.Threading.RateLimiting. 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 desses componentes com outros, os autores de componentes devem criar um método de extensão Use*
para registrar o componente no 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>
Essas extensões também podem consultar serviços relevantes do contêiner de 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 usar isso facilmente no seu fluxo de trabalho, 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 cenário hospedado, em que o consumidor depende de injeção de dependência para fornecer a instância de RateLimiter
. Os métodos de extensão anteriores demonstram o uso de um método Use
em ChatClientBuilder. O ChatClientBuilder
também fornece as sobrecargas Use que facilitam a escrita desses manipuladores de delegação.
- 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 de RateLimitingChatClient
, as sobrescrições de CompleteAsync
e CompleteStreamingAsync
só precisam executar tarefas antes e depois de delegar para o próximo cliente no pipeline. Para obter o mesmo resultado sem escrever uma classe personalizada, você pode usar uma sobrecarga de Use
que aceita um delegado usado tanto para CompleteAsync
quanto 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 obter o mesmo resultado, mas com o RateLimiter recuperado da 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
no local, e onde é importante poder escrever uma implementação diferente para cada um, especialmente para lidar com seus tipos de retorno exclusivos, existe outra sobrecarga de Use
que aceita um delegado para cada um.
Injeção de dependência
IChatClient implementações normalmente serão fornecidas a um aplicativo por meio de Injeção de dependência (DI). Neste exemplo, um IDistributedCache é adicionado ao contêiner de DI, assim como um IChatClient
. O registro do IChatClient
emprega um construtor que cria um pipeline que contém um cliente de cache (que usará um IDistributedCache
recuperado da DI) e o cliente de exemplo. O IChatClient
injetado pode ser recuperado e usado em outro lugar no aplicativo.
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.
A interface IEmbeddingGenerator
.
A interface do IEmbeddingGenerator<TInput,TEmbedding> representa um gerador genérico de inserções. Aqui, TInput
é o tipo de valores de entrada que estão sendo inseridos e TEmbedding
é o tipo de inserção gerada, que herda da classe Embedding.
A classe Embedding
serve como uma classe base para inserções geradas por um IEmbeddingGenerator
. Ela foi criada para armazenar e gerenciar os metadados e os dados associados a inserções. Tipos derivados como Embedding<T>
fornecem os dados concretos de vetor de inserção. Por exemplo, uma incorporação expõe uma propriedade Embedding<T>.Vector para acessar seus dados de inserção.
A interface do IEmbeddingGenerator
define um método para gerar inserções de forma assíncrona para uma coleção de valores de entrada, com suporte opcional de configuração e cancelamento. Ela também fornece metadados que descrevem o gerador e permite a recuperação de serviços com tipo forte que podem ser fornecidos pelo gerador ou seus serviços subjacentes.
Exemplo de implementação
Considere o seguinte exemplo de implementação de um IEmbeddingGenerator
para mostrar a estrutura geral, mas isso só gera vetores de inserçã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 interface doIEmbeddingGenerator<string, Embedding<float>>
. - Tem um construtor primário que aceita um ponto de extremidade e uma ID de modelo, que são usados para identificar o gerador.
- Expõe uma propriedade de
Metadata
que fornece metadados sobre o gerador. - Implementa o método
GenerateAsync
para gerar inserções para um conjunto de valores de entrada:- Simula uma operação assíncrona com um atraso de 100 milissegundos.
- Retorna inserções aleatórias para cada valor de entrada.
Você pode encontrar implementações concretas reais nos seguintes pacotes:
Criar inserções
A operação primária executada com um IEmbeddingGenerator<TInput,TEmbedding> é a geração de incorporação, que é realizada com 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
Assim como acontece com IChatClient
, as implementações de IEmbeddingGenerator
podem ser em camadas. Assim como a biblioteca Microsoft.Extensions.AI
fornece a delegação de implementações de IChatClient
para armazenamento em cache e telemetria, ela 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 do IEmbeddingGenerator<TInput, TEmbedding>
que serve como uma classe base para a criação de geradores de inserção que delegam suas operações a outra instância do IEmbeddingGenerator<TInput, TEmbedding>
. Ele permite encadear vários geradores em qualquer ordem, transmitindo 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 do gerador interno, permitindo a geração de inserções flexíveis e modulares.
Veja a seguir um exemplo de implementação de um gerador de incorporação de delegação que limita a taxa de solicitações de geração de incorporação:
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);
}
}
Isso pode então ser colocado 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 incorporação 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 taxa.
Confira também
- Desenvolver aplicativos .NET com recursos de IA
- Blocos de construção de IA unificada para .NET usando Microsoft.Extensions.AI
- Construir um Aplicativo de Chat de IA com .NET
- Injeção de dependência do .NET
- Limitar a taxa de um manipulador HTTP no .NET
- Host Genérico .NET
- Como armazenar em cache no .NET