diff --git a/docs/deployment/app-configuration-values.md b/docs/deployment/app-configuration-values.md
index ffc59c3d2d..a98302cd58 100644
--- a/docs/deployment/app-configuration-values.md
+++ b/docs/deployment/app-configuration-values.md
@@ -6,6 +6,7 @@ FoundationaLLM uses Azure App Configuration to store configuration values, Key V
| Key | Default Value | Description |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `FoundationaLLM:Instance:Id` | Generated GUID | The value should be a GUID represents a unique instance of the FoundationaLLM instance. |
| `FoundationaLLM:AgentHub:AgentMetadata:StorageContainer` | agents | |
| `FoundationaLLM:AgentHub:StorageManager:BlobStorage:ConnectionString` | Key Vault secret name: `foundationallm-agenthub-storagemanager-blobstorage-connectionstring` | This is a Key Vault reference. |
| `FoundationaLLM:APIs:AgentFactoryAPI:APIKey` | Key Vault secret name: `foundationallm-apis-agentfactoryapi-apikey` | This is a Key Vault reference. |
diff --git a/src/FoundationaLLM.sln b/src/FoundationaLLM.sln
index 0406170311..29c5943860 100644
--- a/src/FoundationaLLM.sln
+++ b/src/FoundationaLLM.sln
@@ -81,7 +81,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Management", "dotnet\Manage
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagementAPI", "dotnet\ManagementAPI\ManagementAPI.csproj", "{2D54392A-8D86-4F54-9993-FB3B6C4C090E}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SemanticKernel", "dotnet\SemanticKernel\SemanticKernel.csproj", "{CDB843FE-108B-435A-BF17-68052C64F500}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticKernel", "dotnet\SemanticKernel\SemanticKernel.csproj", "{CDB843FE-108B-435A-BF17-68052C64F500}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agent", "dotnet\Agent\Agent.csproj", "{9BE97AEC-032C-454B-BDAA-29418A769237}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -200,6 +202,10 @@ Global
{CDB843FE-108B-435A-BF17-68052C64F500}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CDB843FE-108B-435A-BF17-68052C64F500}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CDB843FE-108B-435A-BF17-68052C64F500}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9BE97AEC-032C-454B-BDAA-29418A769237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9BE97AEC-032C-454B-BDAA-29418A769237}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9BE97AEC-032C-454B-BDAA-29418A769237}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9BE97AEC-032C-454B-BDAA-29418A769237}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -239,6 +245,7 @@ Global
{46FB5F1B-57C6-4CA3-B626-887DF6D806DD} = {B6DC1190-2873-44A3-85B3-63D7BDE99231}
{2D54392A-8D86-4F54-9993-FB3B6C4C090E} = {B6DC1190-2873-44A3-85B3-63D7BDE99231}
{CDB843FE-108B-435A-BF17-68052C64F500} = {B6DC1190-2873-44A3-85B3-63D7BDE99231}
+ {9BE97AEC-032C-454B-BDAA-29418A769237} = {B6DC1190-2873-44A3-85B3-63D7BDE99231}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FF5DE858-4B85-4EE8-8A6D-46E8E4FBA078}
diff --git a/src/dotnet/Agent/Agent.csproj b/src/dotnet/Agent/Agent.csproj
new file mode 100644
index 0000000000..831058821e
--- /dev/null
+++ b/src/dotnet/Agent/Agent.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net8.0
+ enable
+ enable
+ FoundationaLLM.Agent
+ FoundationaLLM.Agent
+ True
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/dotnet/Agent/Models/Metadata/AgentBase.cs b/src/dotnet/Agent/Models/Metadata/AgentBase.cs
new file mode 100644
index 0000000000..2df634571f
--- /dev/null
+++ b/src/dotnet/Agent/Models/Metadata/AgentBase.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using FoundationaLLM.Common.Models.Metadata;
+using FoundationaLLM.Common.Models.ResourceProvider;
+using Newtonsoft.Json;
+
+namespace FoundationaLLM.Agent.Models.Metadata
+{
+ ///
+ /// Base agent metadata model.
+ ///
+ public class AgentBase : ResourceBase
+ {
+ ///
+ /// The agent's language model configuration.
+ ///
+ [JsonProperty("language_model")]
+ public LanguageModel? LanguageModel { get; set; }
+ ///
+ /// Indicates whether sessions are enabled for the agent.
+ ///
+ [JsonProperty("sessions_enabled")]
+ public bool SessionsEnabled { get; set; }
+ ///
+ /// The agent's conversation history configuration.
+ ///
+ [JsonProperty("conversation_history")]
+ public ConversationHistory? ConversationHistory { get; set; }
+ ///
+ /// The agent's Gatekeeper configuration.
+ ///
+ [JsonProperty("gatekeeper")]
+ public Gatekeeper? Gatekeeper { get; set; }
+ ///
+ /// The agent's LLM orchestrator type.
+ ///
+ [JsonProperty("orchestrator")]
+ public string? Orchestrator { get; set; }
+ ///
+ /// The agent's prompt.
+ ///
+ [JsonProperty("prompt")]
+ public string? Prompt { get; set; }
+ }
+
+ ///
+ /// Agent conversation history settings.
+ ///
+ public class ConversationHistory
+ {
+ ///
+ /// Indicates whether the conversation history is enabled.
+ ///
+ [JsonProperty("enabled")]
+ public bool Enabled { get; set; }
+ ///
+ /// The maximum number of turns to store in the conversation history.
+ ///
+ [JsonProperty("max_history")]
+ public int MaxHistory { get; set; }
+ }
+
+ ///
+ /// Agent Gatekeeper settings.
+ ///
+ public class Gatekeeper
+ {
+ ///
+ /// Indicates whether to abide by or override the system settings for the Gatekeeper.
+ ///
+ [JsonProperty("use_system_setting")]
+ public bool UseSystemSetting { get; set; }
+ ///
+ /// If is false, provides Gatekeeper feature selection.
+ ///
+ [JsonProperty("options")]
+ public string[]? Options { get; set; }
+ }
+
+}
diff --git a/src/dotnet/Agent/Models/Metadata/KnowledgeManagementAgent.cs b/src/dotnet/Agent/Models/Metadata/KnowledgeManagementAgent.cs
new file mode 100644
index 0000000000..8a50547803
--- /dev/null
+++ b/src/dotnet/Agent/Models/Metadata/KnowledgeManagementAgent.cs
@@ -0,0 +1,32 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FoundationaLLM.Agent.Models.Metadata
+{
+ ///
+ /// The Knowledge Management agent metadata model.
+ ///
+ public class KnowledgeManagementAgent : AgentBase
+ {
+ ///
+ /// The vectorization indexing profile resource path.
+ ///
+ [JsonProperty("indexing_profile")]
+ public string? IndexingProfile { get; set; }
+ ///
+ /// The vectorization embedding profile resource path.
+ ///
+ [JsonProperty("embedding_profile")]
+ public string? EmbeddingProfile { get; set; }
+
+ ///
+ /// Set default property values.
+ ///
+ public KnowledgeManagementAgent() =>
+ Type = Common.Constants.AgentTypes.KnowledgeManagement;
+ }
+}
diff --git a/src/dotnet/Agent/Models/Resources/AgentReference.cs b/src/dotnet/Agent/Models/Resources/AgentReference.cs
new file mode 100644
index 0000000000..3335feb037
--- /dev/null
+++ b/src/dotnet/Agent/Models/Resources/AgentReference.cs
@@ -0,0 +1,37 @@
+using FoundationaLLM.Agent.Models.Metadata;
+using FoundationaLLM.Common.Constants;
+using FoundationaLLM.Common.Exceptions;
+using Newtonsoft.Json;
+
+namespace FoundationaLLM.Agent.Models.Resources
+{
+ ///
+ /// Provides details about an agent.
+ ///
+ public class AgentReference
+ {
+ ///
+ /// The name of the agent.
+ ///
+ public required string Name { get; set; }
+ ///
+ /// The filename of the agent.
+ ///
+ public required string Filename { get; set; }
+ ///
+ /// The type of the agent.
+ ///
+ public required string Type { get; set; }
+
+ ///
+ /// The object type of the agent.
+ ///
+ [JsonIgnore]
+ public Type AgentType =>
+ Type switch
+ {
+ AgentTypes.KnowledgeManagement => typeof(KnowledgeManagementAgent),
+ _ => throw new ResourceProviderException($"The agent type {Type} is not supported.")
+ };
+ }
+}
diff --git a/src/dotnet/Agent/Models/Resources/AgentReferenceStore.cs b/src/dotnet/Agent/Models/Resources/AgentReferenceStore.cs
new file mode 100644
index 0000000000..c8e1e46170
--- /dev/null
+++ b/src/dotnet/Agent/Models/Resources/AgentReferenceStore.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FoundationaLLM.Agent.Models.Resources
+{
+ ///
+ /// Models the content of the agent reference store managed by the FoundationaLLM.Agent resource provider.
+ ///
+ public class AgentReferenceStore
+ {
+ ///
+ /// The list of all agents registered in the system.
+ ///
+ public required List AgentReferences { get; set; }
+
+ ///
+ /// Creates a string-based dictionary of values from the current object.
+ ///
+ /// The string-based dictionary of values from the current object.
+ public Dictionary ToDictionary() =>
+ AgentReferences.ToDictionary(ar => ar.Name);
+
+ ///
+ /// Creates a new instance of the from a dictionary.
+ ///
+ /// A string-based dictionary of values.
+ /// The object created from the dictionary.
+ public static AgentReferenceStore FromDictionary(Dictionary dictionary) =>
+ new AgentReferenceStore
+ {
+ AgentReferences = dictionary.Values.ToList()
+ };
+ }
+}
diff --git a/src/dotnet/Agent/ResourceProviders/AgentResourceProviderService.cs b/src/dotnet/Agent/ResourceProviders/AgentResourceProviderService.cs
new file mode 100644
index 0000000000..5188cc69ae
--- /dev/null
+++ b/src/dotnet/Agent/ResourceProviders/AgentResourceProviderService.cs
@@ -0,0 +1,235 @@
+using System.Collections.Concurrent;
+using System.Text;
+using FoundationaLLM.Agent.Models.Metadata;
+using FoundationaLLM.Agent.Models.Resources;
+using FoundationaLLM.Common.Constants;
+using FoundationaLLM.Common.Exceptions;
+using FoundationaLLM.Common.Interfaces;
+using FoundationaLLM.Common.Models.Configuration.Instance;
+using FoundationaLLM.Common.Services.ResourceProviders;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+
+namespace FoundationaLLM.Agent.ResourceProviders
+{
+ ///
+ /// Implements the FoundationaLLM.Agent resource provider.
+ ///
+ public class AgentResourceProviderService(
+ IOptions instanceOptions,
+ [FromKeyedServices(DependencyInjectionKeys.FoundationaLLM_Agent_ResourceProviderService)] IStorageService storageService,
+ ILogger logger)
+ : ResourceProviderServiceBase(
+ instanceOptions.Value,
+ storageService,
+ logger)
+ {
+ private readonly JsonSerializerSettings _serializerSettings = new()
+ {
+ TypeNameHandling = TypeNameHandling.Auto,
+ Formatting = Formatting.Indented
+ };
+ private ConcurrentDictionary _agentReferences = [];
+
+ private const string AGENT_REFERENCES_FILE_NAME = "_agent-references.json";
+ private const string AGENT_REFERENCES_FILE_PATH = $"/{ResourceProviderNames.FoundationaLLM_Agent}/_agent-references.json";
+
+ ///
+ protected override string _name => ResourceProviderNames.FoundationaLLM_Agent;
+
+ ///
+ protected override Dictionary _resourceTypes =>
+ new()
+ {
+ {
+ AgentResourceTypeNames.Agents,
+ new ResourceTypeDescriptor(AgentResourceTypeNames.Agents)
+ },
+ {
+ AgentResourceTypeNames.AgentReferences,
+ new ResourceTypeDescriptor(AgentResourceTypeNames.AgentReferences)
+ }
+ };
+
+ ///
+ protected override async Task InitializeInternal()
+ {
+ _logger.LogInformation("Starting to initialize the {ResourceProvider} resource provider...", _name);
+
+ if (await _storageService.FileExistsAsync(_storageContainerName, AGENT_REFERENCES_FILE_PATH, default))
+ {
+ var fileContent = await _storageService.ReadFileAsync(_storageContainerName, AGENT_REFERENCES_FILE_PATH, default);
+ var agentReferenceStore = JsonConvert.DeserializeObject(
+ Encoding.UTF8.GetString(fileContent.ToArray()));
+
+ _agentReferences = new ConcurrentDictionary(
+ agentReferenceStore!.ToDictionary());
+ }
+ else
+ {
+ await _storageService.WriteFileAsync(
+ _storageContainerName,
+ AGENT_REFERENCES_FILE_PATH,
+ JsonConvert.SerializeObject(new AgentReferenceStore { AgentReferences = [] }),
+ default,
+ default);
+ }
+
+ _logger.LogInformation("The {ResourceProvider} resource provider was successfully initialized.", _name);
+ }
+
+ ///
+ protected override async Task GetResourcesAsyncInternal(List instances) =>
+ instances[0].ResourceType switch
+ {
+ AgentResourceTypeNames.Agents => await LoadAndSerializeAgents(instances[0]),
+ _ => throw new ResourceProviderException($"The resource type {instances[0].ResourceType} is not supported by the {_name} resource manager.")
+ };
+
+ ///
+ protected override async Task UpsertResourceAsync(List instances, string serializedResource)
+ {
+ switch (instances[0].ResourceType)
+ {
+ case AgentResourceTypeNames.Agents:
+ await UpdateAgent(instances, serializedResource);
+ break;
+ default:
+ throw new ResourceProviderException($"The resource type {instances[0].ResourceType} is not supported by the {_name} resource manager.");
+ }
+ }
+
+ private async Task LoadAndSerializeAgents(ResourceTypeInstance instance)
+ {
+ if (instance.ResourceId == null)
+ {
+ var serializedAgents = new List();
+
+ foreach (var agentReference in _agentReferences.Values)
+ {
+ var agent = await LoadAgent(agentReference);
+ serializedAgents.Add(
+ JsonConvert.SerializeObject(agent, agentReference.AgentType, _serializerSettings));
+ }
+
+ return $"[{string.Join(",", [.. serializedAgents])}]";
+ }
+ else
+ {
+ if (!_agentReferences.TryGetValue(instance.ResourceId, out var agentReference))
+ throw new ResourceProviderException($"Could not locate the {instance.ResourceId} agent resource.");
+
+ var agent = await LoadAgent(agentReference);
+ return JsonConvert.SerializeObject(agent, agentReference.AgentType, _serializerSettings);
+ }
+ }
+
+ private async Task LoadAgent(AgentReference agentReference)
+ {
+ if (await _storageService.FileExistsAsync(_storageContainerName, agentReference.Filename, default))
+ {
+ var fileContent = await _storageService.ReadFileAsync(_storageContainerName, agentReference.Filename, default);
+ return JsonConvert.DeserializeObject(
+ Encoding.UTF8.GetString(fileContent.ToArray()),
+ agentReference.AgentType,
+ _serializerSettings) as AgentBase
+ ?? throw new ResourceProviderException($"Failed to load the agent {agentReference.Name}.");
+ }
+
+ throw new ResourceProviderException($"Could not locate the {agentReference.Name} agent resource.");
+ }
+
+ private async Task UpdateAgent(List instances, string serializedAgent)
+ {
+ var agentBase = JsonConvert.DeserializeObject(serializedAgent)
+ ?? throw new ResourceProviderException("The object definition is invalid.");
+
+ if (instances[0].ResourceId != agentBase.Name)
+ throw new ResourceProviderException("The resource path does not match the object definition (name mismatch).");
+
+ var agentReference = new AgentReference
+ {
+ Name = agentBase.Name!,
+ Type = agentBase.Type!,
+ Filename = $"/{_name}/{agentBase.Name}.json"
+ };
+
+ var agent = JsonConvert.DeserializeObject(serializedAgent, agentReference.AgentType, _serializerSettings);
+ (agent as AgentBase)!.ObjectId = GetObjectId(instances);
+
+ await _storageService.WriteFileAsync(
+ _storageContainerName,
+ agentReference.Filename,
+ JsonConvert.SerializeObject(agent, agentReference.AgentType, _serializerSettings),
+ default,
+ default);
+
+ _agentReferences[agentReference.Name] = agentReference;
+
+ await _storageService.WriteFileAsync(
+ _storageContainerName,
+ AGENT_REFERENCES_FILE_PATH,
+ JsonConvert.SerializeObject(AgentReferenceStore.FromDictionary(_agentReferences.ToDictionary())),
+ default,
+ default);
+ }
+
+
+
+
+
+
+
+
+ ///
+ protected override async Task GetResourceAsyncInternal(List instances) where T: class =>
+ instances[0].ResourceType switch
+ {
+ AgentResourceTypeNames.AgentReferences => await GetAgentAsync(instances),
+ _ => throw new ResourceProviderException($"The resource type {instances[0].ResourceType} is not supported by the {_name} resource manager.")
+ };
+
+ ///
+ protected override async Task> GetResourcesAsyncInternal(List instances) where T : class =>
+ instances[0].ResourceType switch
+ {
+ AgentResourceTypeNames.AgentReferences => await GetAgentsAsync(instances),
+ _ => throw new ResourceProviderException($"The resource type {instances[0].ResourceType} is not supported by the {_name} resource manager.")
+ };
+
+
+ private async Task> GetAgentsAsync(List instances) where T : class
+ {
+ if (typeof(T) != typeof(AgentReference))
+ throw new ResourceProviderException($"The type of requested resource ({typeof(T)}) does not match the resource type specified in the path ({instances[0].ResourceType}).");
+
+ var agentReferences = _agentReferences.Values.Cast().ToList();
+ foreach (var agentReference in agentReferences)
+ {
+ var agent = await LoadAgent(agentReference);
+ }
+
+ return agentReferences.Cast().ToList();
+ }
+
+ private async Task GetAgentAsync(List instances) where T : class
+ {
+ if (instances.Count != 1)
+ throw new ResourceProviderException($"Invalid resource path");
+
+ if (typeof(T) != typeof(AgentReference))
+ throw new ResourceProviderException($"The type of requested resource ({typeof(T)}) does not match the resource type specified in the path ({instances[0].ResourceType}).");
+
+ _agentReferences.TryGetValue(instances[0].ResourceId!, out var agentReference);
+ if (agentReference != null)
+ {
+ return agentReference as T ?? throw new ResourceProviderException(
+ $"The resource {instances[0].ResourceId!} of type {instances[0].ResourceType} was not found.");
+ }
+ throw new ResourceProviderException(
+ $"The resource {instances[0].ResourceId!} of type {instances[0].ResourceType} was not found.");
+ }
+ }
+}
diff --git a/src/dotnet/Agent/ResourceProviders/AgentResourceTypeNames.cs b/src/dotnet/Agent/ResourceProviders/AgentResourceTypeNames.cs
new file mode 100644
index 0000000000..627723b082
--- /dev/null
+++ b/src/dotnet/Agent/ResourceProviders/AgentResourceTypeNames.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FoundationaLLM.Agent.ResourceProviders
+{
+ ///
+ /// Contains constants of the names of the resource types managed by the FoundationaLLM.Agent resource manager.
+ ///
+ public class AgentResourceTypeNames
+ {
+ ///
+ /// Agent references.
+ ///
+ public const string AgentReferences = "agentreferences";
+ ///
+ /// Agents.
+ ///
+ public const string Agents = "agents";
+ }
+}
diff --git a/src/dotnet/AgentFactory/Models/Orchestration/Metadata/DataSourceBase.cs b/src/dotnet/AgentFactory/Models/Orchestration/Metadata/DataSourceBase.cs
index e7327b8335..df9145e8b1 100644
--- a/src/dotnet/AgentFactory/Models/Orchestration/Metadata/DataSourceBase.cs
+++ b/src/dotnet/AgentFactory/Models/Orchestration/Metadata/DataSourceBase.cs
@@ -9,7 +9,7 @@ namespace FoundationaLLM.AgentFactory.Core.Models.Orchestration.Metadata
public class DataSourceBase : MetadataBase
{
///
- /// Discriptor for the type of data in the data source.
+ /// Descriptor for the type of data in the data source.
///
/// Survey data for a CSV file that contains survey results.
[JsonProperty("data_description")]
diff --git a/src/dotnet/AgentFactoryAPI/Program.cs b/src/dotnet/AgentFactoryAPI/Program.cs
index 137d913187..6837f832f4 100644
--- a/src/dotnet/AgentFactoryAPI/Program.cs
+++ b/src/dotnet/AgentFactoryAPI/Program.cs
@@ -12,6 +12,7 @@
using FoundationaLLM.Common.Interfaces;
using FoundationaLLM.Common.Middleware;
using FoundationaLLM.Common.Models.Configuration.API;
+using FoundationaLLM.Common.Models.Configuration.Instance;
using FoundationaLLM.Common.Models.Context;
using FoundationaLLM.Common.OpenAPI;
using FoundationaLLM.Common.Services;
@@ -72,6 +73,8 @@ public static void Main(string[] args)
builder.Services.AddOptions()
.Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_APIs_AgentFactoryAPI));
builder.Services.AddTransient();
+ builder.Services.AddOptions()
+ .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_Instance));
builder.Services.AddOptions()
.Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_APIs_SemanticKernelAPI));
diff --git a/src/dotnet/Common/Constants/AgentTypes.cs b/src/dotnet/Common/Constants/AgentTypes.cs
new file mode 100644
index 0000000000..da40275c2d
--- /dev/null
+++ b/src/dotnet/Common/Constants/AgentTypes.cs
@@ -0,0 +1,17 @@
+namespace FoundationaLLM.Common.Constants
+{
+ ///
+ /// Contains constants for the types of agents.
+ ///
+ public class AgentTypes
+ {
+ ///
+ /// Knowledge Management agents are best for Q&A, summarization, and reasoning over textual data.
+ ///
+ public const string KnowledgeManagement = "knowledge-management";
+ ///
+ /// Analytic agents are best for querying, analyzing, calculating, and reporting on tabular data.
+ ///
+ public const string Analytic = "analytic";
+ }
+}
diff --git a/src/dotnet/Common/Constants/AppConfigurationKeys.cs b/src/dotnet/Common/Constants/AppConfigurationKeys.cs
index 31f306f876..d54373363d 100644
--- a/src/dotnet/Common/Constants/AppConfigurationKeys.cs
+++ b/src/dotnet/Common/Constants/AppConfigurationKeys.cs
@@ -12,6 +12,10 @@ namespace FoundationaLLM.Common.Constants
///
public static class AppConfigurationKeys
{
+ ///
+ /// The key for the FoundationaLLM:Instance:Id app configuration setting.
+ ///
+ public const string FoundationaLLM_Instance_Id = "FoundationaLLM:Instance:Id";
///
/// The key for the FoundationaLLM:AgentHub:AgentMetadata:StorageContainer app configuration setting.
///
@@ -660,6 +664,10 @@ public static class AppConfigurationKeys
///
public static class AppConfigurationKeyFilters
{
+ ///
+ /// The key filter for the FoundationaLLM:Instance:* app configuration settings.
+ ///
+ public const string FoundationaLLM_Instance = "FoundationaLLM:Instance:*";
///
/// The key filter for the FoundationaLLM:Branding:* app configuration settings.
///
@@ -720,6 +728,10 @@ public static class AppConfigurationKeyFilters
/// The key filter for the FoundationaLLM:Vectorization:* app configuration settings.
///
public const string FoundationaLLM_Vectorization = "FoundationaLLM:Vectorization:*";
+ ///
+ /// The key filter for the FoundationaLLM:Agent:* app configuration settings.
+ ///
+ public const string FoundationaLLM_Agent = "FoundationaLLM:Agent:*";
}
///
@@ -727,6 +739,10 @@ public static class AppConfigurationKeyFilters
///
public static class AppConfigurationKeySections
{
+ ///
+ /// The key section for the FoundationaLLM:Instance app configuration settings.
+ ///
+ public const string FoundationaLLM_Instance = "FoundationaLLM:Instance";
///
/// The key section for the FoundationaLLM:Branding app configuration settings.
///
@@ -817,10 +833,6 @@ public static class AppConfigurationKeySections
///
public const string FoundationaLLM_Vectorization_StateService = "FoundationaLLM:Vectorization:StateService:Storage";
///
- /// The key section for the FoundationaLLM:Vectorization:ResourceProviderService:Storage app configuration settings.
- ///
- public const string FoundationaLLM_Vectorization_ResourceProviderService_Storage = "FoundationaLLM:Vectorization:ResourceProviderService:Storage";
- ///
/// The key section for the FoundationaLLM:Vectorization:ContentSources app configuration settings.
///
public const string FoundationaLLM_Vectorization_ContentSources = "FoundationaLLM:Vectorization:ContentSources";
@@ -833,5 +845,19 @@ public static class AppConfigurationKeySections
/// The key section for the FoundationaLLM:Vectorization:AzureAISearchIndexingService app configuration settings.
///
public const string FoundationaLLM_Vectorization_AzureAISearchIndexingService = "FoundationaLLM:Vectorization:AzureAISearchIndexingService";
+
+ #region Resource providers
+
+ ///
+ /// The key section for the FoundationaLLM:Vectorization:ResourceProviderService:Storage app configuration settings.
+ ///
+ public const string FoundationaLLM_Vectorization_ResourceProviderService_Storage = "FoundationaLLM:Vectorization:ResourceProviderService:Storage";
+
+ ///
+ /// The key section for the FoundationaLLM:Agent:ResourceProviderService:Storage app configuration settings.
+ ///
+ public const string FoundationaLLM_Agent_ResourceProviderService_Storage = "FoundationaLLM:Agent:ResourceProviderService:Storage";
+
+ #endregion
}
}
diff --git a/src/dotnet/Common/Constants/DependencyInjectionKeys.cs b/src/dotnet/Common/Constants/DependencyInjectionKeys.cs
index f79f769430..542b938c16 100644
--- a/src/dotnet/Common/Constants/DependencyInjectionKeys.cs
+++ b/src/dotnet/Common/Constants/DependencyInjectionKeys.cs
@@ -50,5 +50,9 @@ public static class DependencyInjectionKeys
/// The dependency injection key for the vectorization steps configuration section.
///
public const string FoundationaLLM_Vectorization_Steps = "FoundationaLLM:Vectorization:Steps";
+ ///
+ /// The dependency injection key for the FoundationaLLM.Agent resource provider.
+ ///
+ public const string FoundationaLLM_Agent_ResourceProviderService = "FoundationaLLM:Agent:ResourceProviderService";
}
}
diff --git a/src/dotnet/Common/Constants/ResourceProviderNames.cs b/src/dotnet/Common/Constants/ResourceProviderNames.cs
index e531956f91..d20e538bcc 100644
--- a/src/dotnet/Common/Constants/ResourceProviderNames.cs
+++ b/src/dotnet/Common/Constants/ResourceProviderNames.cs
@@ -15,5 +15,13 @@ public static class ResourceProviderNames
/// The name of the FoundationaLLM.Vectorization resource provider.
///
public const string FoundationaLLM_Vectorization = "FoundationaLLM.Vectorization";
+ ///
+ /// The name of the FoundationaLLM.Agent resource provider.
+ ///
+ public const string FoundationaLLM_Agent = "FoundationaLLM.Agent";
+ ///
+ /// The name of the FoundationaLLM.Configuration resource provider.
+ ///
+ public const string FoundationaLLM_Configuration = "FoundationaLLM.Configuration";
}
}
diff --git a/src/dotnet/Common/Interfaces/ICallContext.cs b/src/dotnet/Common/Interfaces/ICallContext.cs
index 0ce44e2e64..31eda9a1dc 100644
--- a/src/dotnet/Common/Interfaces/ICallContext.cs
+++ b/src/dotnet/Common/Interfaces/ICallContext.cs
@@ -31,5 +31,9 @@ public interface ICallContext
/// from one or more services.
///
UnifiedUserIdentity? CurrentUserIdentity { get; set; }
+ ///
+ /// The unique identifier of the current FoundationaLLM deployment instance.
+ ///
+ string? InstanceId { get; set; }
}
}
diff --git a/src/dotnet/Common/Interfaces/IResourceProviderService.cs b/src/dotnet/Common/Interfaces/IResourceProviderService.cs
index d9d285ec2f..a953cfcb25 100644
--- a/src/dotnet/Common/Interfaces/IResourceProviderService.cs
+++ b/src/dotnet/Common/Interfaces/IResourceProviderService.cs
@@ -39,6 +39,13 @@ public interface IResourceProviderService
/// The of resources corresponding to the specified logical path.
IList GetResources(string resourcePath) where T : class;
+ ///
+ /// Gets the resources based on the logical path of the resource type.
+ ///
+ /// The logical path of the resource type.
+ /// The serialized form of resources corresponding to the specified logical path.
+ Task GetResourcesAsync(string resourcePath);
+
///
/// Gets a resource based on its logical path.
///
@@ -79,6 +86,14 @@ public interface IResourceProviderService
/// The instance of the resource being created or updated.
void UpsertResource(string resourcePath, T resource) where T : class;
+ ///
+ /// Creates or updates a resource based on its logical path.
+ ///
+ /// The logical path of the resource.
+ /// The serialized instance of the resource being created or updated.
+ ///
+ Task UpsertResourceAsync(string resourcePath, string serializedResource);
+
///
/// Deletes a resource based on its logical path.
///
@@ -93,5 +108,12 @@ public interface IResourceProviderService
/// The type of the resource.
/// The logical path of the resource.
void DeleteResource(string resourcePath) where T : class;
+
+ ///
+ /// Deletes a resource based on its logical path.
+ ///
+ /// The logical path of the resource.
+ ///
+ Task DeleteResourceAsync(string resourcePath);
}
}
diff --git a/src/dotnet/Common/Interfaces/IStorageService.cs b/src/dotnet/Common/Interfaces/IStorageService.cs
index becfe899c7..0cd1819ae9 100644
--- a/src/dotnet/Common/Interfaces/IStorageService.cs
+++ b/src/dotnet/Common/Interfaces/IStorageService.cs
@@ -11,6 +11,11 @@ namespace FoundationaLLM.Common.Interfaces
///
public interface IStorageService
{
+ ///
+ /// The optional instance name of the storage service.
+ ///
+ string? InstanceName { get; set; }
+
///
/// Reads the binary content of a specified file from the storage.
///
@@ -26,9 +31,10 @@ public interface IStorageService
/// The name of the container where the file is located.
/// The path of the file to read.
/// The binary content written to the file.
+ /// An optional content type.
/// The cancellation token that signals that operations should be cancelled.
///
- Task WriteFileAsync(string containerName, string filePath, Stream fileContent, CancellationToken cancellationToken);
+ Task WriteFileAsync(string containerName, string filePath, Stream fileContent, string? contentType, CancellationToken cancellationToken);
///
/// Writes the string content to a specified file from the storage.
@@ -36,9 +42,10 @@ public interface IStorageService
/// The name of the container where the file is located.
/// The path of the file to read.
/// The string content written to the file.
+ /// An optional content type.
/// The cancellation token that signals that operations should be cancelled.
///
- Task WriteFileAsync(string containerName, string filePath, string fileContent, CancellationToken cancellationToken);
+ Task WriteFileAsync(string containerName, string filePath, string fileContent, string? contentType, CancellationToken cancellationToken);
///
/// Checks if a file exists on the storage.
diff --git a/src/dotnet/Common/Middleware/CallContextMiddleware.cs b/src/dotnet/Common/Middleware/CallContextMiddleware.cs
index 8fa8a0fcf1..dfcd0f91d7 100644
--- a/src/dotnet/Common/Middleware/CallContextMiddleware.cs
+++ b/src/dotnet/Common/Middleware/CallContextMiddleware.cs
@@ -1,8 +1,10 @@
using FoundationaLLM.Common.Interfaces;
using FoundationaLLM.Common.Models.Authentication;
+using FoundationaLLM.Common.Models.Configuration.Instance;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using FoundationaLLM.Common.Models.Metadata;
+using Microsoft.Extensions.Options;
namespace FoundationaLLM.Common.Middleware
{
@@ -28,8 +30,13 @@ public CallContextMiddleware(RequestDelegate next) =>
/// Resolves user claims to a object.
/// Stores context information extracted from the current HTTP request. This information
/// is primarily used to inject HTTP headers into downstream HTTP calls.
+ /// Contains the FoundationaLLM instance configuration settings.
///
- public async Task InvokeAsync(HttpContext context, IUserClaimsProviderService claimsProviderService, ICallContext callContext)
+ public async Task InvokeAsync(
+ HttpContext context,
+ IUserClaimsProviderService claimsProviderService,
+ ICallContext callContext,
+ IOptions instanceSettings)
{
if (context.User is { Identity.IsAuthenticated: true })
{
@@ -40,18 +47,29 @@ public async Task InvokeAsync(HttpContext context, IUserClaimsProviderService cl
{
// Extract from HTTP headers if available:
var serializedIdentity = context.Request.Headers[Constants.HttpHeaders.UserIdentity].ToString();
- if (!string.IsNullOrEmpty(serializedIdentity))
+ if (!string.IsNullOrWhiteSpace(serializedIdentity))
{
callContext.CurrentUserIdentity = JsonConvert.DeserializeObject(serializedIdentity)!;
}
}
var agentHint = context.Request.Headers[Constants.HttpHeaders.AgentHint].FirstOrDefault();
- if (!string.IsNullOrEmpty(agentHint))
+ if (!string.IsNullOrWhiteSpace(agentHint))
{
callContext.AgentHint = JsonConvert.DeserializeObject(agentHint);
}
+ callContext.InstanceId = context.Request.RouteValues["instanceId"] as string;
+ if (!string.IsNullOrWhiteSpace(callContext.InstanceId) && callContext.InstanceId != instanceSettings.Value.Id)
+ {
+ // Throw 403 Forbidden since the instance ID within the route does not match the instance ID in the
+ // configuration settings:
+ context.Response.StatusCode = StatusCodes.Status403Forbidden;
+ await context.Response.WriteAsync("Access denied. Invalid instance ID.");
+
+ return; // Short-circuit the request pipeline.
+ }
+
// Call the next delegate/middleware in the pipeline:
await _next(context);
}
diff --git a/src/dotnet/Common/Models/Configuration/Instance/InstanceSettings.cs b/src/dotnet/Common/Models/Configuration/Instance/InstanceSettings.cs
new file mode 100644
index 0000000000..3a302a56a6
--- /dev/null
+++ b/src/dotnet/Common/Models/Configuration/Instance/InstanceSettings.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FoundationaLLM.Common.Models.Configuration.Instance
+{
+ ///
+ /// Provides configuration settings for the current FoundationaLLM deployment instance.
+ ///
+ public class InstanceSettings
+ {
+ ///
+ /// The unique identifier of the current FoundationaLLM deployment instance.
+ /// Format is a GUID.
+ ///
+ public required string Id { get; set; }
+ }
+}
diff --git a/src/dotnet/Common/Models/Context/CallContext.cs b/src/dotnet/Common/Models/Context/CallContext.cs
index bd591ddb9d..bef8a9d720 100644
--- a/src/dotnet/Common/Models/Context/CallContext.cs
+++ b/src/dotnet/Common/Models/Context/CallContext.cs
@@ -16,5 +16,7 @@ public class CallContext : ICallContext
public Agent? AgentHint { get; set; }
///
public UnifiedUserIdentity? CurrentUserIdentity { get; set; }
+ ///
+ public string? InstanceId { get; set; }
}
}
diff --git a/src/dotnet/Common/Models/ResourceProvider/ResourceBase.cs b/src/dotnet/Common/Models/ResourceProvider/ResourceBase.cs
new file mode 100644
index 0000000000..77f862b9f6
--- /dev/null
+++ b/src/dotnet/Common/Models/ResourceProvider/ResourceBase.cs
@@ -0,0 +1,36 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FoundationaLLM.Common.Models.ResourceProvider
+{
+ ///
+ /// Basic properties for all resources.
+ ///
+ public class ResourceBase
+ {
+ ///
+ /// The name of the resource.
+ ///
+ [JsonProperty("name")]
+ public required string Name { get; set; }
+ ///
+ /// The type of the resource.
+ ///
+ [JsonProperty("type")]
+ public required string Type { get; set; }
+ ///
+ /// The unique identifier of the resource.
+ ///
+ [JsonProperty("object_id")]
+ public required string ObjectId { get; set; }
+ ///
+ /// The description of the resource.
+ ///
+ [JsonProperty("description")]
+ public string? Description { get; set; }
+ }
+}
diff --git a/src/dotnet/Common/Services/BlobStorageService.cs b/src/dotnet/Common/Services/BlobStorageService.cs
index ca820fa881..8d7d1274ea 100644
--- a/src/dotnet/Common/Services/BlobStorageService.cs
+++ b/src/dotnet/Common/Services/BlobStorageService.cs
@@ -60,6 +60,7 @@ public async Task WriteFileAsync(
string containerName,
string filePath,
Stream fileContent,
+ string? contentType,
CancellationToken cancellationToken = default)
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
@@ -67,7 +68,16 @@ public async Task WriteFileAsync(
fileContent.Seek(0, SeekOrigin.Begin);
- BlobUploadOptions options = new();
+ BlobUploadOptions options = new()
+ {
+ HttpHeaders = new BlobHttpHeaders()
+ {
+ ContentType = string.IsNullOrWhiteSpace(contentType)
+ ? "application/json"
+ : contentType
+ }
+ };
+
await blobClient.UploadAsync(fileContent, options, cancellationToken).ConfigureAwait(false);
}
@@ -76,11 +86,13 @@ public async Task WriteFileAsync(
string containerName,
string filePath,
string fileContent,
+ string? contentType,
CancellationToken cancellationToken = default) =>
await WriteFileAsync(
containerName,
filePath,
new MemoryStream(Encoding.UTF8.GetBytes(fileContent)),
+ contentType,
cancellationToken).ConfigureAwait(false);
///
diff --git a/src/dotnet/Common/Services/DataLakeStorageService.cs b/src/dotnet/Common/Services/DataLakeStorageService.cs
index a032c1a16f..923d3d2cf6 100644
--- a/src/dotnet/Common/Services/DataLakeStorageService.cs
+++ b/src/dotnet/Common/Services/DataLakeStorageService.cs
@@ -73,6 +73,7 @@ public Task WriteFileAsync(
string containerName,
string filePath,
Stream fileContent,
+ string? contentType,
CancellationToken cancellationToken) =>
throw new NotImplementedException();
@@ -81,6 +82,7 @@ public Task WriteFileAsync(
string containerName,
string filePath,
string fileContent,
+ string? contentType,
CancellationToken cancellationToken) =>
throw new NotImplementedException();
diff --git a/src/dotnet/Common/Services/ResourceProviders/ResourceProviderServiceBase.cs b/src/dotnet/Common/Services/ResourceProviders/ResourceProviderServiceBase.cs
index d74d21cf10..cbf3724e89 100644
--- a/src/dotnet/Common/Services/ResourceProviders/ResourceProviderServiceBase.cs
+++ b/src/dotnet/Common/Services/ResourceProviders/ResourceProviderServiceBase.cs
@@ -1,5 +1,6 @@
using FoundationaLLM.Common.Exceptions;
using FoundationaLLM.Common.Interfaces;
+using FoundationaLLM.Common.Models.Configuration.Instance;
using FoundationaLLM.Common.Models.ResourceProvider;
using Microsoft.Extensions.Logging;
@@ -22,6 +23,11 @@ public class ResourceProviderServiceBase : IResourceProviderService
///
protected readonly ILogger _logger;
+ ///
+ /// The that provides instance-wide settings.
+ ///
+ protected readonly InstanceSettings _instanceSettings;
+
///
/// The name of the storage container name used by the resource provider to store its internal data.
///
@@ -46,14 +52,17 @@ public class ResourceProviderServiceBase : IResourceProviderService
///
/// Creates a new instance of the resource provider.
///
+ /// The that provides instance-wide settings.
/// The providing storage services to the resource provider.
/// The logger used for logging.
public ResourceProviderServiceBase(
+ InstanceSettings instanceSettings,
IStorageService storageService,
ILogger logger)
{
_storageService = storageService;
_logger = logger;
+ _instanceSettings = instanceSettings;
// Kicks off the initialization on a separate thread and does not wait for it to complete.
// The completion of the initialization process will be signaled by setting the _isInitialized property.
@@ -74,6 +83,8 @@ public async Task Initialize()
}
}
+ #region IResourceProviderService
+
///
public async Task ExecuteAction(string actionPath)
{
@@ -101,6 +112,15 @@ public async Task> GetResourcesAsync(string resourcePath) where T :
return await GetResourcesAsyncInternal(instances);
}
+ ///
+ public async Task GetResourcesAsync(string resourcePath)
+ {
+ if (!_isInitialized)
+ throw new ResourceProviderException($"The resource provider {_name} is not initialized.");
+ var instances = GetResourceInstancesFromPath(resourcePath);
+ return await GetResourcesAsyncInternal(instances);
+ }
+
///
public T GetResource(string resourcePath) where T : class
{
@@ -128,6 +148,15 @@ public async Task UpsertResourceAsync(string resourcePath, T resource) where
await UpsertResourceAsync(instances, resource);
}
+ ///
+ public async Task UpsertResourceAsync(string resourcePath, string serializedResource)
+ {
+ if (!_isInitialized)
+ throw new ResourceProviderException($"The resource provider {_name} is not initialized.");
+ var instances = GetResourceInstancesFromPath(resourcePath);
+ await UpsertResourceAsync(instances, serializedResource);
+ }
+
///
public void UpsertResource(string resourcePath, T resource) where T : class
{
@@ -146,6 +175,15 @@ public async Task DeleteResourceAsync(string resourcePath) where T : class
await DeleteResourceAsync(instances);
}
+ ///
+ public async Task DeleteResourceAsync(string resourcePath)
+ {
+ if (!_isInitialized)
+ throw new ResourceProviderException($"The resource provider {_name} is not initialized.");
+ var instances = GetResourceInstancesFromPath(resourcePath);
+ await DeleteResourceAsync(instances);
+ }
+
///
public void DeleteResource(string resourcePath) where T : class
{
@@ -155,6 +193,8 @@ public void DeleteResource(string resourcePath) where T : class
DeleteResource(instances);
}
+ #endregion
+
///
/// The internal implementation of Initialize. Must be overridden in derived classes.
///
@@ -195,6 +235,17 @@ protected virtual async Task> GetResourcesAsyncInternal(List
+ /// The internal implementation of GetResourcesAsync. Must be overridden in derived classes.
+ ///
+ /// The list of objects parsed from the resource path.
+ ///
+ protected virtual async Task GetResourcesAsyncInternal(List instances)
+ {
+ await Task.CompletedTask;
+ throw new NotImplementedException();
+ }
+
///
/// The internal implementation of GetResource. Must be overridden in derived classes.
///
@@ -235,6 +286,18 @@ protected virtual async Task UpsertResourceAsync(List i
throw new NotImplementedException();
}
+ ///
+ /// The internal implementation of UpsertResourceAsync. Must be overridden in derived classes.
+ ///
+ /// The list of objects parsed from the resource path.
+ /// The serialized resource being created or updated.
+ ///
+ protected virtual async Task UpsertResourceAsync(List instances, string serializedResource)
+ {
+ await Task.CompletedTask;
+ throw new NotImplementedException();
+ }
+
///
/// The internal implementation of DeleteResource. Must be overridden in derived classes.
///
@@ -254,6 +317,35 @@ protected virtual async Task DeleteResourceAsync(List i
throw new NotImplementedException();
}
+ ///
+ /// The internal implementation of DeleteResourceAsync. Must be overridden in derived classes.
+ ///
+ /// The list of objects parsed from the resource path.
+ ///
+ protected virtual async Task DeleteResourceAsync(List instances)
+ {
+ await Task.CompletedTask;
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Builds the resource unique identifier based on the resource path.
+ ///
+ /// The list of objects parsed from the resource path.
+ /// The unique resource identifier.
+ ///
+ protected string GetObjectId(List instances)
+ {
+ foreach (var instance in instances)
+ if (string.IsNullOrWhiteSpace(instance.ResourceType)
+ || string.IsNullOrWhiteSpace(instance.ResourceId)
+ || !(instance.Action == null))
+ throw new ResourceProviderException("The provided resource path is not a valid resource identifier.");
+
+ return $"/instances/{_instanceSettings.Id}/providers/{_name}/{string.Join("/",
+ instances.Select(i => $"{i.ResourceType}/{i.ResourceId}").ToArray())}";
+ }
+
private List GetResourceInstancesFromPath(string resourcePath)
{
if (string.IsNullOrWhiteSpace(resourcePath))
diff --git a/src/dotnet/Common/Services/StorageServiceBase.cs b/src/dotnet/Common/Services/StorageServiceBase.cs
index 132e948ebf..8aca963b0c 100644
--- a/src/dotnet/Common/Services/StorageServiceBase.cs
+++ b/src/dotnet/Common/Services/StorageServiceBase.cs
@@ -26,6 +26,11 @@ public abstract class StorageServiceBase
///
protected readonly ILogger _logger;
+ ///
+ /// The optional instance name of the storage service.
+ ///
+ public string? InstanceName { get; set; }
+
///
/// Initializes a new instance of the with the specified options and logger.
///
diff --git a/src/dotnet/GatekeeperAPI/Program.cs b/src/dotnet/GatekeeperAPI/Program.cs
index 5e7d0e48e3..262e6aa358 100644
--- a/src/dotnet/GatekeeperAPI/Program.cs
+++ b/src/dotnet/GatekeeperAPI/Program.cs
@@ -6,6 +6,7 @@
using FoundationaLLM.Common.Extensions;
using FoundationaLLM.Common.Interfaces;
using FoundationaLLM.Common.Middleware;
+using FoundationaLLM.Common.Models.Configuration.Instance;
using FoundationaLLM.Common.Models.Context;
using FoundationaLLM.Common.OpenAPI;
using FoundationaLLM.Common.Services;
@@ -69,6 +70,8 @@ public static void Main(string[] args)
builder.Services.AddScoped();
builder.Services.AddOptions()
.Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_APIs_GatekeeperAPI));
+ builder.Services.AddOptions()
+ .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_Instance));
// Register the downstream services and HTTP clients.
RegisterDownstreamServices(builder);
diff --git a/src/dotnet/Management/Management.csproj b/src/dotnet/Management/Management.csproj
index 8b8945bda6..520d39134e 100644
--- a/src/dotnet/Management/Management.csproj
+++ b/src/dotnet/Management/Management.csproj
@@ -18,7 +18,9 @@
+
+
diff --git a/src/dotnet/Management/Models/ResourceBase.cs b/src/dotnet/Management/Models/ResourceBase.cs
new file mode 100644
index 0000000000..1e49154c7b
--- /dev/null
+++ b/src/dotnet/Management/Models/ResourceBase.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FoundationaLLM.Management.Models
+{
+ public class ResourceBase
+ {
+ public required string Type { get; set; }
+ public required string Name { get; set; }
+ }
+}
diff --git a/src/dotnet/Management/Services/AgentManagementService.cs b/src/dotnet/Management/Services/AgentManagementService.cs
index d41fb76958..c83a8a56e4 100644
--- a/src/dotnet/Management/Services/AgentManagementService.cs
+++ b/src/dotnet/Management/Services/AgentManagementService.cs
@@ -1,12 +1,67 @@
-using System;
+using FoundationaLLM.Common.Constants;
+using FoundationaLLM.Common.Interfaces;
+using FoundationaLLM.Vectorization.Models.Resources;
+using Microsoft.Extensions.DependencyInjection;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using FoundationaLLM.Agent.Models.Resources;
+using FoundationaLLM.Agent.ResourceProviders;
+using FoundationaLLM.Vectorization.ResourceProviders;
namespace FoundationaLLM.Management.Services
{
- internal class AgentManagementService
+ public class AgentManagementService(
+ [FromKeyedServices(DependencyInjectionKeys.FoundationaLLM_Vectorization_ResourceProviderService)]
+ IResourceProviderService vectorizationResourceProviderService,
+ [FromKeyedServices(DependencyInjectionKeys.FoundationaLLM_Agent_ResourceProviderService)]
+ IResourceProviderService agentResourceProviderService)
{
+ private readonly IResourceProviderService _vectorizationResourceProviderService =
+ vectorizationResourceProviderService;
+ private readonly IResourceProviderService _agentResourceProviderService =
+ agentResourceProviderService;
+
+ private List? GetVectorContentSourceProfiles()
+ {
+ var contentSourceProfiles = _vectorizationResourceProviderService.GetResources(
+ $"/{VectorizationResourceTypeNames.ContentSourceProfiles}");
+
+ return contentSourceProfiles as List;
+ }
+
+ private List? GetVectorIndexingProfiles()
+ {
+ var indexingProfiles = _vectorizationResourceProviderService.GetResources(
+ $"/{VectorizationResourceTypeNames.IndexingProfiles}");
+
+ return indexingProfiles as List;
+ }
+
+ private List? GetContentSourceProfiles()
+ {
+ var contentSourceProfiles = _vectorizationResourceProviderService.GetResources(
+ $"/{VectorizationResourceTypeNames.ContentSourceProfiles}");
+
+ return contentSourceProfiles as List;
+ }
+
+ private List? GetIndexingProfiles()
+ {
+ var indexingProfiles = _vectorizationResourceProviderService.GetResources(
+ $"/{VectorizationResourceTypeNames.IndexingProfiles}");
+
+ return indexingProfiles as List;
+ }
+
+ private List? GetAgentReferences()
+ {
+ var agentReferences = _agentResourceProviderService.GetResources(
+ $"/{AgentResourceTypeNames.AgentReferences}");
+
+ return agentReferences as List;
+ }
}
}
diff --git a/src/dotnet/ManagementAPI/Controllers/CachesController.cs b/src/dotnet/ManagementAPI/Controllers/CachesController.cs
index 9183fea7ed..40108777a0 100644
--- a/src/dotnet/ManagementAPI/Controllers/CachesController.cs
+++ b/src/dotnet/ManagementAPI/Controllers/CachesController.cs
@@ -1,4 +1,5 @@
using Asp.Versioning;
+using FoundationaLLM.Common.Constants;
using FoundationaLLM.Common.Models.Cache;
using FoundationaLLM.Common.Models.Configuration.Branding;
using FoundationaLLM.Management.Interfaces;
@@ -20,7 +21,7 @@ namespace FoundationaLLM.Management.API.Controllers
[Authorize(Policy = "RequiredScope")]
[ApiVersion(1.0)]
[ApiController]
- [Route("[controller]")]
+ [Route($"instances/{{instanceId}}/providersX/{ResourceProviderNames.FoundationaLLM_Configuration}/caches")]
public class CachesController(
ICacheManagementService cacheManagementService) : ControllerBase
{
diff --git a/src/dotnet/ManagementAPI/Controllers/ConfigurationsController.cs b/src/dotnet/ManagementAPI/Controllers/ConfigurationsController.cs
index 1430361bbd..66ade3d50f 100644
--- a/src/dotnet/ManagementAPI/Controllers/ConfigurationsController.cs
+++ b/src/dotnet/ManagementAPI/Controllers/ConfigurationsController.cs
@@ -1,4 +1,5 @@
using Asp.Versioning;
+using FoundationaLLM.Common.Constants;
using FoundationaLLM.Common.Models.Cache;
using FoundationaLLM.Common.Models.Configuration.Branding;
using FoundationaLLM.Management.Interfaces;
@@ -20,7 +21,7 @@ namespace FoundationaLLM.Management.API.Controllers
[Authorize(Policy = "RequiredScope")]
[ApiVersion(1.0)]
[ApiController]
- [Route("[controller]")]
+ [Route($"instances/{{instanceId}}/providersX/{ResourceProviderNames.FoundationaLLM_Configuration}/configurations")]
public class ConfigurationsController(
IConfigurationManagementService configurationManagementService) : ControllerBase
{
diff --git a/src/dotnet/ManagementAPI/Controllers/ResourceController.cs b/src/dotnet/ManagementAPI/Controllers/ResourceController.cs
new file mode 100644
index 0000000000..ea89b37cef
--- /dev/null
+++ b/src/dotnet/ManagementAPI/Controllers/ResourceController.cs
@@ -0,0 +1,104 @@
+using Asp.Versioning;
+using FoundationaLLM.Common.Exceptions;
+using FoundationaLLM.Common.Interfaces;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace FoundationaLLM.Management.API.Controllers
+{
+ ///
+ /// Provides methods to manage resources.
+ ///
+ /// The list of resource providers.
+ /// The used for logging.
+ [Authorize]
+ [Authorize(Policy = "RequiredScope")]
+ [ApiVersion(1.0)]
+ [ApiController]
+ [Route($"instances/{{instanceId}}/providers/{{resourceProvider}}")]
+ public class ResourceController(
+ IEnumerable resourceProviderServices,
+ ILogger logger) : Controller
+ {
+ private readonly Dictionary _resourceProviderServices =
+ resourceProviderServices.ToDictionary(
+ rps => rps.Name);
+ private readonly ILogger _logger = logger;
+
+ ///
+ /// Gets one or more resources.
+ ///
+ /// The FoundationaLLM instance identifier.
+ /// The name of the resource provider that should handle the request.
+ /// The logical path of the resource type.
+ ///
+ [HttpGet("{*resourcePath}", Name = "GetResources")]
+ public async Task GetResources(string instanceId, string resourceProvider, string resourcePath) =>
+ await HandleRequest(
+ resourceProvider,
+ resourcePath,
+ async (resourceProviderService) =>
+ {
+ var result = await resourceProviderService.GetResourcesAsync(resourcePath);
+ return new OkObjectResult(result);
+ });
+
+ ///
+ /// Creates or updates resources.
+ ///
+ /// The FoundationaLLM instance identifier.
+ /// The name of the resource provider that should handle the request.
+ /// The logical path of the resource type.
+ /// The serialized resource to be created or updated.
+ ///
+ [HttpPost("{*resourcePath}", Name = "UpsertResource")]
+ public async Task UpsertResource(string instanceId, string resourceProvider, string resourcePath, [FromBody] object serializedResource) =>
+ await HandleRequest(
+ resourceProvider,
+ resourcePath,
+ async (resourceProviderService) =>
+ {
+ await resourceProviderService.UpsertResourceAsync(resourcePath, serializedResource.ToString()!);
+ return new OkResult();
+ });
+
+ ///
+ /// Deletes a resource.
+ ///
+ /// The FoundationaLLM instance identifier.
+ /// The name of the resource provider that should handle the request.
+ /// The logical path of the resource type.
+ ///
+ [HttpDelete("{*resourcePath}", Name = "DeleteResource")]
+ public async Task DeleteResource(string instanceId, string resourceProvider, string resourcePath) =>
+ await HandleRequest(
+ resourceProvider,
+ resourcePath,
+ async (resourceProviderService) =>
+ {
+ await resourceProviderService.DeleteResourceAsync(resourcePath);
+ return new OkResult();
+ });
+
+ private async Task HandleRequest(string resourceProvider, string resourcePath, Func> handler)
+ {
+ if (!_resourceProviderServices.TryGetValue(resourceProvider, out var resourceProviderService))
+ return new NotFoundResult();
+
+ try
+ {
+ return await handler(resourceProviderService);
+ }
+ catch (ResourceProviderException ex)
+ {
+ _logger.LogError(ex, ex.Message);
+ return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "The {ResourceProviderName} encountered an error while handling the request for {ResourcePath}.", resourceProvider, resourcePath);
+ return StatusCode(StatusCodes.Status500InternalServerError, $"The {resourceProvider} encountered an error while handling the request for {resourcePath}.");
+ }
+ }
+ }
+}
diff --git a/src/dotnet/ManagementAPI/Controllers/StatusController.cs b/src/dotnet/ManagementAPI/Controllers/StatusController.cs
index 8d7a9bd7ad..d9175e4144 100644
--- a/src/dotnet/ManagementAPI/Controllers/StatusController.cs
+++ b/src/dotnet/ManagementAPI/Controllers/StatusController.cs
@@ -11,7 +11,7 @@ namespace FoundationaLLM.Management.API.Controllers
[Authorize(Policy = "RequiredScope")]
[ApiVersion(1.0)]
[ApiController]
- [Route("[controller]")]
+ [Route("status")]
public class StatusController : ControllerBase
{
///
diff --git a/src/dotnet/ManagementAPI/Program.cs b/src/dotnet/ManagementAPI/Program.cs
index eeea010c78..cdf3c19ad3 100644
--- a/src/dotnet/ManagementAPI/Program.cs
+++ b/src/dotnet/ManagementAPI/Program.cs
@@ -5,11 +5,13 @@
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Net.Http;
+using FoundationaLLM.Agent.ResourceProviders;
using FoundationaLLM.Common.Authentication;
using FoundationaLLM.Common.Constants;
using FoundationaLLM.Common.Interfaces;
using FoundationaLLM.Common.Middleware;
using FoundationaLLM.Common.Models.Configuration.Branding;
+using FoundationaLLM.Common.Models.Configuration.Instance;
using FoundationaLLM.Common.Models.Context;
using FoundationaLLM.Common.OpenAPI;
using FoundationaLLM.Common.Services;
@@ -19,8 +21,10 @@
using FoundationaLLM.Management.Models.Configuration;
using FoundationaLLM.Management.Services;
using FoundationaLLM.Management.Services.APIServices;
+using FoundationaLLM.Vectorization.ResourceProviders;
using Microsoft.Identity.Web;
using Polly;
+using Microsoft.Extensions.DependencyInjection;
namespace FoundationaLLM.Management.API
{
@@ -43,10 +47,13 @@ public static void Main(string[] args)
{
options.Connect(builder.Configuration[AppConfigurationKeys.FoundationaLLM_AppConfig_ConnectionString]);
options.ConfigureKeyVault(options => { options.SetCredential(new DefaultAzureCredential()); });
+ options.Select(AppConfigurationKeyFilters.FoundationaLLM_Instance);
options.Select(AppConfigurationKeyFilters.FoundationaLLM_APIs);
options.Select(AppConfigurationKeyFilters.FoundationaLLM_CosmosDB);
options.Select(AppConfigurationKeyFilters.FoundationaLLM_Branding);
options.Select(AppConfigurationKeyFilters.FoundationaLLM_ManagementAPI_Entra);
+ options.Select(AppConfigurationKeyFilters.FoundationaLLM_Vectorization);
+ options.Select(AppConfigurationKeyFilters.FoundationaLLM_Agent);
});
if (builder.Environment.IsDevelopment())
builder.Configuration.AddJsonFile("appsettings.development.json", true, true);
@@ -71,6 +78,8 @@ public static void Main(string[] args)
builder.Services.AddOptions()
.Configure(o =>
o.ConnectionString = builder.Configuration[AppConfigurationKeys.FoundationaLLM_AppConfig_ConnectionString]!);
+ builder.Services.AddOptions()
+ .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_Instance));
builder.Services.AddScoped();
builder.Services.AddScoped();
@@ -83,7 +92,73 @@ public static void Main(string[] args)
builder.Services.AddScoped();
builder.Services.AddScoped();
- // Register the authentication services
+ //----------------------------
+ // Resource providers
+ //----------------------------
+
+ #region Vectorization resource provider
+
+ builder.Services.AddOptions(
+ DependencyInjectionKeys.FoundationaLLM_Vectorization_ResourceProviderService)
+ .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_Vectorization_ResourceProviderService_Storage));
+
+ builder.Services.AddSingleton( sp =>
+ {
+ var settings = sp.GetRequiredService>()
+ .Get(DependencyInjectionKeys.FoundationaLLM_Vectorization_ResourceProviderService);
+ var logger = sp.GetRequiredService>();
+
+ return new BlobStorageService(
+ Options.Create(settings),
+ logger)
+ {
+ InstanceName = DependencyInjectionKeys.FoundationaLLM_Vectorization_ResourceProviderService
+ };
+ });
+
+ // Register the resource provider services (cannot use Keyed singletons due to the Microsoft Identity package being incompatible):
+ builder.Services.AddSingleton(sp =>
+ new VectorizationResourceProviderService(
+ sp.GetRequiredService>(),
+ sp.GetRequiredService>()
+ .Single(s => s.InstanceName == DependencyInjectionKeys.FoundationaLLM_Vectorization_ResourceProviderService),
+ sp.GetRequiredService>()));
+
+ #endregion
+
+ #region Agent resource provider
+
+ builder.Services.AddOptions(
+ DependencyInjectionKeys.FoundationaLLM_Agent_ResourceProviderService)
+ .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_Agent_ResourceProviderService_Storage));
+
+ builder.Services.AddSingleton(sp =>
+ {
+ var settings = sp.GetRequiredService>()
+ .Get(DependencyInjectionKeys.FoundationaLLM_Agent_ResourceProviderService);
+ var logger = sp.GetRequiredService>();
+
+ return new BlobStorageService(
+ Options.Create(settings),
+ logger)
+ {
+ InstanceName = DependencyInjectionKeys.FoundationaLLM_Agent_ResourceProviderService
+ };
+ });
+
+ builder.Services.AddSingleton(sp =>
+ new AgentResourceProviderService(
+ sp.GetRequiredService>(),
+ sp.GetRequiredService>()
+ .Single(s => s.InstanceName == DependencyInjectionKeys.FoundationaLLM_Agent_ResourceProviderService),
+ sp.GetRequiredService>()));
+
+ #endregion
+
+ // Activate all resource providers (give them a chance to initialize).
+ builder.Services.ActivateSingleton>();
+
+ // Register the authentication services:
RegisterAuthConfiguration(builder);
builder.Services.AddApplicationInsightsTelemetry(new ApplicationInsightsServiceOptions
@@ -137,11 +212,6 @@ public static void Main(string[] args)
})
.AddSwaggerGenNewtonsoftSupport();
- builder.Services.Configure(options =>
- {
- options.LowercaseUrls = true;
- });
-
var app = builder.Build();
// Set the CORS policy before other middleware.
diff --git a/src/dotnet/SemanticKernelAPI/Program.cs b/src/dotnet/SemanticKernelAPI/Program.cs
index 85d789dcf2..76d9c99bb2 100644
--- a/src/dotnet/SemanticKernelAPI/Program.cs
+++ b/src/dotnet/SemanticKernelAPI/Program.cs
@@ -3,6 +3,7 @@
using FoundationaLLM.Common.Constants;
using FoundationaLLM.Common.Extensions;
using FoundationaLLM.Common.Interfaces;
+using FoundationaLLM.Common.Models.Configuration.Instance;
using FoundationaLLM.Common.OpenAPI;
using FoundationaLLM.SemanticKernel.Core.Interfaces;
using FoundationaLLM.SemanticKernel.Core.Models.ConfigurationOptions;
@@ -54,6 +55,8 @@ public static void Main(string[] args)
builder.Services.AddScoped();
builder.Services.AddOptions()
.Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_APIs_SemanticKernelAPI));
+ builder.Services.AddOptions()
+ .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_Instance));
builder.Services.AddTransient();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
diff --git a/src/dotnet/Vectorization/Models/Resources/ContentSourceProfile.cs b/src/dotnet/Vectorization/Models/Resources/ContentSourceProfile.cs
index 149e92e975..f2ba5438cf 100644
--- a/src/dotnet/Vectorization/Models/Resources/ContentSourceProfile.cs
+++ b/src/dotnet/Vectorization/Models/Resources/ContentSourceProfile.cs
@@ -4,7 +4,7 @@
namespace FoundationaLLM.Vectorization.Models.Resources
{
///
- /// Provides detials about a content source.
+ /// Provides details about a content source.
///
public class ContentSourceProfile : VectorizationProfileBase
{
diff --git a/src/dotnet/Vectorization/ResourceProviders/VectorizationResourceProviderService.cs b/src/dotnet/Vectorization/ResourceProviders/VectorizationResourceProviderService.cs
index 86e19b89e9..12a128ef80 100644
--- a/src/dotnet/Vectorization/ResourceProviders/VectorizationResourceProviderService.cs
+++ b/src/dotnet/Vectorization/ResourceProviders/VectorizationResourceProviderService.cs
@@ -1,10 +1,12 @@
using FoundationaLLM.Common.Constants;
using FoundationaLLM.Common.Exceptions;
using FoundationaLLM.Common.Interfaces;
+using FoundationaLLM.Common.Models.Configuration.Instance;
using FoundationaLLM.Common.Services.ResourceProviders;
using FoundationaLLM.Vectorization.Models.Resources;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.Linq;
using System.Text;
@@ -15,9 +17,11 @@ namespace FoundationaLLM.Vectorization.ResourceProviders
/// Implements the FoundationaLLM.Vectorization resource provider.
///
public class VectorizationResourceProviderService(
+ IOptions instanceOptions,
[FromKeyedServices(DependencyInjectionKeys.FoundationaLLM_Vectorization_ResourceProviderService)] IStorageService storageService,
ILogger logger)
: ResourceProviderServiceBase(
+ instanceOptions.Value,
storageService,
logger)
{
@@ -108,14 +112,57 @@ protected override async Task InitializeInternal()
protected override T GetResourceInternal(List instances) where T: class =>
instances[0].ResourceType switch
{
- VectorizationResourceTypeNames.ContentSourceProfiles => GetContentSourceProfiles(instances),
+ VectorizationResourceTypeNames.ContentSourceProfiles => GetContentSourceProfile(instances),
VectorizationResourceTypeNames.TextPartitioningProfiles => GetTextPartitioningProfile(instances),
VectorizationResourceTypeNames.TextEmbeddingProfiles => GetTextEmbeddingProfile(instances),
VectorizationResourceTypeNames.IndexingProfiles => GetIndexingProfile(instances),
_ => throw new ResourceProviderException($"The resource type {instances[0].ResourceType} is not supported by the {_name} resource manager.")
};
- private T GetContentSourceProfiles(List instances) where T: class
+ ///
+ protected override List GetResourcesInternal(List instances) where T : class =>
+ instances[0].ResourceType switch
+ {
+ VectorizationResourceTypeNames.ContentSourceProfiles => GetContentSourceProfiles(instances),
+ VectorizationResourceTypeNames.TextPartitioningProfiles => GetTextPartitioningProfiles(instances),
+ VectorizationResourceTypeNames.TextEmbeddingProfiles => GetTextEmbeddingProfiles(instances),
+ VectorizationResourceTypeNames.IndexingProfiles => GetIndexingProfiles(instances),
+ _ => throw new ResourceProviderException($"The resource type {instances[0].ResourceType} is not supported by the {_name} resource manager.")
+ };
+
+ private List GetContentSourceProfiles(List instances) where T : class
+ {
+ if (typeof(T) != typeof(ContentSourceProfile))
+ throw new ResourceProviderException($"The type of requested resource ({typeof(T)}) does not match the resource type specified in the path ({instances[0].ResourceType}).");
+
+ return _contentSourceProfiles.Values.Cast().ToList();
+ }
+
+ private List GetTextPartitioningProfiles(List instances) where T : class
+ {
+ if (typeof(T) != typeof(TextPartitioningProfile))
+ throw new ResourceProviderException($"The type of requested resource ({typeof(T)}) does not match the resource type specified in the path ({instances[0].ResourceType}).");
+
+ return _textPartitioningProfiles.Values.Cast().ToList();
+ }
+
+ private List GetTextEmbeddingProfiles(List instances) where T : class
+ {
+ if (typeof(T) != typeof(TextEmbeddingProfile))
+ throw new ResourceProviderException($"The type of requested resource ({typeof(T)}) does not match the resource type specified in the path ({instances[0].ResourceType}).");
+
+ return _textEmbeddingProfiles.Values.Cast().ToList();
+ }
+
+ private List GetIndexingProfiles(List instances) where T : class
+ {
+ if (typeof(T) != typeof(IndexingProfile))
+ throw new ResourceProviderException($"The type of requested resource ({typeof(T)}) does not match the resource type specified in the path ({instances[0].ResourceType}).");
+
+ return _indexingProfiles.Values.Cast().ToList();
+ }
+
+ private T GetContentSourceProfile(List instances) where T: class
{
if (instances.Count != 1)
throw new ResourceProviderException($"Invalid resource path");
diff --git a/src/dotnet/Vectorization/Services/VectorizationStates/BlobStorageVectorizationStateService.cs b/src/dotnet/Vectorization/Services/VectorizationStates/BlobStorageVectorizationStateService.cs
index ef1d34e316..7efb854418 100644
--- a/src/dotnet/Vectorization/Services/VectorizationStates/BlobStorageVectorizationStateService.cs
+++ b/src/dotnet/Vectorization/Services/VectorizationStates/BlobStorageVectorizationStateService.cs
@@ -77,6 +77,7 @@ await _storageService.WriteFileAsync(
BLOB_STORAGE_CONTAINER_NAME,
artifactPath,
artifact.Content!,
+ default,
default);
artifact.CanonicalId = artifactPath;
}
@@ -86,6 +87,7 @@ await _storageService.WriteFileAsync(
BLOB_STORAGE_CONTAINER_NAME,
$"{persistenceIdentifier}.json",
content,
+ default,
default);
}
}
diff --git a/src/dotnet/VectorizationAPI/Program.cs b/src/dotnet/VectorizationAPI/Program.cs
index 1463c377c6..b3507abc49 100644
--- a/src/dotnet/VectorizationAPI/Program.cs
+++ b/src/dotnet/VectorizationAPI/Program.cs
@@ -20,6 +20,7 @@
using Microsoft.ApplicationInsights.AspNetCore.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
+using FoundationaLLM.Common.Models.Configuration.Instance;
var builder = WebApplication.CreateBuilder(args);
@@ -58,6 +59,8 @@
});
// Add configurations to the container
+builder.Services.AddOptions()
+ .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_Instance));
builder.Services.AddOptions()
.Bind(builder.Configuration.GetSection(AppConfigurationKeys.FoundationaLLM_Vectorization_VectorizationWorker));
diff --git a/src/dotnet/VectorizationWorker/Program.cs b/src/dotnet/VectorizationWorker/Program.cs
index ad61f014c6..41734c6402 100644
--- a/src/dotnet/VectorizationWorker/Program.cs
+++ b/src/dotnet/VectorizationWorker/Program.cs
@@ -3,6 +3,7 @@
using FoundationaLLM.Common.Authentication;
using FoundationaLLM.Common.Constants;
using FoundationaLLM.Common.Interfaces;
+using FoundationaLLM.Common.Models.Configuration.Instance;
using FoundationaLLM.Common.OpenAPI;
using FoundationaLLM.Common.Services;
using FoundationaLLM.Common.Services.Tokenizers;
@@ -56,6 +57,9 @@
});
// Add configurations to the container
+builder.Services.AddOptions()
+ .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_Instance));
+
builder.Services.AddOptions()
.Bind(builder.Configuration.GetSection(AppConfigurationKeys.FoundationaLLM_Vectorization_VectorizationWorker));
diff --git a/tests/dotnet/Common.Tests/Middleware/AgentHintMiddlewareTests.cs b/tests/dotnet/Common.Tests/Middleware/AgentHintMiddlewareTests.cs
index fcb66248f6..239baae1f3 100644
--- a/tests/dotnet/Common.Tests/Middleware/AgentHintMiddlewareTests.cs
+++ b/tests/dotnet/Common.Tests/Middleware/AgentHintMiddlewareTests.cs
@@ -11,6 +11,8 @@
using System.Text;
using System.Threading.Tasks;
using FoundationaLLM.Common.Models.Metadata;
+using FoundationaLLM.Common.Models.Configuration.Instance;
+using Microsoft.Extensions.Options;
namespace FoundationaLLM.Common.Tests.Middleware
{
@@ -23,6 +25,7 @@ public async Task InvokeAsync_WithAuthenticatedUser_ShouldSetCurrentUserIdentity
var context = new DefaultHttpContext();
var claimsProviderService = Substitute.For();
var callContext = Substitute.For();
+ var instanceSettings = Options.Create(Substitute.For());
var middleware = new CallContextMiddleware(next: _ => Task.FromResult(0));
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[]
{
@@ -33,7 +36,7 @@ public async Task InvokeAsync_WithAuthenticatedUser_ShouldSetCurrentUserIdentity
}, "mock"));
// Act
- await middleware.InvokeAsync(context, claimsProviderService, callContext);
+ await middleware.InvokeAsync(context, claimsProviderService, callContext, instanceSettings);
// Assert
claimsProviderService.Received(1).GetUserIdentity(context.User);
@@ -47,12 +50,13 @@ public async Task InvokeAsync_WithUnauthenticatedUser_ShouldSetCurrentUserIdenti
var context = new DefaultHttpContext();
var claimsProviderService = Substitute.For();
var callContext = Substitute.For();
+ var instanceSettings = Options.Create(Substitute.For());
var middleware = new CallContextMiddleware(next: _ => Task.FromResult(0));
var userIdentity = new UnifiedUserIdentity { Username = "testuser@example.com", UPN = "testuser@example.com", Name = "testuser" };
context.Request.Headers[Constants.HttpHeaders.UserIdentity] = JsonConvert.SerializeObject(userIdentity);
// Act
- await middleware.InvokeAsync(context, claimsProviderService, callContext);
+ await middleware.InvokeAsync(context, claimsProviderService, callContext, instanceSettings);
// Assert
callContext.Received(1).CurrentUserIdentity = Arg.Is(x => x.Username == userIdentity.Username && x.UPN == userIdentity.UPN && x.Name == userIdentity.Name);
@@ -65,6 +69,7 @@ public async Task InvokeAsync_WithAgentHint_ShouldSetAgentHint()
var context = new DefaultHttpContext();
var claimsProviderService = Substitute.For();
var callContext = Substitute.For();
+ var instanceSettings = Options.Create(Substitute.For());
var middleware = new CallContextMiddleware(next: _ => Task.FromResult(0));
var agentHint = new Agent
{
@@ -74,7 +79,7 @@ public async Task InvokeAsync_WithAgentHint_ShouldSetAgentHint()
context.Request.Headers[Constants.HttpHeaders.AgentHint] = JsonConvert.SerializeObject(agentHint);
// Act
- await middleware.InvokeAsync(context, claimsProviderService, callContext);
+ await middleware.InvokeAsync(context, claimsProviderService, callContext, instanceSettings);
// Assert
callContext.Received(1).AgentHint = Arg.Is(x => x.Name == agentHint.Name && x.Private == agentHint.Private);