Skip to content
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

Added event handler for MemoryManager operations. #59

Merged
merged 6 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Previous classification is not required if changes are simple or all belong to t
- Updated sample projects with latest changes.
- Overloaded `AddDefaultDocumentConnectorProvider` and `AddDefaultDocumentContentExtractor` methods with a parameter to pass a function to calculate the length of a text and inject it as a dependency.
- Added Readme file to all solution's projects.
- Added event handler for `MemoryManager` operations.

## [8.1.1]

Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

<PropertyGroup>
<VersionPrefix>8.1.2</VersionPrefix>
<VersionSuffix>preview-10</VersionSuffix>
<VersionSuffix>preview-11</VersionSuffix>
</PropertyGroup>

<!--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</ItemGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\"/>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Encamina.Enmarcha.SemanticKernel.Abstractions.Events;

/// <summary>
/// Represents the event arguments of an <see cref="IMemoryManager.MemoryManagerEvent"/>.
/// </summary>
public sealed class MemoryManagerEventArgs : EventArgs
{
/// <summary>
/// Gets event type.
/// </summary>
public MemoryManagerEventTypes EventType { get; init; }

/// <summary>
/// Gets the memory identifier.
/// </summary>
public string MemoryId { get; init; }

/// <summary>
/// Gets the collection name.
/// </summary>
public string CollectionName { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Encamina.Enmarcha.SemanticKernel.Abstractions.Events;

/// <summary>
/// Represents the specific type of operation from a <see cref="IMemoryManager"/>.
/// </summary>
public enum MemoryManagerEventTypes
{
/// <summary>
/// 'Undefined' event type.
/// </summary>
Undefined = 0,

/// <summary>
/// 'Get' event type.
/// </summary>
Get = 1,

/// <summary>
/// 'Upsert' event type.
/// </summary>
Upsert = 2,

/// <summary>
/// 'UpsertBatch' event type.
/// </summary>
UpsertBatch = 3,

/// <summary>
/// 'Delete' event type.
/// </summary>
Delete = 4,
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Ignore Spelling: Upsert

using Encamina.Enmarcha.SemanticKernel.Abstractions.Events;

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;

Expand All @@ -10,6 +12,12 @@
/// </summary>
public interface IMemoryManager
{
/// <summary>
/// This event is fired when <see cref="IMemoryManager"/> executes an action.
/// The event information is defined in the object <see cref="MemoryManagerEventArgs"/>, and the action types in the object <see cref="MemoryManagerEventTypes"/>.
/// </summary>
event EventHandler<MemoryManagerEventArgs> MemoryManagerEvent;

/// <summary>
/// Gets the instance of the memory store manage by this manager.
/// </summary>
Expand All @@ -21,10 +29,17 @@
/// <param name="memoryId">The memory unique identifier.</param>
/// <param name="collectionName">Name of the collection where the content will be saved.</param>
/// <param name="chunks">List of strings containing the content of the memory.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <param name="kernel"><see cref="Kernel"/> instance object.</param>
/// <param name="metadata">Metadata of the memory.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task UpsertMemoryAsync(string memoryId, string collectionName, IEnumerable<string> chunks, Kernel kernel, IDictionary<string, string> metadata = null, CancellationToken cancellationToken = default);
Task UpsertMemoryAsync(
string memoryId,
string collectionName,
IEnumerable<string> chunks,
Kernel kernel,
IDictionary<string, string> metadata = null,

Check warning on line 41 in src/Encamina.Enmarcha.SemanticKernel.Abstractions/IMemoryManager.cs

View workflow job for this annotation

GitHub Actions / CI

Use the overloading mechanism instead of the optional parameters. (https://rules.sonarsource.com/csharp/RSPEC-2360)

Check warning on line 41 in src/Encamina.Enmarcha.SemanticKernel.Abstractions/IMemoryManager.cs

View workflow job for this annotation

GitHub Actions / CI

Use the overloading mechanism instead of the optional parameters. (https://rules.sonarsource.com/csharp/RSPEC-2360)
CancellationToken cancellationToken = default);

Check warning on line 42 in src/Encamina.Enmarcha.SemanticKernel.Abstractions/IMemoryManager.cs

View workflow job for this annotation

GitHub Actions / CI

Use the overloading mechanism instead of the optional parameters. (https://rules.sonarsource.com/csharp/RSPEC-2360)

Check warning on line 42 in src/Encamina.Enmarcha.SemanticKernel.Abstractions/IMemoryManager.cs

View workflow job for this annotation

GitHub Actions / CI

Use the overloading mechanism instead of the optional parameters. (https://rules.sonarsource.com/csharp/RSPEC-2360)

/// <summary>
/// Deletes the memory content from a collection.
Expand Down Expand Up @@ -53,6 +68,7 @@
/// The <c>key</c> in the dictionary must contain a unique identifier for the content of the memory,
/// and the <c>value</c> of the dictionary must provide the memory content (chunks and metadata).
/// </param>
/// <param name="kernel"><see cref="Kernel"/> object instance.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The unique identifiers for the memory records (not necessarily the same as the unique identifier of the memory content).</returns>
IAsyncEnumerable<string> BatchUpsertMemoriesAsync(string collectionName, IDictionary<string, MemoryContent> memoryContents, Kernel kernel, CancellationToken cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Encamina.Enmarcha.SemanticKernel;
using Encamina.Enmarcha.SemanticKernel.Abstractions;
using Encamina.Enmarcha.SemanticKernel.Abstractions.Events;
using Encamina.Enmarcha.SemanticKernel.Options;

using Microsoft.Extensions.Configuration;
Expand All @@ -18,7 +19,7 @@
/// Adds and configures the <see cref="MemoryManager"/> type as transient service instance of the <see cref="IMemoryManager"/> service to the <see cref="IServiceCollection"/>.
/// </summary>
/// <remarks>
/// This method registers the <see cref="MemoryManager"/> type as a transient service due to its dependency on <see cref="Kernel"/>, which is typically registered as transient or scoped (rarely as singleton).

Check warning on line 22 in src/Encamina.Enmarcha.SemanticKernel/Extensions/IServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 212 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)

Check warning on line 22 in src/Encamina.Enmarcha.SemanticKernel/Extensions/IServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 212 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)
/// </remarks>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
Expand All @@ -29,12 +30,31 @@
return services;
}

/// <summary>
/// Adds and configures the <see cref="MemoryManager"/> type as transient service instance of the <see cref="IMemoryManager"/> service to the <see cref="IServiceCollection"/>.
/// </summary>
/// <remarks>
/// This method registers the <see cref="MemoryManager"/> type as a transient service due to its dependency on <see cref="Kernel"/>, which is typically registered as transient or scoped (rarely as singleton).

Check warning on line 37 in src/Encamina.Enmarcha.SemanticKernel/Extensions/IServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 212 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)

Check warning on line 37 in src/Encamina.Enmarcha.SemanticKernel/Extensions/IServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 212 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)
/// </remarks>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <param name="memoryStorageEventHandler">A delegate to handle events from <see cref="IMemoryManager.MemoryManagerEvent"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddMemoryManager(
this IServiceCollection services,
Action<string, MemoryManagerEventArgs> memoryStorageEventHandler)
{
services.TryAddSingleton(memoryStorageEventHandler);
services.TryAddSingleton<IMemoryManager, MemoryManager>();

return services;
}

/// <summary>
/// Adds and configures an «Ephemeral Memory Store Handler», which removes collections from memory after a configured time of inactivity (thus ephemeral), as a transient
/// instance of the <see cref="IMemoryStoreHandler"/> service to the <see cref="IServiceCollection"/>.
/// </summary>
/// <remarks>
/// This method registers the <see cref="EphemeralMemoryStoreHandlerOptions"/> type as a transient service because of its dependency on <see cref="IMemoryManager"/>s, which depend on <see cref="Kernel"/>, typically a

Check warning on line 57 in src/Encamina.Enmarcha.SemanticKernel/Extensions/IServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 220 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)

Check warning on line 57 in src/Encamina.Enmarcha.SemanticKernel/Extensions/IServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 220 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)
/// transient or scoped service (rarely a singleton).
/// </remarks>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
Expand Down
26 changes: 23 additions & 3 deletions src/Encamina.Enmarcha.SemanticKernel/MemoryManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text.Json;

using Encamina.Enmarcha.SemanticKernel.Abstractions;
using Encamina.Enmarcha.SemanticKernel.Abstractions.Events;

using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
Expand All @@ -26,17 +27,27 @@
/// Initializes a new instance of the <see cref="MemoryManager"/> class.
/// </summary>
/// <param name="memoryStore">A valid instance of a <see cref="IMemoryStore"/> to manage.</param>
public MemoryManager(IMemoryStore memoryStore, ILogger<MemoryManager> logger)
/// <param name="memoryStorageEventHandler">A delegate to handle events from <see cref="MemoryManagerEvent"/>.</param>
/// <param name="logger">Log service.</param>
public MemoryManager(IMemoryStore memoryStore, ILogger<MemoryManager> logger, Action<object, MemoryManagerEventArgs> memoryStorageEventHandler)
{
this.logger = logger;
MemoryStore = memoryStore;
this.logger = logger;

if (memoryStorageEventHandler is not null)
{
MemoryManagerEvent += (sender, args) => memoryStorageEventHandler?.Invoke(sender ?? nameof(MemoryManagerEventTypes.Undefined), args);
}
}

/// <inheritdoc/>
public event EventHandler<MemoryManagerEventArgs> MemoryManagerEvent;

/// <inheritdoc/>
public IMemoryStore MemoryStore { get; init; }

/// <inheritdoc/>
public virtual async Task UpsertMemoryAsync(string memoryId, string collectionName, IEnumerable<string> chunks, Kernel kernel, IDictionary<string, string> metadata = null, CancellationToken cancellationToken = default)

Check warning on line 50 in src/Encamina.Enmarcha.SemanticKernel/MemoryManager.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 222 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)

