-
Notifications
You must be signed in to change notification settings - Fork 336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor to support Dependency Injection with ITextEmbeddingGenerator #444
Refactor to support Dependency Injection with ITextEmbeddingGenerator #444
Conversation
would like not having to introduce global constants, could you replace constants with nameof(class)? |
Unfortunately, we can't use public IKernelMemoryBuilder AddKeyedSingleton<TService, TImplementation>(object serviceKey)
where TService : class
where TImplementation : class, TService
{
this.Services.AddKeyedSingleton<TService, TImplementation>(serviceKey);
return this;
} So in this case, we don't have a class to be used with public SimpleVectorDb(
SimpleVectorDbConfig config,
[FromKeyedServices(Constants.TextEmbeddingGeneratorKey)] ITextEmbeddingGenerator embeddingGenerator,
ILogger<SimpleVectorDb>? log = null)
{
this._embeddingGenerator = embeddingGenerator; So, again we don't have a class for We could use public static IKernelMemoryBuilder WithCustomEmbeddingGenerator(
this IKernelMemoryBuilder builder,
ITextEmbeddingGenerator service,
bool useForIngestion = true,
bool useForRetrieval = true)
{
service = service ?? throw new ConfigurationException("Memory Builder: the embedding generator instance is NULL");
if (useForRetrieval)
{
builder.AddKeyedSingleton<ITextEmbeddingGenerator>(Constants.TextEmbeddingGeneratorKey, service);
}
if (useForIngestion)
{
builder.AddIngestionEmbeddingGenerator(service);
}
return builder;
}
public static IKernelMemoryBuilder WithCustomEmbeddingGenerator<T>(
this IKernelMemoryBuilder builder,
bool useForIngestion = true,
bool useForRetrieval = true) where T : class, ITextEmbeddingGenerator
{
if (useForRetrieval)
{
builder.AddKeyedSingleton<ITextEmbeddingGenerator, T>(Constants.TextEmbeddingGeneratorKey);
}
if (useForIngestion)
{
builder.AddIngestionEmbeddingGenerator<T>();
}
return builder;
} So that it directly uses the public static IKernelMemoryBuilder WithCustomEmbeddingGenerator(
this IKernelMemoryBuilder builder,
ITextEmbeddingGenerator service,
bool useForIngestion = true,
bool useForRetrieval = true)
{
service = service ?? throw new ConfigurationException("Memory Builder: the embedding generator instance is NULL");
if (useForRetrieval)
{
builder.Services.AddKeyedSingleton<ITextEmbeddingGenerator>(nameof(ITextEmbeddingGenerator), service);
}
if (useForIngestion)
{
builder.AddIngestionEmbeddingGenerator(service);
}
return builder;
}
public static IKernelMemoryBuilder WithCustomEmbeddingGenerator<T>(
this IKernelMemoryBuilder builder,
bool useForIngestion = true,
bool useForRetrieval = true) where T : class, ITextEmbeddingGenerator
{
if (useForRetrieval)
{
builder.Services.AddKeyedSingleton<ITextEmbeddingGenerator, T>(nameof(ITextEmbeddingGenerator));
}
if (useForIngestion)
{
builder.AddIngestionEmbeddingGenerator<T>();
}
return builder;
} |
Using the same For instance, when using both Example: public SimpleVectorDb(
SimpleVectorDbConfig config,
[FromKeyedServices(nameof(SimpleVectorDb))] ITextEmbeddingGenerator embeddingGenerator,
ILogger<SimpleVectorDb>? log = null)
{
this._embeddingGenerator = embeddingGenerator; public QdrantMemory(
QdrantConfig config,
[FromKeyedServices(nameof(QdrantMemory))] ITextEmbeddingGenerator embeddingGenerator,
ILogger<QdrantMemory>? log = null) builder.WithKeyedSingleton<ITextEmbeddingGenerator, OpenAITextEmbeddingGenerator>(nameof(SimpleVectordb));
builder.WithKeyedSingleton<ITextEmbeddingGenerator, AzureOpenAITextEmbeddingGenerator>(nameof(QdrantMemory)); If this works, then we could introduce some specific extension methods, allowing this syntax, similarly to IHttpClientFactory: builder.WithKeyedEmbeddingGenerator<SimpleVectordb, OpenAITextEmbeddingGenerator>();
builder.WithKeyedEmbeddingGenerator<QdrantMemory, AzureOpenAITextEmbeddingGenerator>(); If all of this works, we might as well revisit // replaces current WithCustomEmbeddingGenerator<T>
WithEmbeddingGenerator<TEmbeddingGenerator>(...)
// keyed injection
WithEmbeddingGenerator<TConsumer, TEmbeddingGenerator>(...) |
In current implementation, for example here: kernel-memory/service/Abstractions/KernelMemoryBuilderExtensions.cs Lines 119 to 138 in d4f77eb
We can register multiple generators for ingestion ( kernel-memory/service/Core/MemoryStorage/DevTools/SimpleVectorDb.cs Lines 38 to 43 in d4f77eb
Will get the last In my draft, I use the single public static IKernelMemoryBuilder WithCustomEmbeddingGenerator(
this IKernelMemoryBuilder builder,
ITextEmbeddingGenerator service,
bool useForIngestion = true,
bool useForRetrieval = true)
{
service = service ?? throw new ConfigurationException("Memory Builder: the embedding generator instance is NULL");
if (useForRetrieval)
{
builder.AddKeyedSingleton<ITextEmbeddingGenerator>(Constants.TextEmbeddingGeneratorKey, service);
}
if (useForIngestion)
{
builder.AddIngestionEmbeddingGenerator(service);
}
return builder;
} In your example: public SimpleVectorDb(
SimpleVectorDbConfig config,
[FromKeyedServices(nameof(SimpleVectorDb))] ITextEmbeddingGenerator embeddingGenerator,
ILogger<SimpleVectorDb>? log = null)
{
this._embeddingGenerator = embeddingGenerator; public QdrantMemory(
QdrantConfig config,
[FromKeyedServices(nameof(QdrantMemory))] ITextEmbeddingGenerator embeddingGenerator,
ILogger<QdrantMemory>? log = null)
But what if I want to register a custom embedding generator, so that I can't use any |
They are tied only if there's a keyed service, e.g. this
should translate to:
Using
Using a constant, it's possible to key on all T's, but not one a specific T.
Using |
Excuse me, but I don't get the point. var fooOfInt = new Foo<int>();
fooOfInt.Print();
var fooOfString = new Foo<string>();
fooOfString.Print();
public class Foo<T>
{
public void Print()
{
var name = nameof(Foo<T>);
Console.WriteLine(name);
}
} Suppose I have the following custom embedding generators: public class CustomEmbeddingGenerator1 : ITextEmbeddingGenerator
{
// ...
}
public class CustomEmbeddingGenerator2 : ITextEmbeddingGenerator
{
// ...
} And I want to use |
Let's leave generics out for a second. A couple observations from my tests (console app) and the PR:
Considering that #334 has a solution as far as I can tell, unless we figure out how to use keyed services with a fallback, I'd leave the code as it is. |
Your idea about using public static IKernelMemoryBuilder WithCustomEmbeddingGenerator<T>(
this IKernelMemoryBuilder builder) where T : class, ITextEmbeddingGenerator
{
builder.Services.AddSingleton<ITextEmbeddingGenerator>(serviceProvider =>
{
return ActivatorUtilities.CreateInstance<T>(serviceProvider);
});
return builder;
} |
Should we park this, given that the original problem can be addressed without code changes? |
If you agree, I can modify this PR to include just the extension method I suggested in my previous comment. It would greatly simplify the use case. What do you think? |
1b3860c
to
237f486
Compare
## Motivation and Context (Why the change? What's the scenario?) Add support for Dependency Injection with ITextEmbeddingGenerator See microsoft/kernel-memory#334. This PR replaces microsoft/kernel-memory#444 so that it excludes unnecessary changes.
Motivation and Context (Why the change? What's the scenario?)
See #334 .