EmbeddingGeneratorBuilder API updates (#5647)

This commit is contained in:
Steve Sanderson 2024-11-15 07:47:41 -08:00 коммит произвёл GitHub
Родитель aa6e8f0bbe
Коммит 09094aebc2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
15 изменённых файлов: 131 добавлений и 90 удалений

Просмотреть файл

@ -432,10 +432,11 @@ var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
// Explore changing the order of the intermediate "Use" calls to see that impact
// that has on what gets cached, traced, etc.
IEmbeddingGenerator<string, Embedding<float>> generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
var generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(
new SampleEmbeddingGenerator(new Uri("http://coolsite.ai"), "my-custom-model"))
.UseDistributedCache(new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())))
.UseOpenTelemetry(sourceName)
.Use(new SampleEmbeddingGenerator(new Uri("http://coolsite.ai"), "my-custom-model"));
.Build();
var embeddings = await generator.GenerateAsync(
[

Просмотреть файл

@ -210,9 +210,9 @@ IDistributedCache cache = new MemoryDistributedCache(Options.Create(new MemoryDi
IEmbeddingGenerator<string, Embedding<float>> ollamaGenerator =
new OllamaEmbeddingGenerator(new Uri("http://localhost:11434/"), "all-minilm");
IEmbeddingGenerator<string, Embedding<float>> generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
IEmbeddingGenerator<string, Embedding<float>> generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(ollamaGenerator)
.UseDistributedCache(cache)
.Use(ollamaGenerator);
.Build();
foreach (var prompt in new[] { "What is AI?", "What is .NET?", "What is AI?" })
{
@ -256,8 +256,7 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddChatClient(
new OllamaChatClient(new Uri("http://localhost:11434/"), "llama3.1"));
builder.Services.AddEmbeddingGenerator<string,Embedding<float>>(g =>
g.Use(new OllamaEmbeddingGenerator(endpoint, "all-minilm")));
builder.Services.AddEmbeddingGenerator(new OllamaEmbeddingGenerator(endpoint, "all-minilm"));
var app = builder.Build();

Просмотреть файл

@ -233,9 +233,9 @@ IEmbeddingGenerator<string, Embedding<float>> openAIGenerator =
new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY"))
.AsEmbeddingGenerator("text-embedding-3-small");
IEmbeddingGenerator<string, Embedding<float>> generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
IEmbeddingGenerator<string, Embedding<float>> generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(openAIGenerator)
.UseDistributedCache(cache)
.Use(openAIGenerator);
.Build();
foreach (var prompt in new[] { "What is AI?", "What is .NET?", "What is AI?" })
{
@ -284,8 +284,8 @@ builder.Services.AddSingleton(new OpenAIClient(builder.Configuration["OPENAI_API
builder.Services.AddChatClient(services =>
services.GetRequiredService<OpenAIClient>().AsChatClient("gpt-4o-mini"));
builder.Services.AddEmbeddingGenerator<string, Embedding<float>>(g =>
g.Use(g.Services.GetRequiredService<OpenAIClient>().AsEmbeddingGenerator("text-embedding-3-small")));
builder.Services.AddEmbeddingGenerator(services =>
services.GetRequiredService<OpenAIClient>().AsEmbeddingGenerator("text-embedding-3-small"));
var app = builder.Build();

Просмотреть файл

@ -10,7 +10,7 @@ namespace Microsoft.Extensions.AI;
/// <summary>A builder for creating pipelines of <see cref="IChatClient"/>.</summary>
public sealed class ChatClientBuilder
{
private Func<IServiceProvider, IChatClient> _innerClientFactory;
private readonly Func<IServiceProvider, IChatClient> _innerClientFactory;
/// <summary>The registered client factory instances.</summary>
private List<Func<IServiceProvider, IChatClient, IChatClient>>? _clientFactories;
@ -30,7 +30,7 @@ public sealed class ChatClientBuilder
_innerClientFactory = Throw.IfNull(innerClientFactory);
}
/// <summary>Returns an <see cref="IChatClient"/> that represents the entire pipeline. Calls to this instance will pass through each of the pipeline stages in turn.</summary>
/// <summary>Builds an <see cref="IChatClient"/> that represents the entire pipeline. Calls to this instance will pass through each of the pipeline stages in turn.</summary>
/// <param name="services">
/// The <see cref="IServiceProvider"/> that should provide services to the <see cref="IChatClient"/> instances.
/// If null, an empty <see cref="IServiceProvider"/> will be used.

Просмотреть файл

@ -37,7 +37,7 @@ public static class ChatClientBuilderServiceCollectionExtensions
return builder;
}
/// <summary>Registers a singleton <see cref="IChatClient"/> in the <see cref="IServiceCollection"/>.</summary>
/// <summary>Registers a keyed singleton <see cref="IChatClient"/> in the <see cref="IServiceCollection"/>.</summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to which the client should be added.</param>
/// <param name="serviceKey">The key with which to associate the client.</param>
/// <param name="innerClient">The inner <see cref="IChatClient"/> that represents the underlying backend.</param>
@ -49,7 +49,7 @@ public static class ChatClientBuilderServiceCollectionExtensions
IChatClient innerClient)
=> AddKeyedChatClient(serviceCollection, serviceKey, _ => innerClient);
/// <summary>Registers a singleton <see cref="IChatClient"/> in the <see cref="IServiceCollection"/>.</summary>
/// <summary>Registers a keyed singleton <see cref="IChatClient"/> in the <see cref="IServiceCollection"/>.</summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to which the client should be added.</param>
/// <param name="serviceKey">The key with which to associate the client.</param>
/// <param name="innerClientFactory">A callback that produces the inner <see cref="IChatClient"/> that represents the underlying backend.</param>

Просмотреть файл

@ -13,39 +13,45 @@ namespace Microsoft.Extensions.AI;
public sealed class EmbeddingGeneratorBuilder<TInput, TEmbedding>
where TEmbedding : Embedding
{
private readonly Func<IServiceProvider, IEmbeddingGenerator<TInput, TEmbedding>> _innerGeneratorFactory;
/// <summary>The registered client factory instances.</summary>
private List<Func<IServiceProvider, IEmbeddingGenerator<TInput, TEmbedding>, IEmbeddingGenerator<TInput, TEmbedding>>>? _generatorFactories;
/// <summary>Initializes a new instance of the <see cref="EmbeddingGeneratorBuilder{TInput, TEmbedding}"/> class.</summary>
/// <param name="services">The service provider to use for dependency injection.</param>
public EmbeddingGeneratorBuilder(IServiceProvider? services = null)
/// <param name="innerGenerator">The inner <see cref="EmbeddingGeneratorBuilder{TInput, TEmbedding}"/> that represents the underlying backend.</param>
public EmbeddingGeneratorBuilder(IEmbeddingGenerator<TInput, TEmbedding> innerGenerator)
{
Services = services ?? EmptyServiceProvider.Instance;
_ = Throw.IfNull(innerGenerator);
_innerGeneratorFactory = _ => innerGenerator;
}
/// <summary>Gets the <see cref="IServiceProvider"/> associated with the builder instance.</summary>
public IServiceProvider Services { get; }
/// <summary>Initializes a new instance of the <see cref="EmbeddingGeneratorBuilder{TInput, TEmbedding}"/> class.</summary>
/// <param name="innerGeneratorFactory">A callback that produces the inner <see cref="EmbeddingGeneratorBuilder{TInput, TEmbedding}"/> that represents the underlying backend.</param>
public EmbeddingGeneratorBuilder(Func<IServiceProvider, IEmbeddingGenerator<TInput, TEmbedding>> innerGeneratorFactory)
{
_innerGeneratorFactory = Throw.IfNull(innerGeneratorFactory);
}
/// <summary>
/// Builds an instance of <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> using the specified inner generator.
/// Builds an <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> that represents the entire pipeline. Calls to this instance will pass through each of the pipeline stages in turn.
/// </summary>
/// <param name="innerGenerator">The inner generator to use.</param>
/// <returns>An instance of <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/>.</returns>
/// <remarks>
/// If there are any factories registered with this builder, <paramref name="innerGenerator"/> is used as a seed to
/// the last factory, and the result of each factory delegate is passed to the previously registered factory.
/// The final result is then returned from this call.
/// </remarks>
public IEmbeddingGenerator<TInput, TEmbedding> Use(IEmbeddingGenerator<TInput, TEmbedding> innerGenerator)
/// <param name="services">
/// The <see cref="IServiceProvider"/> that should provide services to the <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> instances.
/// If null, an empty <see cref="IServiceProvider"/> will be used.
/// </param>
/// <returns>An instance of <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> that represents the entire pipeline.</returns>
public IEmbeddingGenerator<TInput, TEmbedding> Build(IServiceProvider? services = null)
{
var embeddingGenerator = Throw.IfNull(innerGenerator);
services ??= EmptyServiceProvider.Instance;
var embeddingGenerator = _innerGeneratorFactory(services);
// To match intuitive expectations, apply the factories in reverse order, so that the first factory added is the outermost.
if (_generatorFactories is not null)
{
for (var i = _generatorFactories.Count - 1; i >= 0; i--)
{
embeddingGenerator = _generatorFactories[i](Services, embeddingGenerator) ??
embeddingGenerator = _generatorFactories[i](services, embeddingGenerator) ??
throw new InvalidOperationException(
$"The {nameof(IEmbeddingGenerator<TInput, TEmbedding>)} entry at index {i} returned null. " +
$"Ensure that the callbacks passed to {nameof(Use)} return non-null {nameof(IEmbeddingGenerator<TInput, TEmbedding>)} instances.");

Просмотреть файл

@ -10,44 +10,74 @@ namespace Microsoft.Extensions.DependencyInjection;
/// <summary>Provides extension methods for registering <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> with a <see cref="IServiceCollection"/>.</summary>
public static class EmbeddingGeneratorBuilderServiceCollectionExtensions
{
/// <summary>Adds a embedding generator to the <see cref="IServiceCollection"/>.</summary>
/// <summary>Registers a singleton embedding generator in the <see cref="IServiceCollection"/>.</summary>
/// <typeparam name="TInput">The type from which embeddings will be generated.</typeparam>
/// <typeparam name="TEmbedding">The type of embeddings to generate.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to which the generator should be added.</param>
/// <param name="generatorFactory">The factory to use to construct the <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> instance.</param>
/// <returns>The <paramref name="services"/> collection.</returns>
/// <remarks>The generator is registered as a scoped service.</remarks>
public static IServiceCollection AddEmbeddingGenerator<TInput, TEmbedding>(
this IServiceCollection services,
Func<EmbeddingGeneratorBuilder<TInput, TEmbedding>, IEmbeddingGenerator<TInput, TEmbedding>> generatorFactory)
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to which the generator should be added.</param>
/// <param name="innerGenerator">The inner <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> that represents the underlying backend.</param>
/// <returns>An <see cref="EmbeddingGeneratorBuilder{TInput, TEmbedding}"/> that can be used to build a pipeline around the inner generator.</returns>
/// <remarks>The generator is registered as a singleton service.</remarks>
public static EmbeddingGeneratorBuilder<TInput, TEmbedding> AddEmbeddingGenerator<TInput, TEmbedding>(
this IServiceCollection serviceCollection,
IEmbeddingGenerator<TInput, TEmbedding> innerGenerator)
where TEmbedding : Embedding
=> AddEmbeddingGenerator(serviceCollection, _ => innerGenerator);
/// <summary>Registers a singleton embedding generator in the <see cref="IServiceCollection"/>.</summary>
/// <typeparam name="TInput">The type from which embeddings will be generated.</typeparam>
/// <typeparam name="TEmbedding">The type of embeddings to generate.</typeparam>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to which the generator should be added.</param>
/// <param name="innerGeneratorFactory">A callback that produces the inner <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> that represents the underlying backend.</param>
/// <returns>An <see cref="EmbeddingGeneratorBuilder{TInput, TEmbedding}"/> that can be used to build a pipeline around the inner generator.</returns>
/// <remarks>The generator is registered as a singleton service.</remarks>
public static EmbeddingGeneratorBuilder<TInput, TEmbedding> AddEmbeddingGenerator<TInput, TEmbedding>(
this IServiceCollection serviceCollection,
Func<IServiceProvider, IEmbeddingGenerator<TInput, TEmbedding>> innerGeneratorFactory)
where TEmbedding : Embedding
{
_ = Throw.IfNull(services);
_ = Throw.IfNull(generatorFactory);
_ = Throw.IfNull(serviceCollection);
_ = Throw.IfNull(innerGeneratorFactory);
return services.AddScoped(services =>
generatorFactory(new EmbeddingGeneratorBuilder<TInput, TEmbedding>(services)));
var builder = new EmbeddingGeneratorBuilder<TInput, TEmbedding>(innerGeneratorFactory);
_ = serviceCollection.AddSingleton(builder.Build);
return builder;
}
/// <summary>Adds an embedding generator to the <see cref="IServiceCollection"/>.</summary>
/// <summary>Registers a keyed singleton embedding generator in the <see cref="IServiceCollection"/>.</summary>
/// <typeparam name="TInput">The type from which embeddings will be generated.</typeparam>
/// <typeparam name="TEmbedding">The type of embeddings to generate.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to which the service should be added.</param>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to which the generator should be added.</param>
/// <param name="serviceKey">The key with which to associated the generator.</param>
/// <param name="generatorFactory">The factory to use to construct the <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> instance.</param>
/// <returns>The <paramref name="services"/> collection.</returns>
/// <remarks>The generator is registered as a scoped service.</remarks>
public static IServiceCollection AddKeyedEmbeddingGenerator<TInput, TEmbedding>(
this IServiceCollection services,
/// <param name="innerGenerator">The inner <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> that represents the underlying backend.</param>
/// <returns>An <see cref="EmbeddingGeneratorBuilder{TInput, TEmbedding}"/> that can be used to build a pipeline around the inner generator.</returns>
/// <remarks>The generator is registered as a singleton service.</remarks>
public static EmbeddingGeneratorBuilder<TInput, TEmbedding> AddKeyedEmbeddingGenerator<TInput, TEmbedding>(
this IServiceCollection serviceCollection,
object serviceKey,
Func<EmbeddingGeneratorBuilder<TInput, TEmbedding>, IEmbeddingGenerator<TInput, TEmbedding>> generatorFactory)
IEmbeddingGenerator<TInput, TEmbedding> innerGenerator)
where TEmbedding : Embedding
=> AddKeyedEmbeddingGenerator(serviceCollection, serviceKey, _ => innerGenerator);
/// <summary>Registers a keyed singleton embedding generator in the <see cref="IServiceCollection"/>.</summary>
/// <typeparam name="TInput">The type from which embeddings will be generated.</typeparam>
/// <typeparam name="TEmbedding">The type of embeddings to generate.</typeparam>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to which the generator should be added.</param>
/// <param name="serviceKey">The key with which to associated the generator.</param>
/// <param name="innerGeneratorFactory">A callback that produces the inner <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> that represents the underlying backend.</param>
/// <returns>An <see cref="EmbeddingGeneratorBuilder{TInput, TEmbedding}"/> that can be used to build a pipeline around the inner generator.</returns>
/// <remarks>The generator is registered as a singleton service.</remarks>
public static EmbeddingGeneratorBuilder<TInput, TEmbedding> AddKeyedEmbeddingGenerator<TInput, TEmbedding>(
this IServiceCollection serviceCollection,
object serviceKey,
Func<IServiceProvider, IEmbeddingGenerator<TInput, TEmbedding>> innerGeneratorFactory)
where TEmbedding : Embedding
{
_ = Throw.IfNull(services);
_ = Throw.IfNull(serviceCollection);
_ = Throw.IfNull(serviceKey);
_ = Throw.IfNull(generatorFactory);
_ = Throw.IfNull(innerGeneratorFactory);
return services.AddKeyedScoped(serviceKey, (services, _) =>
generatorFactory(new EmbeddingGeneratorBuilder<TInput, TEmbedding>(services)));
var builder = new EmbeddingGeneratorBuilder<TInput, TEmbedding>(innerGeneratorFactory);
_ = serviceCollection.AddKeyedSingleton(serviceKey, (services, _) => builder.Build(services));
return builder;
}
}

Просмотреть файл

@ -63,10 +63,10 @@ public class AzureAIInferenceEmbeddingGeneratorTests
Assert.Same(embeddingGenerator, embeddingGenerator.GetService<IEmbeddingGenerator<string, Embedding<float>>>());
Assert.Same(client, embeddingGenerator.GetService<EmbeddingsClient>());
using IEmbeddingGenerator<string, Embedding<float>> pipeline = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
using IEmbeddingGenerator<string, Embedding<float>> pipeline = new EmbeddingGeneratorBuilder<string, Embedding<float>>(embeddingGenerator)
.UseOpenTelemetry()
.UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions())))
.Use(embeddingGenerator);
.Build();
Assert.NotNull(pipeline.GetService<DistributedCachingEmbeddingGenerator<string, Embedding<float>>>());
Assert.NotNull(pipeline.GetService<CachingEmbeddingGenerator<string, Embedding<float>>>());

Просмотреть файл

@ -81,10 +81,10 @@ public abstract class EmbeddingGeneratorIntegrationTests : IDisposable
{
SkipIfNotEnabled();
using var generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
using var generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(CreateEmbeddingGenerator()!)
.UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions())))
.UseCallCounting()
.Use(CreateEmbeddingGenerator()!);
.Build();
string input = "Red, White, and Blue";
var embedding1 = await generator.GenerateEmbeddingAsync(input);
@ -110,9 +110,9 @@ public abstract class EmbeddingGeneratorIntegrationTests : IDisposable
.AddInMemoryExporter(activities)
.Build();
var embeddingGenerator = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
var embeddingGenerator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(CreateEmbeddingGenerator()!)
.UseOpenTelemetry(sourceName: sourceName)
.Use(CreateEmbeddingGenerator()!);
.Build();
_ = await embeddingGenerator.GenerateEmbeddingAsync("Hello, world!");

Просмотреть файл

@ -29,10 +29,10 @@ public class OllamaEmbeddingGeneratorTests
Assert.Same(generator, generator.GetService<OllamaEmbeddingGenerator>());
Assert.Same(generator, generator.GetService<IEmbeddingGenerator<string, Embedding<float>>>());
using IEmbeddingGenerator<string, Embedding<float>> pipeline = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
using IEmbeddingGenerator<string, Embedding<float>> pipeline = new EmbeddingGeneratorBuilder<string, Embedding<float>>(generator)
.UseOpenTelemetry()
.UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions())))
.Use(generator);
.Build();
Assert.NotNull(pipeline.GetService<DistributedCachingEmbeddingGenerator<string, Embedding<float>>>());
Assert.NotNull(pipeline.GetService<CachingEmbeddingGenerator<string, Embedding<float>>>());

Просмотреть файл

@ -78,10 +78,10 @@ public class OpenAIEmbeddingGeneratorTests
Assert.NotNull(embeddingGenerator.GetService<EmbeddingClient>());
using IEmbeddingGenerator<string, Embedding<float>> pipeline = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
using IEmbeddingGenerator<string, Embedding<float>> pipeline = new EmbeddingGeneratorBuilder<string, Embedding<float>>(embeddingGenerator)
.UseOpenTelemetry()
.UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions())))
.Use(embeddingGenerator);
.Build();
Assert.NotNull(pipeline.GetService<DistributedCachingEmbeddingGenerator<string, Embedding<float>>>());
Assert.NotNull(pipeline.GetService<CachingEmbeddingGenerator<string, Embedding<float>>>());
@ -100,10 +100,10 @@ public class OpenAIEmbeddingGeneratorTests
Assert.Same(embeddingGenerator, embeddingGenerator.GetService<IEmbeddingGenerator<string, Embedding<float>>>());
Assert.Same(openAIClient, embeddingGenerator.GetService<EmbeddingClient>());
using IEmbeddingGenerator<string, Embedding<float>> pipeline = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
using IEmbeddingGenerator<string, Embedding<float>> pipeline = new EmbeddingGeneratorBuilder<string, Embedding<float>>(embeddingGenerator)
.UseOpenTelemetry()
.UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions())))
.Use(embeddingGenerator);
.Build();
Assert.NotNull(pipeline.GetService<DistributedCachingEmbeddingGenerator<string, Embedding<float>>>());
Assert.NotNull(pipeline.GetService<CachingEmbeddingGenerator<string, Embedding<float>>>());

