Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Os desenvolvedores do .NET precisam integrar e interagir com uma variedade crescente de serviços de IA (inteligência artificial) em seus aplicativos. As Microsoft.Extensions.AI
bibliotecas fornecem uma abordagem unificada para representar componentes de IA generativos e habilitam a integração e interoperabilidade perfeitas com vários serviços de IA. Este artigo apresenta as bibliotecas e fornece exemplos de uso detalhados para ajudá-lo a começar.
Os pacotes
O 📦 pacote Microsoft.Extensions.AI.Abstractions fornece os principais tipos de troca, incluindo IChatClient e IEmbeddingGenerator<TInput,TEmbedding>. Qualquer biblioteca .NET que forneça um cliente LLM pode implementar a interface IChatClient
para habilitar a integração perfeita com o código de consumo.
O pacote 📦 Microsoft.Extensions.AI tem uma dependência implícita no pacote Microsoft.Extensions.AI.Abstractions
. Esse pacote permite que você integre facilmente componentes como invocação automática de ferramenta de função, telemetria e cache em seus aplicativos usando padrões familiares de injeção de dependência e middleware. Por exemplo, ele fornece o método de extensão UseOpenTelemetry(ChatClientBuilder, ILoggerFactory, String, Action<OpenTelemetryChatClient>), que adiciona suporte ao OpenTelemetry ao pipeline do cliente do chat.
Qual pacote referenciar
Bibliotecas que fornecem implementações das abstrações normalmente fazem referência apenas Microsoft.Extensions.AI.Abstractions
.
Para também ter acesso a utilitários de nível superior para trabalhar com componentes de IA generativos, faça referência ao pacote Microsoft.Extensions.AI
em vez disso (que por sua vez faz referência ao Microsoft.Extensions.AI.Abstractions
). A maioria dos aplicativos e serviços consumidores deve fazer referência ao pacote Microsoft.Extensions.AI
juntamente com uma ou mais bibliotecas que fornecem implementações concretas das abstracções.
Instalar os pacotes
Para obter informações sobre como instalar pacotes NuGet, consulte adicionar pacote dotnet ou gerenciar dependências de pacote em aplicativos .NET.
Exemplos de uso de API
As seguintes subseções mostram exemplos de uso específicos do IChatClient :
- Solicitar uma resposta de chat
- Solicitar uma resposta de chat ao vivo
- Ferramenta de chamado
- Respostas de cache
- Usar telemetria
- Fornecer opções
- Pipelines de funcionalidade
- Middleware
IChatClient
personalizado - Injeção de dependência
- Clientes com estado versus sem estado
As seções a seguir mostram exemplos de uso específicos do IEmbeddingGenerator :
A interface IChatClient
.
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 permite recuperar serviços fortemente tipados fornecidos pelo cliente ou por seus serviços subjacentes.
As bibliotecas .NET que fornecem clientes para modelos e serviços de linguagem podem fornecer uma implementação da IChatClient
interface. Todos os consumidores da interface são capazes de interoperar perfeitamente com esses modelos e serviços por meio das abstrações. Você pode ver uma implementação simples em implementações de exemplo de IChatClient e IEmbeddingGenerator.
Solicitar uma resposta de chat
Com uma instância de IChatClient, você pode chamar o IChatClient.GetResponseAsync método para enviar uma solicitação e obter uma resposta. 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;
using OllamaSharp;
IChatClient client = new OllamaApiClient(
new Uri("http://localhost:11434/"), "phi3:mini");
Console.WriteLine(await client.GetResponseAsync("What is AI?"));
O método de IChatClient.GetResponseAsync
principal aceita uma lista de mensagens. Essa lista representa o histórico de todas as mensagens que fazem parte da conversa.
Console.WriteLine(await client.GetResponseAsync(
[
new(ChatRole.System, "You are a helpful AI assistant"),
new(ChatRole.User, "What is AI?"),
]));
O ChatResponse que é retornado de GetResponseAsync
expõe uma lista de instâncias ChatMessage que representam uma ou mais mensagens geradas como parte da operação. Em casos comuns, há apenas uma mensagem de resposta, mas em algumas situações, pode haver várias mensagens. A lista de mensagens é ordenada, de modo que a última mensagem na lista represente a mensagem final para a solicitação. Para fornecer todas essas mensagens de resposta de volta ao serviço em uma solicitação subsequente, você pode adicionar as mensagens da resposta de volta à lista de mensagens.
List<ChatMessage> history = [];
while (true)
{
Console.Write("Q: ");
history.Add(new(ChatRole.User, Console.ReadLine()));
ChatResponse response = await client.GetResponseAsync(history);
Console.WriteLine(response);
history.AddMessages(response);
}
Solicitar uma resposta de chat por transmissão ao vivo
As entradas de IChatClient.GetStreamingResponseAsync são idênticas às de GetResponseAsync
. No entanto, em vez de retornar a resposta completa como parte de um objeto ChatResponse, o método retorna um IAsyncEnumerable<T> em que T
é ChatResponseUpdate, fornecendo um fluxo de atualizações que formam coletivamente a resposta única.
await foreach (ChatResponseUpdate update in client.GetStreamingResponseAsync("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.
Assim como GetResponseAsync
, você pode adicionar as atualizações de IChatClient.GetStreamingResponseAsync de volta à lista de mensagens. Como as atualizações são partes individuais de uma resposta, você pode usar auxiliares como ToChatResponse(IEnumerable<ChatResponseUpdate>) para compor uma ou mais atualizações em uma única instância ChatResponse.
Auxiliares como AddMessages compõem um ChatResponse e, em seguida, extraem as mensagens compostas da resposta e as adicionam a uma lista.
List<ChatMessage> chatHistory = [];
while (true)
{
Console.Write("Q: ");
chatHistory.Add(new(ChatRole.User, Console.ReadLine()));
List<ChatResponseUpdate> updates = [];
await foreach (ChatResponseUpdate update in
client.GetStreamingResponseAsync(history))
{
Console.Write(update);
updates.Add(update);
}
Console.WriteLine();
chatHistory.AddMessages(updates);
}
Ferramenta de chamado
Alguns modelos e serviços dão suporte à chamada de ferramentas. Para coletar informações adicionais, você pode configurar o ChatOptions com informações sobre ferramentas (geralmente métodos .NET) que o modelo pode solicitar que o cliente invoque. 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 com o histórico de conversa. A biblioteca de Microsoft.Extensions.AI.Abstractions
inclui abstrações para vários tipos de conteúdo de mensagem, incluindo solicitações de chamada de função e resultados. Embora IChatClient
os consumidores possam interagir diretamente com esse conteúdo, Microsoft.Extensions.AI
fornece auxiliares que podem habilitar a invocação automática das ferramentas em resposta às solicitações correspondentes. As bibliotecas Microsoft.Extensions.AI.Abstractions
e Microsoft.Extensions.AI
fornecem os seguintes tipos:
- AIFunction: representa uma função que pode ser descrita para um modelo de IA e invocada.
- AIFunctionFactory: fornece métodos de fábrica para criar
AIFunction
instâncias que representam métodos .NET. - FunctionInvokingChatClient: encapsula um
IChatClient
como outroIChatClient
que adiciona recursos automáticos de invocação de função.
O exemplo a seguir demonstra uma invocação de função aleatória (este exemplo depende do pacote NuGet OllamaSharp📦):
using Microsoft.Extensions.AI;
using OllamaSharp;
string GetCurrentWeather() => Random.Shared.NextDouble() > 0.5 ? "It's sunny" : "It's raining";
IChatClient client = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1");
client = ChatClientBuilderChatClientExtensions
.AsBuilder(client)
.UseFunctionInvocation()
.Build();
ChatOptions options = new() { Tools = [AIFunctionFactory.Create(GetCurrentWeather)] };
var response = client.GetStreamingResponseAsync("Should I wear a rain coat?", options);
await foreach (var update in response)
{
Console.Write(update);
}
O código anterior:
- Define uma função chamada
GetCurrentWeather
que retorna uma previsão de tempo aleatória. - Cria uma instância de um ChatClientBuilder com um
OllamaSharp.OllamaApiClient
e o configura para usar a invocação de função. - Chama
GetStreamingResponseAsync
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 novo histórico de chat é 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. Na próxima vez que a mesma história for enviada, de modo que uma resposta armazenada em cache possa ser encontrada no cache, o DistributedCachingChatClient
retorna a resposta armazenada em cache em vez de encaminhar 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;
using OllamaSharp;
var sampleChatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1");
IChatClient client = new ChatClientBuilder(sampleChatClient)
.UseDistributedCache(new MemoryDistributedCache(
Options.Create(new MemoryDistributedCacheOptions())))
.Build();
string[] prompts = ["What is AI?", "What is .NET?", "What is AI?"];
foreach (var prompt in prompts)
{
await foreach (var update in client.GetStreamingResponseAsync(prompt))
{
Console.Write(update);
}
Console.WriteLine();
}
Este exemplo depende do 📦 pacote NuGet 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 abrange métricas e envolve outras implementações arbitrárias IChatClient
.
using Microsoft.Extensions.AI;
using OllamaSharp;
using OpenTelemetry.Trace;
// Configure OpenTelemetry exporter.
string sourceName = Guid.NewGuid().ToString();
TracerProvider tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
.AddSource(sourceName)
.AddConsoleExporter()
.Build();
IChatClient ollamaClient = new OllamaApiClient(
new Uri("http://localhost:11434/"), "phi3:mini");
IChatClient client = new ChatClientBuilder(ollamaClient)
.UseOpenTelemetry(
sourceName: sourceName,
configure: c => c.EnableSensitiveData = true)
.Build();
Console.WriteLine((await client.GetResponseAsync("What is AI?")).Text);
(O exemplo anterior depende do 📦 pacote NuGet OpenTelemetry.Exporter.Console .)
Como alternativa, o método LoggingChatClient e o método correspondente UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) oferecem uma maneira simples de registrar entradas de log em um ILogger para cada solicitação e resposta.
Fornecer opções
Cada chamada para GetResponseAsync ou GetStreamingResponseAsync 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 forma flexível, por meio do dicionário ChatOptions.AdditionalProperties ou por meio de uma instância de opções que o provedor subjacente entende, por meio da propriedade ChatOptions.RawRepresentationFactory.
Você também pode especificar opções ao criar um IChatClient
com a API fluente ChatClientBuilder encadeando uma chamada ao método de extensão ConfigureOptions(ChatClientBuilder, Action<ChatOptions>). 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;
using OllamaSharp;
IChatClient client = new OllamaApiClient(new Uri("http://localhost:11434"));
client = ChatClientBuilderChatClientExtensions.AsBuilder(client)
.ConfigureOptions(options => options.ModelId ??= "phi3")
.Build();
// Will request "phi3".
Console.WriteLine(await client.GetResponseAsync("What is AI?"));
// Will request "llama3.1".
Console.WriteLine(await client.GetResponseAsync("What is AI?", new() { ModelId = "llama3.1" }));
Pipelines de funcionalidade
As instâncias de IChatClient
podem ser organizadas em camadas para criar um pipeline de componentes que adicionam mais funcionalidades. 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 trecho de código que cria camadas de um cache distribuído, invocação de função e rastreamento OpenTelemetry sobre um cliente de chat de exemplo:
// Explore changing the order of the intermediate "Use" calls.
IChatClient client = new ChatClientBuilder(new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1"))
.UseDistributedCache(new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())))
.UseFunctionInvocation()
.UseOpenTelemetry(sourceName: sourceName, configure: c => c.EnableSensitiveData = true)
.Build();
Middleware IChatClient
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 GetResponseAsync
, GetStreamingResponseAsync
e Dispose
, que encaminham chamadas para o cliente interno. Uma classe derivada pode substituir apenas os métodos necessários para aumentar o comportamento, delegando outras chamadas à implementação base. Essa abordagem é útil para 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 da DelegatingChatClient
qual usa a biblioteca System.Threading.RateLimiting para fornecer funcionalidade de limitação de taxa.
using Microsoft.Extensions.AI;
using System.Runtime.CompilerServices;
using System.Threading.RateLimiting;
public sealed class RateLimitingChatClient(
IChatClient innerClient, RateLimiter rateLimiter)
: DelegatingChatClient(innerClient)
{
public override async Task<ChatResponse> GetResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
.ConfigureAwait(false);
if (!lease.IsAcquired)
throw new InvalidOperationException("Unable to acquire lease.");
return await base.GetResponseAsync(messages, options, cancellationToken)
.ConfigureAwait(false);
}
public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
.ConfigureAwait(false);
if (!lease.IsAcquired)
throw new InvalidOperationException("Unable to acquire lease.");
await foreach (var update in base.GetStreamingResponseAsync(messages, options, cancellationToken)
.ConfigureAwait(false))
{
yield return update;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
rateLimiter.Dispose();
base.Dispose(disposing);
}
}
Assim como acontece com outras IChatClient
implementações, ela RateLimitingChatClient
pode ser composta:
using Microsoft.Extensions.AI;
using OllamaSharp;
using System.Threading.RateLimiting;
var client = new RateLimitingChatClient(
new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1"),
new ConcurrencyLimiter(new() { PermitLimit = 1, QueueLimit = int.MaxValue }));
Console.WriteLine(await client.GetResponseAsync("What color is the sky?"));
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 UseRatingLimiting
método de extensão:
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)
);
}
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:
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.RateLimiting;
public static class RateLimitingChatClientExtensions
{
public static ChatClientBuilder UseRateLimiting(
this ChatClientBuilder builder,
RateLimiter? rateLimiter = null) =>
builder.Use((innerClient, services) =>
new RateLimitingChatClient(
innerClient,
services.GetRequiredService<RateLimiter>())
);
}
Agora é fácil para o consumidor integrar isso em seu processo, por exemplo:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
IChatClient client = new OllamaApiClient(
new Uri("http://localhost:11434/"),
"phi3:mini");
builder.Services.AddChatClient(services =>
client
.AsBuilder()
.UseDistributedCache()
.UseRateLimiting()
.UseOpenTelemetry()
.Build(services));
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. Por exemplo, no exemplo anterior de RateLimitingChatClient
, as sobrescrições de GetResponseAsync
e GetStreamingResponseAsync
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 GetResponseAsync
quanto para GetStreamingResponseAsync
, reduzindo o código repetitivo necessário.
using Microsoft.Extensions.AI;
using OllamaSharp;
using System.Threading.RateLimiting;
RateLimiter rateLimiter = new ConcurrencyLimiter(new()
{
PermitLimit = 1,
QueueLimit = int.MaxValue
});
IChatClient client = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1");
client = ChatClientBuilderChatClientExtensions
.AsBuilder(client)
.UseDistributedCache()
.Use(async (messages, options, nextAsync, cancellationToken) =>
{
using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken).ConfigureAwait(false);
if (!lease.IsAcquired)
throw new InvalidOperationException("Unable to acquire lease.");
await nextAsync(messages, options, cancellationToken);
})
.UseOpenTelemetry()
.Build();
Para cenários em que você precisa de uma implementação diferente para GetResponseAsync
e GetStreamingResponseAsync
para lidar com seus tipos de retorno exclusivos, você pode usar a sobrecarga Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken,
Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions,
IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>) que aceita um delegado para cada um.
Injeção de dependência
IChatClientas implementações geralmente são fornecidas a um aplicativo por meio de DI (injeção de dependência). Neste exemplo, um IDistributedCache é adicionado ao contêiner de DI, assim como um IChatClient
. O registro do IChatClient
usa um construtor que cria um pipeline que contém um cliente de cache (que usa 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;
using OllamaSharp;
// App setup.
var builder = Host.CreateApplicationBuilder();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddChatClient(new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.1"))
.UseDistributedCache();
var host = builder.Build();
// Elsewhere in the app.
var chatClient = host.Services.GetRequiredService<IChatClient>();
Console.WriteLine(await chatClient.GetResponseAsync("What is AI?"));
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.
Clientes com estado versus sem estado
Os serviços sem estado exigem que todo o histórico de conversas relevante seja enviado de volta em cada solicitação. Por outro lado, os serviços com estado controlam o histórico e exigem que apenas mensagens adicionais sejam enviadas com uma solicitação. A interface IChatClient foi projetada para lidar com serviços de IA sem estado e com suporte a estado.
Ao trabalhar com um serviço sem estado, os chamadores mantêm uma lista de todas as mensagens. Eles adicionam todas as mensagens de resposta recebidas e fornecem a lista de volta nas interações subsequentes.
List<ChatMessage> history = [];
while (true)
{
Console.Write("Q: ");
history.Add(new(ChatRole.User, Console.ReadLine()));
var response = await client.GetResponseAsync(history);
Console.WriteLine(response);
history.AddMessages(response);
}
Para serviços com estado, talvez você já conheça o identificador usado para a conversa relevante. Você pode colocar esse identificador em ChatOptions.ConversationId. Em seguida, o uso segue o mesmo padrão, exceto que não é necessário manter um histórico manualmente.
ChatOptions statefulOptions = new() { ConversationId = "my-conversation-id" };
while (true)
{
Console.Write("Q: ");
ChatMessage message = new(ChatRole.User, Console.ReadLine());
Console.WriteLine(await client.GetResponseAsync(message, statefulOptions));
}
Alguns serviços podem dar suporte à criação automática de uma ID de conversa para uma solicitação que não tenha uma ou à criação de uma nova ID de conversa que represente o estado atual da conversa depois de incorporar a última rodada de mensagens. Nesses casos, você pode transferir o ChatResponse.ConversationId para as ChatOptions.ConversationId
para solicitações subsequentes. Por exemplo:
ChatOptions options = new();
while (true)
{
Console.Write("Q: ");
ChatMessage message = new(ChatRole.User, Console.ReadLine());
ChatResponse response = await client.GetResponseAsync(message, options);
Console.WriteLine(response);
options.ConversationId = response.ConversationId;
}
Se você não souber antecipadamente se o serviço é sem estado ou com estado, você pode verificar a resposta ConversationId e agir com base em seu valor. Se estiver definido, esse valor será propagado para as opções e o histórico será limpo para não reenviar o mesmo histórico novamente. Se a resposta ConversationId
não estiver definida, a mensagem de resposta será adicionada ao histórico para que ela seja enviada de volta ao serviço na próxima curva.
List<ChatMessage> chatHistory = [];
ChatOptions chatOptions = new();
while (true)
{
Console.Write("Q: ");
chatHistory.Add(new(ChatRole.User, Console.ReadLine()));
ChatResponse response = await client.GetResponseAsync(chatHistory);
Console.WriteLine(response);
chatOptions.ConversationId = response.ConversationId;
if (response.ConversationId is not null)
{
chatHistory.Clear();
}
else
{
chatHistory.AddMessages(response);
}
}
A interface IEmbeddingGenerator
.
A interface do IEmbeddingGenerator<TInput,TEmbedding> representa um gerador genérico de inserções. Para os parâmetros de tipo genérico, TInput
é o tipo de valores de entrada que estão sendo inseridos e TEmbedding
é o tipo de inserção gerada, que herda da Embedding classe.
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 Embedding<float>
expõe uma ReadOnlyMemory<float> Vector { get; }
propriedade para acesso aos 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.
A maioria dos usuários não precisa implementar a IEmbeddingGenerator
interface. No entanto, se você for um autor de biblioteca, poderá ver uma implementação simples em implementações de exemplo de IChatClient e IEmbeddingGenerator.
Criar inserções
A operação primária executada com um IEmbeddingGenerator<TInput,TEmbedding> é a geração de embeddings, realizada com seu método GenerateAsync.
using Microsoft.Extensions.AI;
using OllamaSharp;
IEmbeddingGenerator<string, Embedding<float>> generator =
new OllamaApiClient(new Uri("http://localhost:11434/"), "phi3:mini");
foreach (Embedding<float> embedding in
await generator.GenerateAsync(["What is AI?", "What is .NET?"]))
{
Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}
Também existem métodos de extensão de acelerador para simplificar casos comuns, como a geração de um vetor de incorporação a partir de uma única entrada.
ReadOnlyMemory<float> vector = await generator.GenerateVectorAsync("What is AI?");
Pipelines de funcionalidade
Assim como acontece com IChatClient
, as implementações de IEmbeddingGenerator
podem ser em camadas. Microsoft.Extensions.AI
fornece uma implementação de delegação para IEmbeddingGenerator
para cache e telemetria.
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using OllamaSharp;
using OpenTelemetry.Trace;
// Configure OpenTelemetry exporter
string sourceName = Guid.NewGuid().ToString();
TracerProvider tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
.AddSource(sourceName)
.AddConsoleExporter()
.Build();
// Explore changing the order of the intermediate "Use" calls to see
// what impact that has on what gets cached and traced.
IEmbeddingGenerator<string, Embedding<float>> generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(
new OllamaApiClient(new Uri("http://localhost:11434/"), "phi3:mini"))
.UseDistributedCache(
new MemoryDistributedCache(
Options.Create(new MemoryDistributedCacheOptions())))
.UseOpenTelemetry(sourceName: sourceName)
.Build();
GeneratedEmbeddings<Embedding<float>> embeddings = await generator.GenerateAsync(
[
"What is AI?",
"What is .NET?",
"What is AI?"
]);
foreach (Embedding<float> embedding in embeddings)
{
Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}
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.
using Microsoft.Extensions.AI;
using OllamaSharp;
using System.Threading.RateLimiting;
IEmbeddingGenerator<string, Embedding<float>> generator =
new RateLimitingEmbeddingGenerator(
new OllamaApiClient(new Uri("http://localhost:11434/"), "phi3:mini"),
new ConcurrencyLimiter(new()
{
PermitLimit = 1,
QueueLimit = int.MaxValue
}));
foreach (Embedding<float> embedding in
await generator.GenerateAsync(["What is AI?", "What is .NET?"]))
{
Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}
Dessa forma, o RateLimitingEmbeddingGenerator
pode ser composto com outras instâncias IEmbeddingGenerator<string, Embedding<float>>
para fornecer funcionalidade de limitação de taxa.
Crie com a Microsoft.Extensions.AI
Você pode começar a criar com a Microsoft.Extensions.AI
das seguintes maneiras:
- Desenvolvedores de biblioteca: se você possui bibliotecas que fornecem clientes para serviços de IA, considere implementar as interfaces em suas bibliotecas. Isso permite que os usuários integrem facilmente seu pacote NuGet por meio das abstrações. Para obter implementações de exemplo, consulte implementações de exemplo de IChatClient e IEmbeddingGenerator.
- Consumidores de serviços: Se você estiver desenvolvendo bibliotecas que consomem serviços de IA, use as abstrações em vez de codificar para um serviço de IA específico. Essa abordagem oferece aos consumidores a flexibilidade para escolher seu provedor preferencial.
- Desenvolvedores de aplicativos: use as abstrações para simplificar a integração em seus aplicativos. Isso permite a portabilidade entre modelos e serviços, facilita o teste e a simulação, aproveita o middleware fornecido pelo ecossistema e mantém uma API consistente em todo o aplicativo, mesmo que você use serviços diferentes em diferentes partes do aplicativo.
- Colaboradores do ecossistema: se você estiver interessado em contribuir para o ecossistema, considere escrever componentes de middleware personalizados.
Para obter mais exemplos, consulte o repositório GitHub dotnet/ai-samples . Para obter um exemplo de ponta a ponta, consulte eShopSupport.