Check warning on line 50 in src/Encamina.Enmarcha.SemanticKernel/MemoryManager.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 222 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)
{
var memoryChunkSize = await GetChunkSize(memoryId, collectionName, cancellationToken);

Expand All @@ -46,6 +57,8 @@
}

await SaveChunks(memoryId, collectionName, chunks, metadata, kernel, cancellationToken);

MemoryManagerEvent?.Invoke(this, new() { EventType = MemoryManagerEventTypes.Upsert, MemoryId = memoryId, CollectionName = collectionName });
}

/// <inheritdoc/>
Expand All @@ -56,6 +69,8 @@
if (chunkSize > 0)
{
await DeleteMemoryAsync(memoryId, collectionName, chunkSize, cancellationToken);

MemoryManagerEvent?.Invoke(this, new() { EventType = MemoryManagerEventTypes.Delete, MemoryId = memoryId, CollectionName = collectionName });
}
}

Expand All @@ -72,6 +87,8 @@
var memoryRecords = await MemoryStore.GetBatchAsync(collectionName, Enumerable.Range(0, chunkSize).Select(i => BuildMemoryIdentifier(memoryId, i)), cancellationToken: cancellationToken)
.ToListAsync(cancellationToken);

MemoryManagerEvent?.Invoke(this, new() { EventType = MemoryManagerEventTypes.Get, MemoryId = memoryId, CollectionName = collectionName });