Просмотреть файл

@ -20,7 +20,8 @@ public class ConfigureOptionsEmbeddingGeneratorTests
[Fact]
public void ConfigureOptions_InvalidArgs_Throws()
{
var builder = new EmbeddingGeneratorBuilder<string, Embedding<float>>();
using var innerGenerator = new TestEmbeddingGenerator();
var builder = new EmbeddingGeneratorBuilder<string, Embedding<float>>(innerGenerator);
Assert.Throws<ArgumentNullException>("configure", () => builder.ConfigureOptions(null!));
}
@ -44,7 +45,7 @@ public class ConfigureOptionsEmbeddingGeneratorTests
}
};
using var generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>()
using var generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(innerGenerator)
.ConfigureOptions(options =>
{
Assert.NotSame(providedOptions, options);
@ -59,7 +60,7 @@ public class ConfigureOptionsEmbeddingGeneratorTests
returnedOptions = options;
})
.Use(innerGenerator);
.Build();
var embeddings = await generator.GenerateAsync([], providedOptions, cts.Token);
Assert.Same(expectedEmbeddings, embeddings);

Просмотреть файл

@ -321,12 +321,12 @@ public class DistributedCachingEmbeddingGeneratorTest
return Task.FromResult<GeneratedEmbeddings<Embedding<float>>>([_expectedEmbedding]);
},
};
using var outer = new EmbeddingGeneratorBuilder<string, Embedding<float>>(services)
using var outer = new EmbeddingGeneratorBuilder<string, Embedding<float>>(testGenerator)
.UseDistributedCache(configure: instance =>
{
instance.JsonSerializerOptions = TestJsonSerializerContext.Default.Options;
})
.Use(testGenerator);
.Build(services);
// Act: Make a request that should populate the cache
Assert.Empty(_storage.Keys);

