Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Разработчикам .NET нужен способ интеграции и взаимодействия с растущими различными службами искусственного интеллекта (ИИ) в своих приложениях. Библиотеки Microsoft.Extensions.AI
обеспечивают унифицированный подход для представления компонентов генерированного искусственного интеллекта и обеспечивает простую интеграцию и взаимодействие с различными службами ИИ. В этой статье представлены библиотеки и приведены подробные примеры использования, которые помогут вам приступить к работе.
Пакеты
Пакет 📦 Microsoft.Extensions.AI.Abstractions предоставляет основные типы обмена: IChatClient и IEmbeddingGenerator<TInput,TEmbedding>. Любая библиотека .NET, предоставляющая клиент ИИ, может реализовать интерфейс IChatClient
, чтобы обеспечить простую интеграцию с потребляющим кодом.
Пакет 📦 Microsoft.Extensions.AI имеет неявную зависимость от пакета Microsoft.Extensions.AI.Abstractions
. Этот пакет позволяет легко интегрировать такие компоненты, как телеметрия и кэширование, в ваши приложения, используя знакомые шаблоны внедрения зависимостей и промежуточного слоя. Например, он предоставляет метод расширения UseOpenTelemetry(ChatClientBuilder, ILoggerFactory, String, Action<OpenTelemetryChatClient>), который добавляет поддержку OpenTelemetry в клиентский конвейер чата.
На какой пакет следует ссылаться
Библиотеки, которые предоставляют реализации абстракций, обычно ссылаются только на Microsoft.Extensions.AI.Abstractions
.
Чтобы также иметь доступ к высокоуровневым служебным программам для работы с компонентами создания ИИ, используйте пакет Microsoft.Extensions.AI
(который сам ссылается на Microsoft.Extensions.AI.Abstractions
). Большинство используемых приложений и служб должны ссылаться на Microsoft.Extensions.AI
пакет вместе с одной или несколькими библиотеками, предоставляющими конкретные реализации абстракций.
Установка пакетов
Сведения об установке пакетов NuGet см. в разделе "Добавление пакетов dotnet " или "Управление зависимостями пакетов" в приложениях .NET.
Примеры использования API
В следующих подразделах показаны конкретные примеры использования IChatClient :
- Запрос ответа чата
- Запросить ответ в потоковом чате
- вызов инструмента
- кешированные ответы
- Использовать телеметрию
- Предоставьте опции
- Конвейеры функциональных возможностей
-
Пользовательское промежуточное ПО
IChatClient
промежуточное ПО - Внедрение зависимостей
- Статические и динамические клиенты
В следующих разделах показаны конкретные примеры использования IEmbeddingGenerator :
Интерфейс IChatClient
Интерфейс IChatClient определяет абстракцию клиента, отвечающую за взаимодействие со службами ИИ, предоставляющими возможности чата. Он включает методы отправки и получения сообщений с мультимодальным содержимым (например, текстом, изображениями и звуком), либо как полный набор, либо передача потоком. Кроме того, он позволяет получать строго типизированные службы, предоставляемые клиентом или его базовыми службами.
Библиотеки .NET, предоставляющие клиентам языковые модели и службы, могут предоставлять реализацию IChatClient
интерфейса. Затем любые потребители интерфейса могут легко взаимодействовать с этими моделями и службами через абстракции.
Запросить ответ в чате
С помощью экземпляра IChatClientможно вызвать IChatClient.GetResponseAsync метод для отправки запроса и получения ответа. Запрос состоит из одного или нескольких сообщений, каждый из которых состоит из одного или нескольких частей содержимого. Методы акселератора существуют для упрощения распространенных случаев, таких как создание запроса для одного фрагмента текстового содержимого.
using Microsoft.Extensions.AI;
IChatClient client = new SampleChatClient(
new Uri("http://coolsite.ai"), "target-ai-model");
Console.WriteLine(await client.GetResponseAsync("What is AI?"));
Основной метод IChatClient.GetResponseAsync
принимает список сообщений. Этот список представляет историю всех сообщений, входящих в беседу.
Console.WriteLine(await client.GetResponseAsync(
[
new(ChatRole.System, "You are a helpful AI assistant"),
new(ChatRole.User, "What is AI?"),
]));
Возвращаемый из GetResponseAsync
ChatResponse предоставляет список экземпляров ChatMessage, представляющих одно или несколько сообщений, созданных в рамках операции. В распространенных случаях существует только одно сообщение ответа, но в некоторых ситуациях может быть несколько сообщений. Список сообщений упорядочивается таким образом, чтобы последнее сообщение в списке представляло окончательное сообщение запросу. Чтобы предоставить все эти ответные сообщения обратно в службу в последующем запросе, можно добавить сообщения из ответа обратно в список сообщений.
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);
}
Запросить ответ в потоковом чате.
Входные данные для IChatClient.GetStreamingResponseAsync идентичны GetResponseAsync
. Однако вместо возврата полного ответа в рамках объекта ChatResponse метод возвращает IAsyncEnumerable<T>, где T
ChatResponseUpdate, предоставляя поток обновлений, которые коллективно формируют единый ответ.
await foreach (ChatResponseUpdate update in client.GetStreamingResponseAsync("What is AI?"))
{
Console.Write(update);
}
Подсказка
API потоковой передачи почти синонимы пользовательского интерфейса искусственного интеллекта. C# обеспечивает убедительные сценарии с поддержкой IAsyncEnumerable<T>
, что позволяет обеспечить естественный и эффективный способ потоковой передачи данных.
Как и в случае с GetResponseAsync
, можно добавить обновления из IChatClient.GetStreamingResponseAsync к списку сообщений. Так как обновления являются отдельными частями ответа, вы можете использовать вспомогательные средства, такие как ToChatResponse(IEnumerable<ChatResponseUpdate>), чтобы составлять одно или несколько обновлений в одном экземпляре ChatResponse.
Вспомогательные функции, такие как AddMessages, компонуют ChatResponse, затем извлекают составленные сообщения из ответа и добавляют их в список.
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);
}
Console.WriteLine();
chatHistory.AddMessages(updates);
}
Запуск инструмента
Некоторые модели и службы поддерживают вызов средства. Чтобы собрать дополнительные сведения, можно настроить ChatOptions с информацией о средствах (обычно методах .NET), чтобы модель могла запрашивать их выполнение клиентом. Вместо отправки окончательного ответа модель запрашивает вызов функции с определенными аргументами. Затем клиент вызывает функцию и отправляет результаты обратно в модель с журналом бесед. Библиотека Microsoft.Extensions.AI
включает абстракции для различных типов контента сообщений, включая запросы и результаты вызова функции. Хотя IChatClient
потребители могут взаимодействовать с этим содержимым напрямую, Microsoft.Extensions.AI
автоматически автоматизирует эти взаимодействия. Он предоставляет следующие типы:
- AIFunction: представляет функцию, которую можно описать в модели ИИ и вызвать.
-
AIFunctionFactory: предоставляет методы для создания фабричных объектов, представляющих методы .NET
AIFunction
. -
FunctionInvokingChatClient: упаковывает функцию
IChatClient
для добавления возможностей автоматического вызова функций.
В следующем примере показано вызов случайной функции (этот пример зависит от 📦 пакета Microsoft.Extensions.AI.Ollama NuGet):
using Microsoft.Extensions.AI;
string GetCurrentWeather() => Random.Shared.NextDouble() > 0.5 ? "It's sunny" : "It's raining";
IChatClient client = new OllamaChatClient(new Uri("http://localhost:11434"), "llama3.1")
.AsBuilder()
.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);
}
Предыдущий код:
- Определяет функцию с именем
GetCurrentWeather
, которая возвращает случайный прогноз погоды. - Создает экземпляр ChatClientBuilder, используя OllamaChatClient, и настраивает его для использования вызова функции.
- Вызывает
GetStreamingResponseAsync
на клиенте, передавая подсказку и список инструментов, включающих функцию, созданную с Create. - Выполняет итерацию по ответу, выводя каждое обновление на консоль.
Ответы из кэша
Если вы знакомы с кэшированием в .NET, полезно знать, что Microsoft.Extensions.AI предоставляет другие такие делегирующие реализации IChatClient
.
DistributedCachingChatClient — это IChatClient
, которая накладывает кэширование на другой произвольный экземпляр IChatClient
. Когда журнальная запись нового чата передается в DistributedCachingChatClient
, она пересылается базовому клиенту, затем ответ кэшируется и отправляется обратно пользователю. При следующей отправке такого же запроса, когда можно будет найти кэшированный ответ в кэше, DistributedCachingChatClient
возвращает кэшированный ответ, вместо переадресации запроса по конвейеру.
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
var sampleChatClient = new OllamaChatClient(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();
}
Этот пример зависит от 📦 пакета NuGet Microsoft.Extensions.Caching.Memory . Дополнительные сведения см. в разделе Кэширование в.NET.
Использование телеметрии
Еще одним примером делегированного клиента чата является OpenTelemetryChatClient. Эта реализация соответствует семантическим конвенциям OpenTelemetry для генеративных систем ИИ. Аналогично другим IChatClient
делегаторам, он добавляет слои метрик и окружает произвольные IChatClient
реализации.
using Microsoft.Extensions.AI;
using OpenTelemetry.Trace;
// Configure OpenTelemetry exporter.
string sourceName = Guid.NewGuid().ToString();
TracerProvider 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: c => c.EnableSensitiveData = true)
.Build();
Console.WriteLine((await client.GetResponseAsync("What is AI?")).Text);
(Предыдущий пример зависит от 📦 пакета NuGet OpenTelemetry.Exporter.Console .)
Кроме того, LoggingChatClient и соответствующий UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) метод предоставляют простой способ записи записей журнала для ILogger каждого запроса и ответа.
Предоставление параметров
Каждый вызов GetResponseAsync или GetStreamingResponseAsync может опционально включать экземпляр ChatOptions, содержащий дополнительные параметры для операции. Наиболее распространенные параметры моделей и служб ИИ отображаются как строго типизированные свойства типа, например ChatOptions.Temperature. Другие параметры могут быть указаны по имени в слаботипизированном виде через словарь ChatOptions.AdditionalProperties.
Кроме того, можно указать параметры при создании IChatClient
при помощи API ChatClientBuilder, использующего fluent-интерфейс, связав вызов метода расширения ConfigureOptions(ChatClientBuilder, Action<ChatOptions>). Этот клиент делегирования оборачивает другого клиента и вызывает предоставленный делегат, чтобы заполнить экземпляр ChatOptions
при каждом вызове. Например, чтобы убедиться, что свойство ChatOptions.ModelId по умолчанию имеет определенное имя модели, можно использовать следующий код:
using Microsoft.Extensions.AI;
IChatClient client = new OllamaChatClient(new Uri("http://localhost:11434"))
.AsBuilder()
.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" }));
Цепочки функциональности
IChatClient
экземпляры можно располагать слоями, чтобы создать конвейер из компонентов, каждый из которых добавляет дополнительную функциональность. Эти компоненты могут поступать из Microsoft.Extensions.AI
, других пакетов NuGet или пользовательских реализаций. Этот подход позволяет расширить поведение IChatClient
различными способами для удовлетворения конкретных потребностей. Рассмотрим следующий фрагмент кода, который накладывает уровни распределенного кэша, вызова функций и трассировки OpenTelemetry на пример клиента чата.
// Explore changing the order of the intermediate "Use" calls.
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: c => c.EnableSensitiveData = true)
.Build();
Настраиваемое промежуточное программное обеспечение IChatClient
Чтобы добавить дополнительные функциональные возможности, можно реализовать IChatClient
напрямую или использовать класс DelegatingChatClient. Этот класс служит основой для создания клиентов чата, которые делегируют операции другому экземпляру класса IChatClient
. Это упрощает последовательное соединение нескольких клиентов, позволяя вызовам напрямую передаваться в базовый клиент.
Класс DelegatingChatClient
предоставляет реализации по умолчанию для таких методов, как GetResponseAsync
, GetStreamingResponseAsync
и Dispose
, которые перенаправяют вызовы внутреннего клиента. Затем производный класс может переопределить только те методы, поведение которых необходимо расширить, при этом делегируя другие вызовы базовой реализации. Этот подход полезен для создания гибких и модульных клиентов чата, которые легко расширить и создать.
Ниже приведен пример класса, производный от DelegatingChatClient
, который использует библиотеку System.Threading.RateLimiting для предоставления функциональности ограничения скорости.
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);
}
}
Как и в других IChatClient
реализациях, RateLimitingChatClient
можно компоновать.
using Microsoft.Extensions.AI;
using System.Threading.RateLimiting;
var client = new RateLimitingChatClient(
new OllamaChatClient(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?"));
Чтобы упростить интеграцию таких компонентов с другими, разработчики компонентов должны создавать расширяющий метод Use*
для регистрации компонента в конвейере. Например, рассмотрим следующий UseRatingLimiting
метод расширения:
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)
);
}
Такие расширения также могут запрашивать соответствующие службы из контейнера DI; IServiceProvider, используемый конвейером, передается как необязательный параметр:
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>())
);
}
Теперь потребителю легко использовать это в конвейере, например:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddChatClient(services =>
new SampleChatClient(new Uri("http://localhost"), "test")
.AsBuilder()
.UseDistributedCache()
.UseRateLimiting()
.UseOpenTelemetry()
.Build(services));
Предыдущие методы расширения демонстрируют использование метода Use
на ChatClientBuilder.
ChatClientBuilder
также предоставляет Use перегрузки, которые упрощают создание таких делегирующих обработчиков. Например, в предыдущем RateLimitingChatClient
примере переопределения для GetResponseAsync
и GetStreamingResponseAsync
необходимо выполнить только те действия, которые выполняются до и после делегирования следующему клиенту в конвейере. Чтобы добиться того же, не создавая пользовательский класс, можно использовать перегрузку Use
, которая принимает делегат, используемый как для GetResponseAsync
, так и для GetStreamingResponseAsync
, уменьшая объём шаблонного кода.
using Microsoft.Extensions.AI;
using System.Threading.RateLimiting;
RateLimiter rateLimiter = new ConcurrencyLimiter(new()
{
PermitLimit = 1,
QueueLimit = int.MaxValue
});
IChatClient client = new OllamaChatClient(new Uri("http://localhost:11434"), "llama3.1")
.AsBuilder()
.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();
В сценариях, где требуется другая реализация для GetResponseAsync
и GetStreamingResponseAsync
для их уникальных типов возвращаемых данных, можно использовать Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken,
Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions,
IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>) перегрузку, которая принимает делегат для каждого из них.
Внедрение зависимостей
IChatClientреализации часто предоставляются приложению с помощью внедрения зависимостей (DI). В этом примере в контейнер DI добавляется IDistributedCache, как и IChatClient
. Регистрация IChatClient
использует конструктор, создающий конвейер, содержащий клиент кэширования (который затем использует извлеченный IDistributedCache
из DI) и образец клиента. Внедренный IChatClient
можно получить и использовать в другом месте в приложении.
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 OllamaChatClient(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?"));
Какие экземпляры и конфигурации внедряются, могут отличаться в зависимости от текущих потребностей приложения, а несколько конвейеров можно внедрить с разными ключами.
Клиенты без состояния и клиенты с состоянием
Службы без отслеживания состояния требуют отправки всех соответствующих журналов бесед по каждому запросу. Напротив, сервисы с сохранением состояния хранят историю и требуют отправки только дополнительных сообщений с запросом. Интерфейс IChatClient предназначен для обеспечения поддержки как бесстатусных, так и с состоянием служб искусственного интеллекта.
При работе со службой без состояния пользователи поддерживают список всех сообщений. Они включают все полученные сообщения ответа и предоставляют список при последующих взаимодействиях.
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);
}
Для служб с отслеживанием состояния может быть уже известно идентификатор, используемый для соответствующей беседы. Этот идентификатор можно поместить в ChatOptions.ChatThreadId. Затем использование следует той же схеме, за исключением необходимости вручную вести историю.
ChatOptions statefulOptions = new() { ChatThreadId = "my-conversation-id" };
while (true)
{
Console.Write("Q: ");
ChatMessage message = new(ChatRole.User, Console.ReadLine());
Console.WriteLine(await client.GetResponseAsync(message, statefulOptions));
}
Некоторые службы могут поддерживать автоматическое создание идентификатора потока для запроса, который не имеет одного. В таких случаях можно передать ChatResponse.ChatThreadId на ChatOptions.ChatThreadId
для последующих запросов. Рассмотрим пример.
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.ChatThreadId = response.ChatThreadId;
}
Если вы не знаете заранее, является ли служба бессостоянием или с состоянием, можно проверить ответ ChatThreadId и принять меры на основе его значения. Если установлено, это значение распространяется на опции и история очищается, чтобы не отправить её повторно. Если ответ ChatThreadId
не установлен, то сообщение ответа добавляется в журнал, чтобы оно было отправлено обратно в службу при следующем обращении.
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.ChatThreadId = response.ChatThreadId;
if (response.ChatThreadId is not null)
{
chatHistory.Clear();
}
else
{
chatHistory.AddMessages(response);
}
}
Интерфейс IEmbeddingGenerator
Интерфейс IEmbeddingGenerator<TInput,TEmbedding> представляет универсальный генератор эмбеддингов. Здесь TInput
— это тип внедренных входных значений, а TEmbedding
— это тип созданного внедрения, который наследует от класса Embedding.
Класс Embedding
служит базовым классом для внедрений, созданных IEmbeddingGenerator
. Он предназначен для хранения метаданных и данных, связанных с внедрением, и управления ими. Производные типы, такие как Embedding<T>
, предоставляют конкретные данные векторов вложения. Например, объект Embedding<float>
предоставляет свойство ReadOnlyMemory<float> Vector { get; }
для доступа к его встроенным данным.
Интерфейс IEmbeddingGenerator
определяет метод асинхронной генерации векторных представлений для коллекции входных значений с поддержкой необязательной конфигурации и возможности отмены. Он также предоставляет метаданные, описывающие генератор, и позволяет получить строго типизированные службы, которые могут быть предоставлены генератором или ее базовыми службами.
Пример реализации
В следующем примере реализации IEmbeddingGenerator
показана общая структура.
using Microsoft.Extensions.AI;
public sealed class SampleEmbeddingGenerator(
Uri endpoint, string modelId)
: IEmbeddingGenerator<string, Embedding<float>>
{
private readonly EmbeddingGeneratorMetadata _metadata =
new("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 new GeneratedEmbeddings<Embedding<float>>(
from value in values
select new Embedding<float>(
Enumerable.Range(0, 384).Select(_ => Random.Shared.NextSingle()).ToArray()));
}
public object? GetService(Type serviceType, object? serviceKey) =>
serviceKey is not null
? null
: serviceType == typeof(EmbeddingGeneratorMetadata)
? _metadata
: serviceType?.IsInstanceOfType(this) is true
? this
: null;
void IDisposable.Dispose() { }
}
Предыдущий код:
- Определяет класс с именем
SampleEmbeddingGenerator
, реализующий интерфейсIEmbeddingGenerator<string, Embedding<float>>
. - Имеет основной конструктор, принимающий конечную точку и идентификатор модели, которые используются для идентификации генератора.
- Метод
GenerateAsync
реализует генерацию встраиваний для коллекции входных значений.
Пример реализации просто создает случайные векторы внедрения. Фактические конкретные реализации можно найти в следующих пакетах:
Создание встраиваний
Основная операция, выполняемая с IEmbeddingGenerator<TInput,TEmbedding>, — генерация внедрений, которая осуществляется с помощью метода GenerateAsync.
using Microsoft.Extensions.AI;
IEmbeddingGenerator<string, Embedding<float>> generator =
new SampleEmbeddingGenerator(
new Uri("http://coolsite.ai"), "target-ai-model");
foreach (Embedding<float> embedding in
await generator.GenerateAsync(["What is AI?", "What is .NET?"]))
{
Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}
Методы расширения акселератора также существуют для упрощения распространенных случаев, таких как создание вектора внедрения из одного входного ввода.
ReadOnlyMemory<float> vector = await generator.GenerateEmbeddingVectorAsync("What is AI?");
Конвейеры функциональных возможностей
Как и в случае с IChatClient
, реализации IEmbeddingGenerator
могут быть многослойными.
Microsoft.Extensions.AI
предоставляет делегированную реализацию 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
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 SampleEmbeddingGenerator(new Uri("http://coolsite.ai"), "target-ai-model"))
.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()));
}
IEmbeddingGenerator
позволяет создавать пользовательские решения на основе промежуточного программного обеспечения, расширяющие функциональные возможности IEmbeddingGenerator
. Класс DelegatingEmbeddingGenerator<TInput,TEmbedding> — это реализация интерфейса IEmbeddingGenerator<TInput, TEmbedding>
, который служит базовым классом для создания генераторов внедрения, которые делегируют свои операции другому экземпляру IEmbeddingGenerator<TInput, TEmbedding>
. Он позволяет связывать несколько генераторов в любом порядке, передавая вызовы через базовый генератор. Класс предоставляет реализации по умолчанию для таких методов, как GenerateAsync и Dispose
, которые перенаправляют вызовы к внутреннему экземпляру генератора, обеспечивая гибкую и модульную генерацию вложений.
Ниже приведен пример реализации такого делегированного генератора встраивания, который ограничивает количество запросов на создание встраивания.
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);
}
}
Затем это можно уложить слоями вокруг произвольного IEmbeddingGenerator<string, Embedding<float>>
для ограничения скорости выполнения всех операций генерации встраивания.
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 (Embedding<float> embedding in
await generator.GenerateAsync(["What is AI?", "What is .NET?"]))
{
Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}
Таким образом, RateLimitingEmbeddingGenerator
можно комбинировать с другими экземплярами IEmbeddingGenerator<string, Embedding<float>>
, чтобы обеспечить функциональность ограничения скорости.
Создание с использованием Microsoft.Extensions.AI
Вы можете начать сборку следующими способами с Microsoft.Extensions.AI
:
- Разработчики библиотек: если вы владеете библиотеками, предоставляющими клиенты для служб ИИ, рассмотрите возможность реализации интерфейсов в библиотеках. Это позволяет пользователям легко интегрировать пакет NuGet с помощью абстракций.
- Потребители служб: если вы разрабатываете библиотеки, использующие службы искусственного интеллекта, используйте абстракции вместо жесткой кодировки для конкретной службы ИИ. Такой подход дает потребителям гибкость выбора предпочитаемой службы.
- Разработчики приложений: используйте абстракции, чтобы упростить интеграцию с приложениями. Это обеспечивает переносимость между моделями и службами, упрощает тестирование и мокирование, использует промежуточное ПО, предоставляемое экосистемой, и поддерживает единый API во всем вашем приложении, даже если вы используете различные службы в разных частях приложения.
- Участники экосистемы: если вы заинтересованы во вкладе в экосистему, рассмотрите возможность создания пользовательских промежуточных компонентов.
Дополнительные примеры см. в репозитории GitHub dotnet/ai-samples . Полный пример см. в разделе eShopSupport.