Skip to content

Commit

Permalink
Merge pull request #2026 from solliancenet/gg-cache-resources
Browse files Browse the repository at this point in the history
Implement in-memory cache for resource providers
  • Loading branch information
ciprianjichici authored Dec 11, 2024
2 parents 316c8be + 6aafd5a commit f284118
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/release-notes/breaking-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The following new App Configuration settings are required:
|`FoundationaLLM:UserPortal:Configuration:ShowLastConversationOnStartup` | `false` | If `true`, the last conversation will be displayed when the user logs in. Otherwise, a new conversation placeholder appears on page load. |
|`FoundationaLLM:UserPortal:Configuration:ShowMessageTokens` | `true` | If `true`, the number of consumed tokens on agent and user messages will appear. |
|`FoundationaLLM:UserPortal:Configuration:ShowViewPrompt` | `true` | If `true`, the "View Prompt" button on agent messages will appear. |
|`FoundationaLLM:Instance:EnableResourceProvidersCache` | `false` | If `true`, the caching of resource providers will be enabled. |

#### Agent Tool configuration changes

Expand Down
8 changes: 8 additions & 0 deletions src/dotnet/Common/Constants/Data/AppConfiguration.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
"value": "",
"content_type": "",
"first_version": "0.8.0"
},
{
"name": "EnableResourceProvidersCache",
"description": "Enable caching for resource providers.",
"secret": "",
"value": "false",
"content_type": "",
"first_version": "0.9.1"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using FoundationaLLM.Common.Models.ResourceProviders;

namespace FoundationaLLM.Common.Interfaces
{
/// <summary>
/// Provides the resource caching services used by FoundationaLLM resource providers.
/// </summary>
public interface IResourceProviderResourceCacheService
{
/// <summary>
/// Tries to get a resource value identified by a resource reference from the cache.
/// </summary>
/// <typeparam name="T">The type of resource value to be retrieved.</typeparam>
/// <param name="resourceReference">The <see cref="ResourceReference"/> used as a key in the cache.</param>
/// <param name="resourceValue">The resource value to be retrieved.</param>
/// <returns><see langword="true"/> is the resource value was found in the cache, <see langword="false"/> otherwise.</returns>
bool TryGetValue<T>(ResourceReference resourceReference, out T? resourceValue) where T: ResourceBase;

/// <summary>
/// Sets a resource value identified by a resource reference in the cache.
/// </summary>
/// <typeparam name="T">The type of resource value to be set.</typeparam>
/// <param name="resourceReference">The <see cref="ResourceReference"/> used as a key in the cache.</param>
/// <param name="resourceValue">The resource value to be set.</param>
void SetValue<T>(ResourceReference resourceReference, T resourceValue) where T : ResourceBase;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,10 @@ public class InstanceSettings
/// The Regex pattern used to validate the values allowed as User Principal Name (UPN) substitutes in the X-USER-IDENTITY header.
/// </summary>
public string? IdentitySubstitutionUserPrincipalNamePattern { get; set; }

/// <summary>
/// Enabling caching for resource providers.
/// </summary>
public bool EnableResourceProvidersCache { get; set; } = false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using FoundationaLLM.Common.Interfaces;
using FoundationaLLM.Common.Models.ResourceProviders;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;

namespace FoundationaLLM.Common.Services.ResourceProviders
{
/// <summary>
/// Provides the resource caching services used by FoundationaLLM resource providers.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> used to log information.</param>
public class ResourceProviderResourceCacheService(
ILogger logger) : IResourceProviderResourceCacheService
{
private readonly ILogger _logger = logger;

private readonly IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 10000, // Limit cache size to 5000 resources.
ExpirationScanFrequency = TimeSpan.FromMinutes(5) // Scan for expired items every five minutes.
});
private readonly MemoryCacheEntryOptions _cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(60)) // Cache entries are valid for 60 minutes.
.SetSlidingExpiration(TimeSpan.FromMinutes(30)) // Reset expiration time if accessed within 5 minutes.
.SetSize(1); // Each cache entry is a single resource.

/// <inheritdoc/>
public void SetValue<T>(ResourceReference resourceReference, T resourceValue) where T : ResourceBase
{
try
{
_cache.Set<T>(GetCacheKey(resourceReference), resourceValue, _cacheEntryOptions);
_logger.LogInformation("The resource {ResourceName} of type {ResourceType} has been set in the cache.",
resourceReference.Name,
resourceReference.Type);
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an error setting the resource {ResourceName} of type {ResourceType} in the cache.",
resourceReference.Name,
resourceReference.Type);
}
}

/// <inheritdoc/>
public bool TryGetValue<T>(ResourceReference resourceReference, out T? resourceValue) where T : ResourceBase
{
resourceValue = default;

try
{
if (_cache.TryGetValue<T>(GetCacheKey(resourceReference), out T? cachedValue)
&& cachedValue != null)
{
resourceValue = cachedValue;
_logger.LogInformation("The resource {ResourceName} of type {ResourceType} has been retrieved from the cache.",
resourceReference.Name,
resourceReference.Type);
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an error getting the resource {ResourceName} of type {ResourceType} from the cache.",
resourceReference.Name,
resourceReference.Type);
}

return false;
}

private string GetCacheKey(ResourceReference resourceReference) =>
$"{resourceReference.Type}|{resourceReference.Name}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using FoundationaLLM.Common.Models.ResourceProviders;
using FoundationaLLM.Common.Services.Events;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Immutable;
Expand All @@ -37,6 +38,8 @@ public class ResourceProviderServiceBase<TResourceReference> : IResourceProvider
private readonly bool _useInternalReferencesStore;
private readonly SemaphoreSlim _lock = new(1, 1);

private readonly IResourceProviderResourceCacheService? _resourceCache;

/// <summary>
/// The resource reference store used by the resource provider.
/// </summary>
Expand Down Expand Up @@ -143,6 +146,9 @@ public ResourceProviderServiceBase(
_eventNamespacesToSubscribe = eventNamespacesToSubscribe;
_useInternalReferencesStore = useInternalReferencesStore;

if (_instanceSettings.EnableResourceProvidersCache)
_resourceCache = new ResourceProviderResourceCacheService(_logger);

_allowedResourceProviders = [_name];
_allowedResourceTypes = GetResourceTypes();

Expand Down Expand Up @@ -996,6 +1002,12 @@ protected async Task<List<ResourceProviderGetResult<T>>> LoadResources<T>(
$"The resource reference {resourceReference.Name} is not of the expected type {typeof(T).Name}.",
StatusCodes.Status400BadRequest);


if (_resourceCache != null
&& _resourceCache.TryGetValue<T>(resourceReference, out T? cachedResource)
&& cachedResource != null)
return cachedResource;

if (await _storageService.FileExistsAsync(_storageContainerName, resourceReference.Filename, default))
{
var fileContent =
Expand All @@ -1009,6 +1021,8 @@ protected async Task<List<ResourceProviderGetResult<T>>> LoadResources<T>(
?? throw new ResourceProviderException($"Failed to load the resource {resourceReference.Name}. Its content file might be corrupt.",
StatusCodes.Status500InternalServerError);

_resourceCache?.SetValue<T>(resourceReference, resourceObject);

return resourceObject;
}
catch (Exception ex)
Expand Down Expand Up @@ -1038,6 +1052,11 @@ protected async Task<List<ResourceProviderGetResult<T>>> LoadResources<T>(
?? throw new ResourceProviderException($"Could not locate the {resourceName} resource.",
StatusCodes.Status404NotFound);

if (_resourceCache != null
&& _resourceCache.TryGetValue<T>(resourceReference, out T? cachedResource)
&& cachedResource != null)
return cachedResource;

if (await _storageService.FileExistsAsync(_storageContainerName, resourceReference.Filename, default))
{
var fileContent =
Expand All @@ -1048,6 +1067,8 @@ protected async Task<List<ResourceProviderGetResult<T>>> LoadResources<T>(
?? throw new ResourceProviderException($"Failed to load the resource {resourceReference.Name}. Its content file might be corrupt.",
StatusCodes.Status400BadRequest);

_resourceCache?.SetValue<T>(resourceReference, resourceObject);

return resourceObject;
}

Expand Down Expand Up @@ -1086,6 +1107,9 @@ await _storageService.WriteFileAsync(
default);

await _resourceReferenceStore!.AddResourceReference(resourceReference);

// Add resource to cache if caching is enabled.
_resourceCache?.SetValue<T>(resourceReference, resource);
}
finally
{
Expand Down Expand Up @@ -1200,6 +1224,9 @@ await _storageService.WriteFileAsync(
JsonSerializer.Serialize<T>(resource, _serializerSettings),
default,
default);

// Update resource to cache if caching is enabled.
_resourceCache?.SetValue<T>(resourceReference, resource);
}
finally
{
Expand Down
7 changes: 7 additions & 0 deletions src/dotnet/Common/Templates/AppConfigurationKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ public static class AppConfigurationKeys
/// </summary>
public const string FoundationaLLM_Instance_IdentitySubstitutionUserPrincipalNamePattern =
"FoundationaLLM:Instance:IdentitySubstitutionUserPrincipalNamePattern";

/// <summary>
/// The app configuration key for the FoundationaLLM:Instance:EnableResourceProvidersCache setting.
/// <para>Value description:<br/>Enable caching for resource providers.</para>
/// </summary>
public const string FoundationaLLM_Instance_EnableResourceProvidersCache =
"FoundationaLLM:Instance:EnableResourceProvidersCache";

#endregion

Expand Down
7 changes: 7 additions & 0 deletions src/dotnet/Common/Templates/appconfig.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
"content_type": "",
"tags": {}
},
{
"key": "FoundationaLLM:Instance:EnableResourceProvidersCache",
"value": "false",
"label": null,
"content_type": "",
"tags": {}
},
{
"key": "FoundationaLLM:Configuration:KeyVaultURI",
"value": "${env:AZURE_KEY_VAULT_ENDPOINT}",
Expand Down

0 comments on commit f284118

Please sign in to comment.