Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Los desarrolladores de .NET deben integrar e interactuar con una variedad creciente de servicios de inteligencia artificial (IA) en sus aplicaciones. Las Microsoft.Extensions.AI
bibliotecas proporcionan un enfoque unificado para representar componentes de IA generativos y permiten una integración e interoperabilidad sin problemas con varios servicios de inteligencia artificial. En este artículo se presentan las bibliotecas y se proporcionan ejemplos de uso detallados para ayudarle a empezar.
Los paquetes
El 📦 paquete Microsoft.Extensions.AI.Abstracciones proporciona los tipos de intercambio principales, incluidos IChatClient y IEmbeddingGenerator<TInput,TEmbedding>. Cualquier biblioteca de .NET que proporcione un cliente LLM puede implementar la interfaz IChatClient
para habilitar una integración fluida con el código consumidor.
El paquete 📦 Microsoft.Extensions.AI tiene una dependencia implícita del paquete Microsoft.Extensions.AI.Abstractions
. Este paquete permite integrar fácilmente componentes como la invocación automática de la herramienta de funciones, la telemetría y el almacenamiento en caché en las aplicaciones mediante patrones conocidos de inserción de dependencias y middleware. Por ejemplo, proporciona el método de extensión UseOpenTelemetry(ChatClientBuilder, ILoggerFactory, String, Action<OpenTelemetryChatClient>), que agrega compatibilidad con OpenTelemetry a la pipeline del cliente del chat.
Qué paquete referenciar
Las bibliotecas que proporcionan implementaciones de las abstracciones suelen hacer referencia solo a Microsoft.Extensions.AI.Abstractions
.
Para tener acceso a utilidades de nivel superior para trabajar con componentes de IA generativa, haga referencia al paquete Microsoft.Extensions.AI
en lugar del paquete Microsoft.Extensions.AI.Abstractions
. La mayoría de las aplicaciones y servicios que consumen deben hacer referencia al Microsoft.Extensions.AI
paquete junto con una o varias bibliotecas que proporcionan implementaciones concretas de las abstracciones.
Instalación de los paquetes
Para obtener información sobre cómo instalar paquetes NuGet, vea dotnet package add or Manage package dependencies in .NET applications (Agregar o administrar dependencias de paquetes en aplicaciones .NET).
Ejemplos de uso de API
Las subsecciones siguientes muestran ejemplos de uso específicos de IChatClient :
- Solicitud de una respuesta de chat
- Solicitud de una respuesta de chat en streaming
- Llamada a la herramienta
- almacenar en caché las respuestas
- Usar telemetría
- Proporcionar opciones
- Canalizaciones de funcionalidad
- Middleware
IChatClient
personalizado - Inserción de dependencia
- Clientes sin estado frente a clientes con estado
En las secciones siguientes se muestran ejemplos de uso específicos de IEmbeddingGenerator :
Interfaz IChatClient
La interfaz IChatClient define una abstracción de cliente responsable de interactuar con los servicios de INTELIGENCIA ARTIFICIAL que proporcionan funcionalidades de chat. Incluye métodos para enviar y recibir mensajes con contenido multi modal (como texto, imágenes y audio), ya sea como un conjunto completo o transmitido incrementalmente. Además, permite recuperar servicios fuertemente tipados proporcionados por el cliente o sus servicios subyacentes.
Las bibliotecas de .NET que proporcionan clientes para los modelos de lenguaje y los servicios pueden proporcionar una implementación de la IChatClient
interfaz. Los consumidores de la interfaz pueden interoperar sin problemas con estos modelos y servicios a través de las abstracciones. Puede ver una implementación sencilla en Implementaciones de ejemplo de IChatClient e IEmbeddingGenerator.
Solicitud de una respuesta de chat
Con una instancia de IChatClient, puede llamar al IChatClient.GetResponseAsync método para enviar una solicitud y obtener una respuesta. La solicitud se compone de uno o varios mensajes, cada uno de los cuales se compone de una o varias partes de contenido. Existen métodos de acelerador para simplificar casos comunes, como la construcción de una solicitud para un solo fragmento de contenido 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?"));
El método principal IChatClient.GetResponseAsync
acepta una lista de mensajes. Esta lista representa el historial de todos los mensajes que forman parte de la conversación.
Console.WriteLine(await client.GetResponseAsync(
[
new(ChatRole.System, "You are a helpful AI assistant"),
new(ChatRole.User, "What is AI?"),
]));
El ChatResponse que se devuelve desde GetResponseAsync
presenta una lista de instancias de ChatMessage que representan uno o más mensajes generados como parte de la operación. En casos comunes, solo hay un mensaje de respuesta, pero en algunas situaciones puede haber varios mensajes. La lista de mensajes está ordenada, de modo que el último mensaje de la lista representa el mensaje final a la solicitud. Para devolver todos esos mensajes de respuesta al servicio en una solicitud posterior, puede volver a agregar los mensajes de la respuesta a la lista de mensajes.
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);
}
Solicitud de una respuesta de chat en streaming
Las entradas de IChatClient.GetStreamingResponseAsync son idénticas a las de GetResponseAsync
. Sin embargo, en lugar de devolver la respuesta completa como parte de un objeto ChatResponse, el método devuelve un IAsyncEnumerable<T> donde T
es ChatResponseUpdate, proporcionando una secuencia de actualizaciones que forman colectivamente la respuesta única.
await foreach (ChatResponseUpdate update in client.GetStreamingResponseAsync("What is AI?"))
{
Console.Write(update);
}
Sugerencia
Las API de streaming son casi sinónimos de experiencias de usuario de IA. C# permite escenarios atractivos con su compatibilidad con IAsyncEnumerable<T>
, lo que permite una forma natural y eficaz de transmitir datos.
Al igual que con GetResponseAsync
, puede agregar las actualizaciones de IChatClient.GetStreamingResponseAsync de nuevo a la lista de mensajes. Dado que las actualizaciones son partes individuales de una respuesta, puede usar asistentes como ToChatResponse(IEnumerable<ChatResponseUpdate>) para componer una o varias actualizaciones de nuevo en una sola ChatResponse instancia.
Los asistentes como AddMessages componen un ChatResponse y luego extraen los mensajes compuestos de la respuesta y los agregan a una 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);
}
Llamada a herramientas
Algunos modelos y servicios admiten llamadas a herramientas. Para recopilar información adicional, puede configurar con ChatOptions información sobre herramientas (normalmente métodos .NET) que el modelo puede solicitar al cliente que invoque. En lugar de enviar una respuesta final, el modelo solicita una invocación de función con argumentos específicos. A continuación, el cliente invoca la función y devuelve los resultados al modelo con el historial de conversaciones. La biblioteca de Microsoft.Extensions.AI.Abstractions
incluye abstracciones para varios tipos de contenido de mensajes, incluidas las solicitudes y los resultados de las llamadas de función. Aunque IChatClient
los consumidores pueden interactuar directamente con este contenido, Microsoft.Extensions.AI
proporciona asistentes que pueden habilitar la invocación automática de las herramientas en respuesta a las solicitudes correspondientes. Las Microsoft.Extensions.AI.Abstractions
bibliotecas y Microsoft.Extensions.AI
proporcionan los siguientes tipos:
- AIFunction: representa una función que se puede describir en un modelo de IA e invocarla.
- AIFunctionFactory: proporciona métodos de fábrica para crear
AIFunction
instancias que representan métodos de .NET. - FunctionInvokingChatClient: Envuelve un
IChatClient
como otroIChatClient
adicional que agrega capacidades de invocación automática de funciones.
En el ejemplo siguiente se muestra una invocación de función aleatoria (este ejemplo depende del 📦 paquete 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);
}
El código anterior:
- Define una función denominada
GetCurrentWeather
que devuelve una previsión meteorológica aleatoria. - Crea una instancia de un ChatClientBuilder con un
OllamaSharp.OllamaApiClient
y lo configura para usar la invocación de función. - Llama a
GetStreamingResponseAsync
en el cliente, pasando una solicitud y una lista de herramientas que incluye una función creada con Create. - Recorre en iteración la respuesta, imprimiendo cada actualización en la consola.
Respuestas de caché
Si está familiarizado con el Almacenamiento en caché en .NET, es bueno saber que Microsoft.Extensions.AI ofrece otras implementaciones delegadas como IChatClient
. El DistributedCachingChatClient es un IChatClient
que aplica almacenamiento en caché sobre otra instancia arbitraria de IChatClient
. Cuando se envía un historial de chat nuevo a DistributedCachingChatClient
, lo reenvía al cliente subyacente y, a continuación, almacena en caché la respuesta antes de devolverla al consumidor. La próxima vez que se envíe el mismo historial, de modo que se pueda encontrar una respuesta almacenada en caché en la memoria caché, DistributedCachingChatClient
devuelve la respuesta almacenada en caché en lugar de reenviar la solicitud a lo largo de la canalización.
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 ejemplo depende del 📦 paquete NuGet Microsoft.Extensions.Caching.Memory . Para más información, vea Almacenamiento en caché en .NET.
Uso de telemetría
Otro ejemplo de un cliente de chat de delegación es el OpenTelemetryChatClient. Esta implementación se adhiere a las convenciones semánticas de OpenTelemetry para sistemas de inteligencia artificial generativa. De forma similar a otros delegadores IChatClient
, se superponen las métricas y se extienden alrededor de otras implementaciones de IChatClient
arbitrarias.
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);
(El ejemplo anterior depende del 📦 paquete NuGet OpenTelemetry.Exporter.Console ).
Como alternativa, el método LoggingChatClient y el método correspondiente UseLogging(ChatClientBuilder, ILoggerFactory, Action<LoggingChatClient>) proporcionan una manera sencilla de escribir entradas de registro en un ILogger para cada solicitud y respuesta.
Proporcionar opciones
Cada llamada a GetResponseAsync o GetStreamingResponseAsync puede proporcionar opcionalmente una instancia de ChatOptions que contenga parámetros adicionales para la operación. Los parámetros más comunes entre los modelos y servicios de IA aparecen como propiedades fuertemente tipadas en el tipo, como ChatOptions.Temperature. Otros parámetros se pueden proporcionar por nombre de manera débilmente tipada, a través del diccionario ChatOptions.AdditionalProperties, o mediante una instancia de opciones que el proveedor subyacente entiende, mediante la propiedad ChatOptions.RawRepresentationFactory.
También puede especificar opciones al construir un IChatClient
con la API fluida ChatClientBuilder mediante el encadenamiento de una llamada al método de extensión ConfigureOptions(ChatClientBuilder, Action<ChatOptions>). Este cliente de delegación encapsula otro cliente e invoca al delegado proporcionado para rellenar una instancia de ChatOptions
para cada llamada. Por ejemplo, para asegurarse de que la propiedad ChatOptions.ModelId tiene como valor predeterminado un nombre de modelo determinado, puede usar código como el siguiente:
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 funcionalidad
IChatClient
Las instancias se pueden superponer para crear una canalización de componentes que agreguen funcionalidad adicional. Estos componentes pueden provenir de Microsoft.Extensions.AI
, otros paquetes NuGet o implementaciones personalizadas. Este enfoque le permite aumentar el comportamiento del IChatClient
de varias maneras de satisfacer sus necesidades específicas. Tenga en cuenta el siguiente fragmento de código que añade capas a los almacenamientos en caché distribuidos, la invocación de funciones y el seguimiento de OpenTelemetry en torno a clientes de chat de ejemplo.
// 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 agregar funcionalidad adicional, puede implementar IChatClient
directamente o usar la clase DelegatingChatClient. Esta clase sirve como base para crear clientes de chat que deleguen operaciones a otra instancia de IChatClient
. Simplifica el encadenamiento de varios clientes, lo que permite que las llamadas pasen a un cliente subyacente.
La clase DelegatingChatClient
proporciona implementaciones predeterminadas para métodos como GetResponseAsync
, GetStreamingResponseAsync
y Dispose
, que reenvía llamadas al cliente interno. A continuación, una clase derivada solo puede invalidar los métodos que necesita para aumentar el comportamiento, al tiempo que se delega otras llamadas a la implementación base. Este enfoque es útil para crear clientes de chat flexibles y modulares que son fáciles de ampliar y redactar.
A continuación se muestra una clase de ejemplo derivada de DelegatingChatClient
que usa la biblioteca System.Threading.RateLimiting para proporcionar funcionalidad de limitación de velocidad.
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);
}
}
Al igual que con otras IChatClient
implementaciones, RateLimitingChatClient
se puede componer:
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 la composición de estos componentes con otros, los autores de componentes deben crear un método de extensión Use*
para registrar el componente en una canalización. Por ejemplo, considere el siguiente UseRatingLimiting
método de extensión:
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)
);
}
Estas extensiones también pueden consultar los servicios pertinentes del contenedor de inserción de dependencias; el IServiceProvider usado por la pipeline se pasa como 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>())
);
}
Ahora es fácil para el consumidor usarlo en su flujo de trabajo, por ejemplo:
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));
Los métodos de extensión anteriores muestran el uso de un método Use
en ChatClientBuilder. El ChatClientBuilder
también proporciona sobrecargas de Use que facilitan la escritura de tales controladores de delegación. Por ejemplo, en el ejemplo anterior de RateLimitingChatClient
, las sobrescrituras de GetResponseAsync
y GetStreamingResponseAsync
solo necesitan realizar el trabajo antes y después de delegar al siguiente cliente en el pipeline. Para lograr lo mismo sin escribir una clase personalizada, se puede usar una sobrecarga de Use
que acepta un delegado que se utiliza para GetResponseAsync
y GetStreamingResponseAsync
, reduciendo el código repetitivo necesario:
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();
En escenarios en los que necesita una implementación diferente para GetResponseAsync
y GetStreamingResponseAsync
para manejar sus tipos de valor devuelto únicos, puede usar la sobrecarga Use(Func<IEnumerable<ChatMessage>,ChatOptions,IChatClient,CancellationToken,
Task<ChatResponse>>, Func<IEnumerable<ChatMessage>,ChatOptions,
IChatClient,CancellationToken,IAsyncEnumerable<ChatResponseUpdate>>) que acepta un delegado para cada una.
Inserción de dependencia
IChatClientLas implementaciones a menudo se proporcionan a una aplicación a través de la inserción de dependencias (DI). En este ejemplo, se agrega un IDistributedCache al contenedor DI, al igual que un IChatClient
. El registro de IChatClient
emplea un generador que crea una pipeline que contiene un cliente de caché (que luego usará un IDistributedCache
recuperado a través de la DI) y el cliente de ejemplo. El IChatClient
insertado se puede recuperar y usar en otra parte de la aplicación.
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?"));
La instancia y la configuración insertadas pueden diferir en función de las necesidades actuales de la aplicación y se pueden insertar varias canalizaciones con claves diferentes.
Clientes sin estado frente a clientes con estado
Los servicios sin estado requieren que se devuelva todo el historial de conversaciones pertinente en cada solicitud. En cambio, los servicios con estado realizan un seguimiento del historial y requieren que solo se envíen mensajes adicionales con una solicitud. La interfaz IChatClient está diseñada para gestionar servicios de IA con y sin estado.
Al trabajar con un servicio sin estado, los autores de llamada mantienen una lista de todos los mensajes. Agregan todos los mensajes de respuesta recibidos y proporcionan la lista de nuevo en las interacciones posteriores.
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);
}
En el caso de los servicios con estado, es posible que ya conozca el identificador usado para la conversación pertinente. Puede colocar ese identificador en ChatOptions.ConversationId. Después, el uso sigue el mismo patrón, salvo que no es necesario mantener manualmente un historial.
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));
}
Algunos servicios pueden admitir la creación automática de un identificador de conversación para una solicitud que no tiene uno o la creación de un nuevo identificador de conversación que represente el estado actual de la conversación después de incorporar la última ronda de mensajes. En tales casos, puede transferir el ChatResponse.ConversationId al ChatOptions.ConversationId
para las solicitudes posteriores. Por ejemplo:
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;
}
Si no sabe con antelación si el servicio es con estado o sin estado, puede comprobar la respuesta ConversationId y actuar según su valor. Si se establece, ese valor se propaga a las opciones y el historial se borra para no volver a enviar el mismo historial de nuevo. Si no se establece la respuesta ConversationId
, el mensaje de respuesta se agrega al historial para que se devuelva al servicio en el siguiente turno.
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);
}
}
Interfaz IEmbeddingGenerator
La interfaz IEmbeddingGenerator<TInput,TEmbedding> representa un generador genérico de incrustaciones. Para los parámetros de tipo genérico, TInput
es el tipo de valores de entrada que se insertan y TEmbedding
es el tipo de inserción generada, que hereda de la Embedding clase .
La clase Embedding
actúa como una clase base para incrustaciones generadas por un IEmbeddingGenerator
. Está diseñado para almacenar y administrar los metadatos y los datos asociados a las incrustaciones. Los tipos derivados, como Embedding<T>, proporcionan los datos vectoriales de inserción concretos. Por ejemplo, Embedding<float>
expone una propiedad ReadOnlyMemory<float> Vector { get; }
para el acceso a sus datos incrustados.
La interfaz IEmbeddingGenerator
define un método para generar de forma asincrónica inserciones para una colección de valores de entrada, con compatibilidad opcional de configuración y cancelación. También proporciona metadatos que describen el generador y permite la recuperación de servicios fuertemente tipados que el generador o sus servicios subyacentes pueden proporcionar.
La mayoría de los usuarios no necesitan implementar la IEmbeddingGenerator
interfaz. Sin embargo, si es autor de la biblioteca, puede ver una implementación sencilla en Implementaciones de ejemplo de IChatClient e IEmbeddingGenerator.
Creación de incrustaciones
La operación principal realizada con un IEmbeddingGenerator<TInput,TEmbedding> es la generación de inserciones, que se genera con su 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()));
}
Los métodos de extensión de acelerador también existen para simplificar los casos comunes, como generar un vector de inserción a partir de una sola entrada.
ReadOnlyMemory<float> vector = await generator.GenerateVectorAsync("What is AI?");
Flujos de funcionalidades
Al igual que con IChatClient
, se pueden superponer las implementaciones de IEmbeddingGenerator
. Microsoft.Extensions.AI
proporciona una implementación de delegación para el IEmbeddingGenerator
almacenamiento en caché y la telemetría.
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()));
}
El IEmbeddingGenerator
permite crear middleware personalizado que amplía la funcionalidad de un IEmbeddingGenerator
. La clase DelegatingEmbeddingGenerator<TInput,TEmbedding> es una implementación de la interfaz IEmbeddingGenerator<TInput, TEmbedding>
que actúa como clase base para crear generadores de inserción que deleguen sus operaciones a otra instancia de IEmbeddingGenerator<TInput, TEmbedding>
. Permite encadenar varios generadores en cualquier orden, pasando llamadas a un generador subyacente. La clase proporciona implementaciones predeterminadas para métodos como GenerateAsync y Dispose
, que reenvía las llamadas a la instancia del generador interno, lo que permite la generación de inserción flexible y modular.
A continuación se muestra un ejemplo de implementación de un generador de incrustaciones delegadas que limita la velocidad de las solicitudes de generación de incrustaciones.
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);
}
}
Después, se puede superponer a una IEmbeddingGenerator<string, Embedding<float>>
arbitraria para limitar la velocidad de todas las operaciones de generación de inserción.
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()));
}
De este modo, RateLimitingEmbeddingGenerator
se puede componer con otras IEmbeddingGenerator<string, Embedding<float>>
instancias para proporcionar funcionalidad de limitación de velocidad.
Compilación con Microsoft.Extensions.AI
Puede comenzar a construir con Microsoft.Extensions.AI
de las siguientes formas:
- Desarrolladores de bibliotecas: si posee bibliotecas que proporcionan clientes para servicios de inteligencia artificial, considere la posibilidad de implementar las interfaces en las bibliotecas. Esto permite a los usuarios integrar fácilmente el paquete NuGet a través de las abstracciones. Para ver implementaciones de ejemplo, consulte Implementaciones de ejemplo de IChatClient e IEmbeddingGenerator.
- Consumidores de servicios: si está desarrollando bibliotecas que consumen servicios de IA, use las abstracciones en lugar de codificar de forma dura a un servicio de IA específico. Este enfoque ofrece a los consumidores la flexibilidad de elegir su proveedor preferido.
- Desarrolladores de aplicaciones: use las abstracciones para simplificar la integración en las aplicaciones. Esto permite la portabilidad entre modelos y servicios, facilita las pruebas y el simulacro, aprovecha el middleware proporcionado por el ecosistema y mantiene una API coherente en toda la aplicación, incluso si usa servicios diferentes en diferentes partes de la aplicación.
- Colaboradores del ecosistema: si está interesado en contribuir al ecosistema, considere la posibilidad de escribir componentes de middleware personalizados.
Para obtener más ejemplos, consulte el repositorio dotnet/ai-samples de GitHub. Para obtener un ejemplo completo, consulte eShopSupport.