Skip to content

Commit

Permalink
Merge pull request #59 from Encamina/@fbelizon/event-handling-in-memo…
Browse files Browse the repository at this point in the history
…ry-storage

Added event handler for `MemoryManager` operations.
  • Loading branch information
fjbelizon authored Jan 23, 2024
2 parents e82f260 + 8834a2f commit 4498a87
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 7 deletions.
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 @@ namespace Encamina.Enmarcha.SemanticKernel.Abstractions;
/// </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 @@ public interface IMemoryManager
/// <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)
CancellationToken cancellationToken = default);

/// <summary>
/// Deletes the memory content from a collection.
Expand Down Expand Up @@ -53,6 +68,7 @@ public interface IMemoryManager
/// 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 Down Expand Up @@ -29,6 +30,25 @@ public static IServiceCollection AddMemoryManager(this IServiceCollection servic
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).
/// </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"/>.
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,12 +27,22 @@ public class MemoryManager : IMemoryManager
/// 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; }

Expand All @@ -46,6 +57,8 @@ public virtual async Task UpsertMemoryAsync(string memoryId, string collectionNa
}

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 @@ public virtual async Task DeleteMemoryAsync(string memoryId, string collectionNa
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 @@ public virtual async Task<MemoryContent> GetMemoryAsync(string memoryId, string
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 Down Expand Up @@ -107,7 +124,10 @@ public virtual async IAsyncEnumerable<string> BatchUpsertMemoriesAsync(string co

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

0 comments on commit 4498a87

Please sign in to comment.