Skip to content

Commit 07aee99

Browse files
committed
Throw exception when mixing volatile and persistent data
1 parent cafb63e commit 07aee99

File tree

5 files changed

+71
-12
lines changed

5 files changed

+71
-12
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project>
33
<PropertyGroup>
44
<!-- Central version prefix - applies to all nuget packages. -->
5-
<Version>0.95.0</Version>
5+
<Version>0.96.0</Version>
66

77
<!-- C# lang version, https://learn.microsoft.com/dotnet/csharp/whats-new -->
88
<LangVersion>12</LangVersion>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"profiles": {
3+
"console": {
4+
"commandName": "Project",
5+
"launchBrowser": false,
6+
"environmentVariables": {
7+
"ASPNETCORE_ENVIRONMENT": "Development"
8+
}
9+
}
10+
}
11+
}

service/Abstractions/IKernelMemoryBuilder.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,20 @@ public interface IKernelMemoryBuilder
2626
/// <summary>
2727
/// Build the memory instance, using defaults and the provided dependencies
2828
/// and overrides. Depending on the dependencies provided, the resulting
29-
/// memory might use either an synchronous or asynchronous pipeline.
29+
/// memory might use either a synchronous or asynchronous pipeline.
3030
/// </summary>
31-
public IKernelMemory Build();
31+
/// <param name="options">Options for the build process</param>
32+
/// <returns>A memory instance</returns>
33+
public IKernelMemory Build(KernelMemoryBuilderBuildOptions? options = null);
3234

3335
/// <summary>
3436
/// Build a specific type of memory instance, e.g. explicitly choosing
3537
/// between synchronous or asynchronous (queue based) pipeline.
3638
/// </summary>
3739
/// <typeparam name="T">Type of memory derived from IKernelMemory</typeparam>
40+
/// <param name="options">Options for the build process</param>
3841
/// <returns>A memory instance</returns>
39-
public T Build<T>() where T : class, IKernelMemory;
42+
public T Build<T>(KernelMemoryBuilderBuildOptions? options = null) where T : class, IKernelMemory;
4043

4144
/// <summary>
4245
/// Add a singleton to the builder service collection pool.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
namespace Microsoft.KernelMemory;
4+
5+
public sealed class KernelMemoryBuilderBuildOptions
6+
{
7+
public bool AllowMixingVolatileAndPersistentData { get; set; } = false;
8+
}

service/Core/KernelMemoryBuilder.cs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections.Generic;
5+
using System.Linq;
56
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.KernelMemory.AI;
78
using Microsoft.KernelMemory.AppBuilders;
@@ -116,16 +117,16 @@ public KernelMemoryBuilder(IServiceCollection? hostServiceCollection = null)
116117
}
117118

