Искусственный интеллект в .NET (предварительная версия)
Благодаря растущему спектру доступных служб искусственного интеллекта разработчики должны интегрировать и взаимодействовать с этими службами в своих приложениях .NET. Библиотека Microsoft.Extensions.AI
предоставляет унифицированный подход к представлению компонентов сгенерированного ИИ, что обеспечивает непрерывную интеграцию и взаимодействие с различными службами ИИ. В этой статье представлены библиотеки и приведены инструкции по установке и примеры использования, которые помогут вам приступить к работе.
Установка пакета
Чтобы установить пакет NuGet 📦 Microsoft.Extensions.AI, используйте интерфейс командной строки .NET или добавьте ссылку на пакет непосредственно в файл проекта C#:
dotnet add package Microsoft.Extensions.AI --prerelease
Дополнительные сведения см. в статье dotnet add package или Управление зависимостями пакетов в приложениях .NET.
Примеры использования
Интерфейс IChatClient определяет абстракцию клиента, отвечающую за взаимодействие со службами ИИ, предоставляющими возможности чата. Он включает методы отправки и получения сообщений с мультимодальным содержимым (например, текстом, изображениями и звуком), либо как полный набор, либо передача потоком. Кроме того, он предоставляет сведения о метаданных клиента и позволяет получать строго типизированные сервисы.
Важный
Дополнительные примеры использования и реальные сценарии см. в разделе «ИИ для разработчиков .NET».
В этом разделе
-
IChatClient
интерфейс- Запрос завершения чата
- Запрос на завершение чата с использованием потоковой передачи
- вызов инструмента
- кешированные ответы
- Использовать телеметрию
- Предоставьте опции
- конвейеры функциональных возможностей
-
Пользовательское промежуточное ПО
IChatClient
промежуточное ПО - Внедрение зависимостей
-
IEmbeddingGenerator
интерфейс
Интерфейс IChatClient
Следующий пример реализует IChatClient
для отображения общей структуры.
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() { }
}
Другие конкретные реализации IChatClient
можно найти в следующих пакетах NuGet:
- 📦 Microsoft.Extensions.AI.AzureAIInference: реализация, поддерживаемая API вывода моделей ИИ Azure.
- 📦 Microsoft.Extensions.AI.Ollama: реализация, поддерживаемая Ollama.
- 📦 Microsoft.Extensions.AI.OpenAI: реализация, поддерживаемая конечными точками OpenAI или совместимыми с OpenAI, такими как Azure OpenAI.
Запрос завершения чата
Чтобы запросить завершение, вызовите метод IChatClient.CompleteAsync. Запрос состоит из одного или нескольких сообщений, каждый из которых состоит из одного или нескольких частей содержимого. Методы акселератора существуют для упрощения распространенных случаев, таких как создание запроса для одного фрагмента текстового содержимого.
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);
Основной метод IChatClient.CompleteAsync
принимает список сообщений. Этот список представляет историю всех сообщений, входящих в беседу.
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?"),
]));
Каждое сообщение в журнале представлено объектом ChatMessage. Класс ChatMessage
предоставляет свойство ChatMessage.Role, указывающее роль сообщения. По умолчанию используется ChatRole.User. Доступны следующие роли:
- ChatRole.Assistant: указывает или задает поведение помощника.
- ChatRole.System: предоставляет ответы на системные, запрашиваемые пользователем входные данные.
- ChatRole.Tool: предоставляет дополнительные сведения и ссылки для завершения чатов.
- ChatRole.User: предоставляет входные данные для завершения чата.
Каждое сообщение чата создается, и его свойству Contents присваивается новое значение TextContent. Существуют различные типы содержимого, которые можно представить, например простую строку или более сложный объект, представляющий много модальное сообщение с текстом, изображениями и звуком:
- AudioContent
- DataContent
- FunctionCallContent
- FunctionResultContent
- ImageContent
- TextContent
- UsageContent
Запрос завершения чата с потоковой передачей
Входные данные для IChatClient.CompleteStreamingAsync идентичны CompleteAsync
. Однако вместо возврата полного ответа в рамках объекта ChatCompletion метод возвращает IAsyncEnumerable<T>, где T
StreamingChatCompletionUpdate, предоставляя поток обновлений, которые коллективно формируют единый ответ.
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);
}
Кончик
API потоковой передачи почти синонимы пользовательского интерфейса искусственного интеллекта. C# обеспечивает убедительные сценарии с поддержкой IAsyncEnumerable<T>
, что позволяет обеспечить естественный и эффективный способ потоковой передачи данных.
Вызов инструмента
Некоторые модели и службы поддерживают использование инструмента вызова, где запросы могут включать инструменты для вызова функций для сбора дополнительных сведений. Вместо отправки окончательного ответа модель запрашивает вызов функции с определенными аргументами. Затем клиент вызывает функцию и отправляет результаты обратно в модель вместе с журналом бесед. Библиотека Microsoft.Extensions.AI
включает абстракции для различных типов контента сообщений, включая запросы и результаты вызова функции. Хотя потребители могут взаимодействовать с этим содержимым напрямую, Microsoft.Extensions.AI
автоматизирует эти взаимодействия и предоставляет:
- AIFunction: представляет функцию, которую можно описать в службе ИИ и вызвать.
-
AIFunctionFactory: предоставляет фабричные методы для создания реализаций, которые часто используются
AIFunction
. -
FunctionInvokingChatClient. Оборачивает
IChatClient
, чтобы добавить возможности автоматического вызова функций.
Рассмотрим следующий пример, демонстрирующий вызов случайной функции:
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);
}
Приведенный ранее пример зависит от пакета NuGet 📦 Microsoft.Extensions.AI.Ollama.
Предыдущий код:
- Определяет функцию с именем
GetCurrentWeather
, которая возвращает случайный прогноз погоды.- Эта функция украшена DescriptionAttribute, которая используется для предоставления описания функции службе ИИ.
- Создает экземпляр ChatClientBuilder, используя OllamaChatClient, и настраивает его для использования вызова функции.
- Вызывает
CompleteStreamingAsync
на клиенте, передав запрос и список инструментов, включающих функцию, созданную с 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 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();
}
Предыдущий пример зависит от пакета NuGet 📦 Microsoft.Extensions.Caching.Memory. Дополнительные сведения см. в разделе Кэширование в.NET.
Использование телеметрии
Еще одним примером делегированного клиента чата является OpenTelemetryChatClient. Эта реализация соответствует семантические соглашения OpenTelemetry для систем создания ИИ. Как и другие делегаторы IChatClient
, он создает слои из метрик и диапазонов над любой базовой реализацией IChatClient
, что обеспечивает повышенную наблюдаемость.
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);
Предыдущий пример зависит от пакета NuGet 📦 OpenTelemetry.Exporter.Console NuGet.
Предоставление параметров
Каждый вызов CompleteAsync или CompleteStreamingAsync может дополнительно предоставить экземпляр ChatOptions, содержащий дополнительные параметры для операции. Наиболее распространенные параметры моделей и служб ИИ отображаются как строго типизированные свойства типа, например ChatOptions.Temperature. Другие параметры могут быть предоставлены по имени в условиях слабой типизации с помощью словаря ChatOptions.AdditionalProperties.
Также можно указать параметры при создании IChatClient
с помощью Fluent API ChatClientBuilder и последовательного вызова метода расширения ConfigureOptions
. Этот клиент делегирования оборачивает другого клиента и вызывает предоставленный делегат, чтобы заполнить экземпляр ChatOptions
при каждом вызове. Например, чтобы убедиться, что свойство ChatOptions.ModelId по умолчанию имеет определенное имя модели, можно использовать следующий код:
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" }));
Приведенный ранее пример зависит от пакета NuGet 📦 Microsoft.Extensions.AI.Ollama.
Конвейеры функциональных возможностей
IChatClient
экземпляры можно накладывать друг на друга для создания конвейера компонентов, каждый из которых добавляет определенную функциональность. Эти компоненты могут поступать из Microsoft.Extensions.AI
, других пакетов NuGet или пользовательских реализаций. Этот подход позволяет расширить поведение IChatClient
различными способами для удовлетворения конкретных потребностей. Рассмотрим следующий пример кода, который слоит распределенный кэш, вызов функций и трассировку OpenTelemetry вокруг примера клиента чата:
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));
}
Предыдущий пример зависит от следующих пакетов NuGet:
- 📦 Microsoft.Extensions.Caching.Memory
- 📦 Microsoft.Extensions.AI.Ollama
- 📦 OpenTelemetry.Exporter.Console
Настраиваемое промежуточное программное обеспечение IChatClient
Чтобы добавить дополнительные функциональные возможности, можно реализовать IChatClient
напрямую или использовать класс DelegatingChatClient. Этот класс служит основой для создания клиентов чата, которые делегируют операции другому экземпляру класса IChatClient
. Это упрощает последовательное соединение нескольких клиентов, позволяя вызовам напрямую передаваться в базовый клиент.
Класс DelegatingChatClient
предоставляет реализации по умолчанию для таких методов, как CompleteAsync
, CompleteStreamingAsync
и Dispose
, которые перенаправяют вызовы внутреннего клиента. Вы можете наследовать от этого класса и переопределить только методы, необходимые для улучшения поведения, а также делегировать другие вызовы базовой реализации. Этот подход помогает создавать гибкие и модульные клиенты чата, которые легко расширить и создать.
Ниже приведен пример класса, производный от DelegatingChatClient
для предоставления функциональных возможностей ограничения скорости, используя 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);
}
}
Предыдущий пример зависит от пакета NuGet 📦 System.Threading.RateLimiting. Составление композиции RateLimitingChatClient
с другим клиентом не вызывает трудностей.
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?");
Чтобы упростить интеграцию таких компонентов с другими, разработчики компонентов должны создавать расширяющий метод Use*
для регистрации компонента в конвейере. Например, рассмотрим следующий метод расширения:
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>
Такие расширения также могут запрашивать соответствующие службы из контейнера DI; IServiceProvider, используемый конвейером, передается как необязательный параметр:
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>
Затем потребитель может легко использовать это в конвейере, например:
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();
Данный пример демонстрирует хостинговый сценарий, где потребитель использует внедрение зависимостей для создания экземпляра RateLimiter
. Предыдущие методы расширения демонстрируют использование метода Use
в ChatClientBuilder.
ChatClientBuilder
также предоставляет Use перегрузки, которые упрощают создание таких делегирующих обработчиков.
- 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>>)
Например, в предыдущем RateLimitingChatClient
примере переопределения CompleteAsync
и CompleteStreamingAsync
необходимо выполнить только работу до и после делегирования следующему клиенту в конвейере. Чтобы добиться того же, не создавая пользовательский класс, можно использовать перегрузку Use
, которая принимает делегат, используемый как для CompleteAsync
, так и для CompleteStreamingAsync
, уменьшая объём шаблонного кода.
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
Предыдущая перегрузка внутренне использует AnonymousDelegatingChatClient
, что обеспечивает более сложные шаблоны только с небольшим дополнительным кодом. Например, чтобы добиться того же результата, но с помощью RateLimiter извлекаемого из 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();
В сценариях, когда разработчик хочет указать спецификацию делегирующих реализаций CompleteAsync
и CompleteStreamingAsync
встроенные, и где важно иметь возможность создать другую реализацию для каждой из них, чтобы по-особому обработать их уникальные типы возвращаемых данных, существует другая перегрузка Use
, принимающая делегат для каждого.
Внедрение зависимостей
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 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();
Предыдущий пример зависит от следующих пакетов NuGet:
Какие экземпляры и конфигурации внедряются, могут отличаться в зависимости от текущих потребностей приложения, а несколько конвейеров можно внедрить с разными ключами.
Интерфейс IEmbeddingGenerator
Интерфейс IEmbeddingGenerator<TInput,TEmbedding> представляет универсальный генератор эмбеддингов. Здесь TInput
— это тип внедренных входных значений, а TEmbedding
— это тип созданного внедрения, который наследует от класса Embedding.
Класс Embedding
служит базовым классом для внедрений, созданных IEmbeddingGenerator
. Он предназначен для хранения метаданных и данных, связанных с внедрением, и управления ими. Производные типы, такие как Embedding<T>
, предоставляют конкретные данные вектора встраивания. Например, внедрение предоставляет свойство Embedding<T>.Vector для доступа к его внедренным данным.
Интерфейс IEmbeddingGenerator
определяет метод асинхронной генерации векторных представлений для коллекции входных значений с поддержкой необязательной конфигурации и возможности отмены. Он также предоставляет метаданные, описывающие генератор, и позволяет получить строго типизированные службы, которые могут быть предоставлены генератором или ее базовыми службами.
Пример реализации
Рассмотрим следующую пример реализации IEmbeddingGenerator
, чтобы показать общую структуру, но это просто создает случайные векторы внедрения.
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() { }
}
Предыдущий код:
- Определяет класс с именем
SampleEmbeddingGenerator
, реализующий интерфейсIEmbeddingGenerator<string, Embedding<float>>
. - Имеет основной конструктор, принимающий конечную точку и идентификатор модели, которые используются для идентификации генератора.
- Предоставляет свойство
Metadata
, которое содержит метаданные о генераторе. - Реализует метод
GenerateAsync
для создания внедрения для коллекции входных значений:- Имитация асинхронной операции путем задержки в 100 миллисекундах.
- Возвращает случайные эмбеддинги для каждого входного значения.
Фактические конкретные реализации можно найти в следующих пакетах:
Создание встраиваний
Основная операция, выполняемая с IEmbeddingGenerator<TInput,TEmbedding>, — генерация внедрений, которая осуществляется с помощью метода 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()));
}
Настраиваемое промежуточное программное обеспечение IEmbeddingGenerator
Как и в случае с IChatClient
, реализации IEmbeddingGenerator
могут быть многослойными. Так же, как Microsoft.Extensions.AI
обеспечивает делегирование реализаций IChatClient
для кэширования и телеметрии, она также предоставляет реализацию для 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()));
}
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 (var embedding in await generator.GenerateAsync(["What is AI?", "What is .NET?"]))
{
Console.WriteLine(string.Join(", ", embedding.Vector.ToArray()));
}
Таким образом, RateLimitingEmbeddingGenerator
может быть объединён с другими экземплярами IEmbeddingGenerator<string, Embedding<float>>
для обеспечения функциональности ограничения скорости.