Просмотреть файл

@ -13,32 +13,37 @@ public class EmbeddingGeneratorBuilderTests
public void PassesServiceProviderToFactories()
{
var expectedServiceProvider = new ServiceCollection().BuildServiceProvider();
using var expectedResult = new TestEmbeddingGenerator();
var builder = new EmbeddingGeneratorBuilder<string, Embedding<float>>(expectedServiceProvider);
using var expectedOuterGenerator = new TestEmbeddingGenerator();
using var expectedInnerGenerator = new TestEmbeddingGenerator();
builder.Use((serviceProvider, innerClient) =>
var builder = new EmbeddingGeneratorBuilder<string, Embedding<float>>(services =>
{
Assert.Same(expectedServiceProvider, serviceProvider);
return expectedResult;
Assert.Same(expectedServiceProvider, services);
return expectedInnerGenerator;
});
using var innerGenerator = new TestEmbeddingGenerator();
Assert.Equal(expectedResult, builder.Use(innerGenerator));
builder.Use((services, innerClient) =>
{
Assert.Same(expectedServiceProvider, services);
return expectedOuterGenerator;
});
Assert.Equal(expectedOuterGenerator, builder.Build(expectedServiceProvider));
}
[Fact]
public void BuildsPipelineInOrderAdded()
{
// Arrange
using var expectedInnerService = new TestEmbeddingGenerator();
var builder = new EmbeddingGeneratorBuilder<string, Embedding<float>>();
using var expectedInnerGenerator = new TestEmbeddingGenerator();
var builder = new EmbeddingGeneratorBuilder<string, Embedding<float>>(expectedInnerGenerator);
builder.Use(next => new InnerServiceCapturingEmbeddingGenerator("First", next));
builder.Use(next => new InnerServiceCapturingEmbeddingGenerator("Second", next));
builder.Use(next => new InnerServiceCapturingEmbeddingGenerator("Third", next));
// Act
var first = (InnerServiceCapturingEmbeddingGenerator)builder.Use(expectedInnerService);
var first = (InnerServiceCapturingEmbeddingGenerator)builder.Build();
// Assert
Assert.Equal("First", first.Name);
@ -46,29 +51,28 @@ public class EmbeddingGeneratorBuilderTests
Assert.Equal("Second", second.Name);
var third = (InnerServiceCapturingEmbeddingGenerator)second.InnerGenerator;
Assert.Equal("Third", third.Name);
Assert.Same(expectedInnerService, third.InnerGenerator);
Assert.Same(expectedInnerGenerator, third.InnerGenerator);
}
[Fact]
public void DoesNotAcceptNullInnerService()
{
Assert.Throws<ArgumentNullException>(() => new EmbeddingGeneratorBuilder<string, Embedding<float>>().Use((IEmbeddingGenerator<string, Embedding<float>>)null!));
Assert.Throws<ArgumentNullException>(() => new EmbeddingGeneratorBuilder<string, Embedding<float>>((IEmbeddingGenerator<string, Embedding<float>>)null!));
}
[Fact]
public void DoesNotAcceptNullFactories()
{
var builder = new EmbeddingGeneratorBuilder<string, Embedding<float>>();
Assert.Throws<ArgumentNullException>(() => builder.Use((Func<IEmbeddingGenerator<string, Embedding<float>>, IEmbeddingGenerator<string, Embedding<float>>>)null!));
Assert.Throws<ArgumentNullException>(() => builder.Use((Func<IServiceProvider, IEmbeddingGenerator<string, Embedding<float>>, IEmbeddingGenerator<string, Embedding<float>>>)null!));
Assert.Throws<ArgumentNullException>(() => new EmbeddingGeneratorBuilder<string, Embedding<float>>((Func<IServiceProvider, IEmbeddingGenerator<string, Embedding<float>>>)null!));
}
[Fact]
public void DoesNotAllowFactoriesToReturnNull()
{
var builder = new EmbeddingGeneratorBuilder<string, Embedding<float>>();
using var innerGenerator = new TestEmbeddingGenerator();
var builder = new EmbeddingGeneratorBuilder<string, Embedding<float>>(innerGenerator);
builder.Use(_ => null!);
var ex = Assert.Throws<InvalidOperationException>(() => builder.Use(new TestEmbeddingGenerator()));
var ex = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Contains("entry at index 0", ex.Message);
}

Просмотреть файл

@ -39,9 +39,9 @@ public class LoggingEmbeddingGeneratorTests
},
};
using IEmbeddingGenerator<string, Embedding<float>> generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(services)
using IEmbeddingGenerator<string, Embedding<float>> generator = new EmbeddingGeneratorBuilder<string, Embedding<float>>(innerGenerator)
.UseLogging()
.Use(innerGenerator);
.Build(services);
await generator.GenerateEmbeddingAsync("Blue whale");