118119
///<inheritdoc />
119-
public IKernelMemory Build()
120+
public IKernelMemory Build(KernelMemoryBuilderBuildOptions? options = null)
120121
{
121122
var type = this.GetBuildType();
122123
switch (type)
123124
{
124125
case ClientTypes.SyncServerless:
125-
return this.BuildServerlessClient();
126+
return this.BuildServerlessClient(options);
126127

127128
case ClientTypes.AsyncService:
128-
return this.BuildAsyncClient();
129+
return this.BuildAsyncClient(options);
129130

130131
case ClientTypes.Undefined:
131132
throw new KernelMemoryException("Missing dependencies or insufficient configuration provided. " +
@@ -138,11 +139,11 @@ public IKernelMemory Build()
138139
}
139140

140141
///<inheritdoc />
141-
public T Build<T>() where T : class, IKernelMemory
142+
public T Build<T>(KernelMemoryBuilderBuildOptions? options = null) where T : class, IKernelMemory
142143
{
143144
if (typeof(T) == typeof(MemoryServerless))
144145
{
145-
if (this.BuildServerlessClient() is not T result)
146+
if (this.BuildServerlessClient(options) is not T result)
146147
{
147148
throw new InvalidOperationException($"Unable to instantiate '{typeof(MemoryServerless)}'. The instance is NULL.");
148149
}
@@ -152,7 +153,7 @@ public T Build<T>() where T : class, IKernelMemory
152153

153154
if (typeof(T) == typeof(MemoryService))
154155
{
155-
if (this.BuildAsyncClient() is not T result)
156+
if (this.BuildAsyncClient(options) is not T result)
156157
{
157158
throw new InvalidOperationException($"Unable to instantiate '{typeof(MemoryService)}'. The instance is NULL.");
158159
}
@@ -226,7 +227,7 @@ private static void CopyServiceCollection(
226227
}
227228
}
228229

229-
private MemoryServerless BuildServerlessClient()
230+
private MemoryServerless BuildServerlessClient(KernelMemoryBuilderBuildOptions? options)
230231
{
231232
try
232233
{
@@ -240,6 +241,7 @@ private MemoryServerless BuildServerlessClient()
240241

241242
// Recreate the service provider, in order to have the latest dependencies just configured
242243
serviceProvider = this._memoryServiceCollection.BuildServiceProvider();
244+
this.CheckStoragePersistence(options, serviceProvider);
243245
var memoryClientInstance = ActivatorUtilities.CreateInstance<MemoryServerless>(serviceProvider);
244246

245247
// Load handlers in the memory client
@@ -257,7 +259,7 @@ private MemoryServerless BuildServerlessClient()
257259
}
258260
}
259261

260-
private MemoryService BuildAsyncClient()
262+
private MemoryService BuildAsyncClient(KernelMemoryBuilderBuildOptions? options)
261263
{
262264
// Add handlers to DI service collection
263265
if (this._useDefaultHandlers)
@@ -282,9 +284,44 @@ private MemoryService BuildAsyncClient()
282284

283285
// Recreate the service provider, in order to have the latest dependencies just configured
284286
serviceProvider = this._memoryServiceCollection.BuildServiceProvider();
287+
this.CheckStoragePersistence(options, serviceProvider);
285288
return ActivatorUtilities.CreateInstance<MemoryService>(serviceProvider);
286289
}
287290

291+
private void CheckStoragePersistence(KernelMemoryBuilderBuildOptions? options, ServiceProvider serviceProvider)
292+
{
293+
if (options is { AllowMixingVolatileAndPersistentData: true }) { return; }
294+
295+
ServiceDescriptor docStoreType = this._memoryServiceCollection.Last<ServiceDescriptor>(x => x.ServiceType == typeof(IDocumentStorage));
296+
ServiceDescriptor memStoreType = this._memoryServiceCollection.Last<ServiceDescriptor>(x => x.ServiceType == typeof(IMemoryDb));
297+
SimpleFileStorageConfig? simpleFileStorageConfig = serviceProvider.GetService<SimpleFileStorageConfig>();
298+
SimpleVectorDbConfig? simpleVectorDbConfig = serviceProvider.GetService<SimpleVectorDbConfig>();
299+
SimpleTextDbConfig? simpleTextDbConfig = serviceProvider.GetService<SimpleTextDbConfig>();
300+
301+
bool persistentDocStore = docStoreType.ImplementationType != typeof(SimpleFileStorage) || simpleFileStorageConfig?.StorageType == FileSystemTypes.Disk;
302+
bool persistentMemStore = memStoreType.ImplementationType != typeof(SimpleVectorDb) && memStoreType.ImplementationType != typeof(SimpleTextDb)
303+
|| (memStoreType.ImplementationType == typeof(SimpleVectorDb) && simpleVectorDbConfig?.StorageType == FileSystemTypes.Disk)
304+
|| (memStoreType.ImplementationType == typeof(SimpleTextDb) && simpleTextDbConfig?.StorageType == FileSystemTypes.Disk);
305+
306+
if (persistentMemStore && !persistentDocStore)
307+
{
308+
throw new ConfigurationException(
309+
"Using a persistent vector store with a volatile document store will lead to duplicate memory records over multiple executions. " +
310+
"Set up Kernel Memory to use a persistent document store like Azure Blobs, AWS S3, SimpleFileStorage on disk, etc. " +
311+
$"Otherwise, use {nameof(KernelMemoryBuilderBuildOptions)}.{nameof(KernelMemoryBuilderBuildOptions.AllowMixingVolatileAndPersistentData)} " +
312+
"to suppress this exception. ");
313+
}
314+
315+
if (persistentDocStore && !persistentMemStore)
316+
{
317+
throw new ConfigurationException(
318+
"Using a volatile vector store with a persistent document store will lead to missing memory records over multiple executions. " +
319+
"Set up Kernel Memory to use a persistent vector store like Azure AI Search, Postgres, Qdrant, SimpleVectorDb on disk, etc. " +
320+
$"Otherwise, use {nameof(KernelMemoryBuilderBuildOptions)}.{nameof(KernelMemoryBuilderBuildOptions.AllowMixingVolatileAndPersistentData)} " +
321+
"to suppress this exception. ");
322+
}
323+
}
324+
288325
private KernelMemoryBuilder CompleteServerlessClient(ServiceProvider serviceProvider)
289326
{
290327
this.UseDefaultSearchClientIfNecessary(serviceProvider);

0 commit comments

Comments
 (0)