return new MemoryContent
{
Metadata = JsonSerializer.Deserialize<Dictionary<string, string>>(memoryRecords[0].Metadata.AdditionalMetadata),
Expand All @@ -80,7 +97,7 @@
}

/// <inheritdoc/>
public virtual async IAsyncEnumerable<string> BatchUpsertMemoriesAsync(string collectionName, IDictionary<string, MemoryContent> memoryContents, Kernel kernel, [EnumeratorCancellation] CancellationToken cancellationToken)

Check warning on line 100 in src/Encamina.Enmarcha.SemanticKernel/MemoryManager.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 225 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)

Check warning on line 100 in src/Encamina.Enmarcha.SemanticKernel/MemoryManager.cs

View workflow job for this annotation

GitHub Actions / CI

Split this 225 characters long line (which is greater than 200 authorized). (https://rules.sonarsource.com/csharp/RSPEC-103)
{
var memoryRecords = new Collection<MemoryRecord>();

Expand All @@ -107,7 +124,10 @@

await foreach (var item in memoryRecordsUniqueIdentifiers)
{
logger.LogInformation($@"Processed memory record {item}.");
MemoryManagerEvent?.Invoke(this, new() { EventType = MemoryManagerEventTypes.UpsertBatch, MemoryId = item, CollectionName = collectionName });

logger.LogInformation(@"Processed memory record {item}.", item);

yield return item;
}
}
Expand Down